diff --git a/Solar.php b/Solar.php index 268944b3..bd98474d 100755 --- a/Solar.php +++ b/Solar.php @@ -162,7 +162,7 @@ class Solar { * * @var string * - * @todo Keep the locale code in $_SESSION? + * @todo Keep the locale code in a session variable? * */ protected static $_locale_code = 'en_US'; @@ -193,8 +193,34 @@ public static function start($config = null) // where is Solar in the filesystem? Solar::$dir = dirname(__FILE__); - // do some security on globals, and turn off all magic quotes - Solar::_globalsQuotes(); + // clear out registered globals + // (this code from Richard Heyes and Stefan Esser) + if (ini_get('register_globals')) { + + // Variables that shouldn't be unset + $noUnset = array( + 'GLOBALS', '_GET', '_POST', '_COOKIE', + '_REQUEST', '_SERVER', '_ENV', '_FILES' + ); + + // sources of global input. + // + // the ternary check on $_SESSION is to make sure that + // it's really an array, not just a string; if it's just a + // string, that can bypass this check somehow. Stefan + // Esser knows how this works, but I don't. + $input = array_merge($_ENV, $_GET, $_POST, $_COOKIE, $_SERVER, $_FILES, + isset($_SESSION) && is_array($_SESSION) ? $_SESSION : array() + ); + + // unset globals set from input sources, but don't unset + // the sources themselves. + foreach ($input as $k => $v) { + if (! in_array($k, $noUnset) && isset($GLOBALS[$k])) { + unset($GLOBALS[$k]); + } + } + } // load the config file values. note that we use Solar::$config here, // not Solar::config(), because we are setting the value of the static @@ -236,12 +262,6 @@ public static function start($config = null) Solar::run($file); } - // start the session if one hasn't been started already, - // and if we're not in the command-line environment. - if (session_id() === '') { - session_start(); - } - // and we're done! Solar::$_status = true; } @@ -316,7 +336,21 @@ public static function locale($class, $key, $num = 1) // do we need to load locale strings for the class? if (! array_key_exists($class, Solar::$locale)) { - Solar::_loadLocale($class); + // build the file name + $base = str_replace('_', '/', $class); + $file = Solar::fixdir($base . '/Locale/') + . Solar::getLocale() . '.php'; + + // can we find the file? + if (Solar::fileExists($file)) { + // put the locale values into the shared locale array + Solar::$locale[$class] = (array) include $file; + } else { + // could not find file. + // fail silently, as it's often the case that the + // translation file simply doesn't exist. + Solar::$locale[$class] = array(); + } } // does the key exist for the class? @@ -703,166 +737,6 @@ public static function config($group, $elem = null) } } - /** - * - * Safely gets the value of an element from the $_GET array. - * - * @param string $key The array element; if null, returns the whole - * array. - * - * @param mixed $default If the requested array element is - * not set, return this value. - * - * @return mixed The array element value (if set), or the - * $default value (if not). - * - */ - public static function get($key = null, $default = null) - { - return Solar::_super('_GET', $key, $default); - } - - /** - * - * Safely gets the value of an element from the $_POST array. - * - * @param string $key The array element; if null, returns the whole - * array. - * - * @param mixed $default If the requested array element is - * not set, return this value. - * - * @return mixed The array element value (if set), or the - * $default value (if not). - * - */ - public static function post($key = null, $default = null) - { - return Solar::_super('_POST', $key, $default); - } - - /** - * - * Safely gets the value of an element from the $_COOKIE array. - * - * @param string $key The array element; if null, returns the whole - * array. - * - * @param mixed $default If the requested array element is - * not set, return this value. - * - * @return mixed The array element value (if set), or the - * $default value (if not). - * - */ - public static function cookie($key = null, $default = null) - { - return Solar::_super('_COOKIE', $key, $default); - } - - /** - * - * Safely gets the value of an element from the $_SERVER array. - * - * @param string $key The array element; if null, returns the whole - * array. - * - * @param mixed $default If the requested array element is - * not set, return this value. - * - * @return mixed The array element value (if set), or the - * $default value (if not). - * - */ - public static function server($key = null, $default = null) - { - return Solar::_super('_SERVER', $key, $default); - } - - /** - * - * Safely gets the value of an element from the $_SESSION array. - * - * @param string $key The array element; if null, returns the whole - * array. - * - * @param mixed $default If the requested array element is - * not set, return this value. - * - * @return mixed The array element value (if set), or the - * $default value (if not). - * - */ - public static function session($key = null, $default = null) - { - return Solar::_super('_SESSION', $key, $default); - } - - /** - * - * Safely gets the value of an element from the $_FILES array. - * - * Returns an empty array if the requested key does not exist. - * - * @param string $key The array element; if null, returns the whole - * array. - * - * @param mixed $default If the requested array element is - * not set, return this value. - * - * @return mixed The array element value (if set), or the - * $default value (if not). - * - */ - public static function files($key = null, $default = array()) - { - return Solar::_super('_FILES', $key, $default); - } - - /** - * - * Safely gets the value of $_SERVER['PATH_INFO'] element. - * - * @param int $key The array element; if null, returns the whole - * array. - * - * @param mixed $default If the requested array element is - * not set, return this value. - * - * @return mixed The array element value (if set), or the - * $default value (if not). - * - */ - public static function pathinfo($key = null, $default = null) - { - // get the pathinfo as passed - $info = Solar::_super('_SERVER', 'PATH_INFO', ''); - - // explode into its elements - $elem = explode('/', $info); - - // drop off the first element (it's always blank) - array_shift($elem); - - // look for the requested element number - if (is_null($key)) { - - // no key selected, return the whole $elem array - return $elem; - - } elseif (isset($elem[$key])) { - - // looking for a specific element key - return $elem[$key]; - - } else { - - // specified element key does not exist - return $default; - - } - } - /** * * Generates a simple exception, but does not throw it. @@ -872,7 +746,7 @@ public static function pathinfo($key = null, $default = null) * when no specific exception classes exist. For example, if a * class named 'Vendor_Example' extended from 'Vendor_Base' throws an * exception or error coded as 'ERR_FILE_NOT_FOUND', the method will - * attempt to return these exception classes in this order: + * attempt to return these exception classes in this order ... * * 1. Vendor_Example_Exception_FileNotFound (class specific) * @@ -1088,168 +962,5 @@ public static function parents($spec, $include_class = false) // done return $stack; } - - /** - * - * Performs some security on globals, removes magic quotes if turned on. - * - * @return void - * - */ - protected static function _globalsQuotes() - { - // clear out registered globals? - // (this code from Richard Heyes and Stefan Esser) - if (ini_get('register_globals')) { - - // Variables that shouldn't be unset - $noUnset = array( - 'GLOBALS', '_GET', '_POST', '_COOKIE', - '_REQUEST', '_SERVER', '_ENV', '_FILES' - ); - - // sources of global input. - // - // the ternary check on $_SESSION is to make sure that - // it's really an array, not just a string; if it's just a - // string, that can bypass this check somehow. Stefan - // Esser knows how this works, but I don't. - $input = array_merge($_GET, $_POST, $_COOKIE, - $_SERVER, $_ENV, $_FILES, - isset($_SESSION) && is_array($_SESSION) ? $_SESSION : array() - ); - - // unset globals set from input sources, but don't unset - // the sources themselves. - foreach ($input as $k => $v) { - if (! in_array($k, $noUnset) && isset($GLOBALS[$k])) { - unset($GLOBALS[$k]); - } - } - } - - // remove magic quotes if they are enabled; sybase quotes - // override normal quotes. - if (ini_get('magic_quotes_gpc')) { - - // what kind of quotes are we using? - if (ini_get('magic_quotes_sybase')) { - // sybase quotes - $func = array('Solar', '_dispelSybase'); - } else { - // "normal" slashed quotes - $func = array('Solar', '_dispelQuotes'); - } - - // dispel magic quotes from superglobals - array_walk_recursive($_GET, $func); - array_walk_recursive($_POST, $func); - array_walk_recursive($_COOKIE, $func); - array_walk_recursive($_FILES, $func); - array_walk_recursive($_SERVER, $func); - } - - // make sure automatic quoting of values from, e.g., SQL sources - // is turned off. turn off sybase quotes too. - ini_set('magic_quotes_runtime', false); - ini_set('magic_quotes_sybase', false); - } - - /** - * - * A stripslashes() alias that supports array_walk_recursive(). - * - * @param string &$value The value to strip slashes from. - * - * @return void - * - */ - protected static function _dispelQuotes(&$value) - { - $value = stripslashes($value); - } - - /** - * - * A str_replace() for Sybase quotes; supports array_walk_recursive(). - * - * @param string &$value The value to dispel Sybase quotes from. - * - * @return void - * - */ - protected static function _dispelSybase(&$value) - { - $value = str_replace("''", "'", $value); - } - - /** - * - * Fetches a superglobal value by key, or a default value. - * - * @param string $type The superglobal variable name to fetch from; - * e.g., '_SERVER' for $_SERVER or '_GET' for $_GET. - * - * @param string $key The superglobal array key to retrieve; if null, - * will return the entire superglobal array for that type. - * - * @param mixed $default If the requested superglobal array key does - * not exist, return this value instead. - * - * @return mixed The value of the superglobal type array key, or the - * default value if the key did not exist. - * - */ - protected static function _super($type, $key = null, $default = null) - { - // get the whole superglobal, or just one key? - if (is_null($key) && isset($GLOBALS[$type])) { - - // no key selected, return the whole array - return $GLOBALS[$type]; - - } elseif (isset($GLOBALS[$type][$key])) { - - // looking for a specific key - return $GLOBALS[$type][$key]; - - } else { - - // specified key does not exist - return $default; - - } - } - - /** - * - * Loads locale strings for a given class. - * - * For example, a Solar_Example_Class must have locale strings - * located at Solar/Example/Class/Locale/*. - * - * @param string $class The class to load strings for. - * - * @return void - * - */ - protected static function _loadLocale($class) - { - // build the file name - $base = str_replace('_', '/', $class); - $file = Solar::fixdir($base . '/Locale/') - . Solar::getLocale() . '.php'; - - // can we find the file? - if (Solar::fileExists($file)) { - // put the locale values into the shared locale array - Solar::$locale[$class] = (array) include $file; - } else { - // could not find file. - // fail silently, as it's often the case that the - // translation file simply doesn't exist. - Solar::$locale[$class] = array(); - } - } } ?> \ No newline at end of file diff --git a/Solar/Access/Adapter/File.php b/Solar/Access/Adapter/File.php index c6ed31dd..d2d76c07 100755 --- a/Solar/Access/Adapter/File.php +++ b/Solar/Access/Adapter/File.php @@ -24,18 +24,16 @@ * * Class for reading access privileges from a text file. * - * The file format is: + * The file format is ... * - * 0:flag 1:type 2:name 3:class 4:action 5:submit + * 0:flag 1:type 2:name 3:class 4:action 5:submit * - * E.g.: + * For example ... * - * - * deny handle * * * * - * allow role sysadmin * * * - * allow handle + Solar_App_Bookmarks * * - * deny user boshag Solar_App_Bookmarks edit * - * }} + * deny handle * * * * + * allow role sysadmin * * * + * allow handle + Solar_App_Bookmarks * * + * deny user boshag Solar_App_Bookmarks edit * * * @category Solar * diff --git a/Solar/App/Bookmarks.php b/Solar/App/Bookmarks.php index 708261c6..10dafed1 100755 --- a/Solar/App/Bookmarks.php +++ b/Solar/App/Bookmarks.php @@ -41,13 +41,13 @@ class Solar_App_Bookmarks extends Solar_App { * * Keys are ... * - * `area_name`: - * (string) The content area for the bookmarks - * app, default 'Solar_App_Bookmarks'. Will be created automatically - * if it does not exist. + * `area_name` + * : (string) The content area for the bookmarks + * app, default 'Solar_App_Bookmarks'. Will be created automatically + * if it does not exist. * - * `content`: - * (dependency) A Solar_Content domain model dependency object. + * `content` + * : (dependency) A Solar_Content domain model dependency object. * * @var array * @@ -319,7 +319,7 @@ public function actionAdd() } // build a link for _redirect() calls and the backlink. - $href = $this->_flash->get('backlink'); + $href = $this->_session->getFlash('backlink'); if (! $href) { // probably browsed to this page directly. link to the user's list. $uri = Solar::factory('Solar_Uri_Action'); @@ -327,7 +327,7 @@ public function actionAdd() } // keep the backlink for the next page load - $this->_flash->set('backlink', $href); + $this->_session->setFlash('backlink', $href); // build the basic form, populated with the bookmark data // from the database @@ -357,7 +357,7 @@ public function actionAdd() try { $item->save(); - $this->_flash->set('add_ok', true); + $this->_session->setFlash('add_ok', true); $this->_redirect("bookmarks/edit/{$item->id}"); } catch (Solar_Exception $e) { @@ -425,15 +425,15 @@ public function actionEdit($id = null) // otherwise, return the list for the user. // - $href = $this->_flash->get('backlink'); + $href = $this->_session->getFlash('backlink'); if (! $href) { // probably browsed directly to this page; return to the user's list $uri = Solar::factory('Solar_Uri_Action'); $href = $uri->quick("bookmarks/user/{$this->_user->auth->handle}"); } - + // keep the backlink for the next page load - $this->_flash->set('backlink', $href); + $this->_session->setFlash('backlink', $href); // --------------------------------------------------------------------- // @@ -446,7 +446,13 @@ public function actionEdit($id = null) // now populate the the submitted POST values to the form $form->populate(); - + + // was this from a quickmark or an add operation? + if ($this->_session->getFlash('add_ok')) { + $form->setStatus(true); + $form->feedback = $this->locale('SUCCESS_ADDED'); + } + // Save? if ($this->_isSubmit('save') && $form->validate()) { @@ -533,7 +539,7 @@ public function actionQuick($uri = null, $subj = null) // if the user *does* already have that URI bookmarked, // redirect to the existing bookmark. if (! empty($existing->id)) { - $this->_flash->set('backlink', $uri); + $this->_session->setFlash('backlink', $uri); $this->_redirect("bookmarks/edit/{$existing['id']}"); } @@ -612,11 +618,11 @@ public function actionTag($tags = null) $total = $this->_bookmarks->countPages($tags, $owner_handle); // flash forward the backlink in case we go to edit - $this->_flash->set('backlink', Solar::server('REQUEST_URI')); + $this->_session->setFlash('backlink', $this->_request->server('REQUEST_URI')); // assign everything else for the view $this->pages = $total['pages']; - $this->order = Solar::get('order', 'created_desc'); + $this->order = $this->_request->get('order', 'created_desc'); $this->page = $page; $this->owner_handle = null; // requested owner_handle $this->tags = $tags; // the requested tags @@ -631,7 +637,7 @@ public function actionTag($tags = null) $this->layout_link[] = array( 'rel' => 'alternate', 'type' => 'application/rss+xml', - 'title' => Solar::server('PATH_INFO'), + 'title' => $this->_request->server('PATH_INFO'), 'href' => $uri->fetch(), ); } @@ -698,14 +704,14 @@ public function actionUser($owner_handle = null, $tags = null) $total = $this->_bookmarks->countPages($tags, $owner_handle); // flash forward the backlink in case we go to edit - $this->_flash->set('backlink', Solar::server('REQUEST_URI')); + $this->_session->setFlash('backlink', $this->_request->server('REQUEST_URI')); // set the view $this->_view = 'browse'; // assign view vars $this->pages = $total['pages']; - $this->order = Solar::get('order', 'created_desc'); + $this->order = $this->_request->get('order', 'created_desc'); $this->page = $page; $this->owner_handle = $owner_handle; // requested owner_handle $this->tags = $tags; // the requested tags @@ -725,7 +731,7 @@ public function actionUser($owner_handle = null, $tags = null) $this->layout_link[] = array( 'rel' => 'alternate', 'type' => 'application/rss+xml', - 'title' => Solar::server('PATH_INFO'), + 'title' => $this->_request->server('PATH_INFO'), 'href' => $uri->fetch(), ); } diff --git a/Solar/App/Bookmarks/Locale/en_US.php b/Solar/App/Bookmarks/Locale/en_US.php index 96793a73..3a85ece9 100755 --- a/Solar/App/Bookmarks/Locale/en_US.php +++ b/Solar/App/Bookmarks/Locale/en_US.php @@ -61,6 +61,8 @@ 'ORDER_TAGS' => 'Tags', 'ORDER_TAGS_DESC' => 'Tags (desc)', + 'SUCCESS_ADDED' => 'Added new bookmark.', + 'BACKLINK' => 'Back', 'CONFIRM_DELETE' => 'Are you sure you want to delete this bookmark?', 'DRAG_THIS' => 'Drag this link to your toolbar for quick bookmarking', diff --git a/Solar/App/Bookmarks/View/edit.php b/Solar/App/Bookmarks/View/edit.php index 7ecf8551..5c2ce166 100755 --- a/Solar/App/Bookmarks/View/edit.php +++ b/Solar/App/Bookmarks/View/edit.php @@ -27,7 +27,7 @@ 'onclick' => "return confirm('" . $this->getTextRaw('CONFIRM_DELETE') . "')" ); - echo $this->form() + echo $this->form(array('id' => 'form-bookmark')) ->auto($this->formdata) ->hidden(array('name' => 'submit', 'value' => $this->getTextRaw('SUBMIT_SAVE'))) ->beginGroup() @@ -36,4 +36,26 @@ ->submit(array('name' => 'submit', 'value' => $this->getTextRaw('SUBMIT_DELETE'), 'attribs' => $attribs)) ->endGroup() ->fetch(); + + // add highlighting to all UL elements in the form. + // this works for success and failure, and for all + // individual failed elements. + $this->jsScriptaculous()->effect->highlight( + "#form-bookmark ul.success", + array( + 'duration' => 3, + 'endcolor' => '#aaaaff', + 'restorecolor' => true, + ) + ); + + $this->jsScriptaculous()->effect->highlight( + "#form-bookmark ul.failure", + array( + 'duration' => 3, + 'endcolor' => '#ffaaaa', + 'restorecolor' => true, + ) + ); + ?> diff --git a/Solar/App/Hello/View/rss.php b/Solar/App/Hello/View/rss.php index d8f40f85..b9fa222d 100755 --- a/Solar/App/Hello/View/rss.php +++ b/Solar/App/Hello/View/rss.php @@ -18,11 +18,13 @@ */ header('Content-Type: text/xml; charset=iso-8859-1'); echo '' . "\n"; +$request = Solar::factory('Solar_Request'); +$server = $request->server(); ?> Solar: Hello World - escape($_SERVER['REQUEST_URI']) ?> + escape($server['REQUEST_URI']) ?> Example hello world RSS feed date('', DATE_RFC822) ?> @@ -30,7 +32,7 @@ <?php echo $this->escape($this->text) ?> date(time(), DATE_RFC822) ?> escape($this->text) ?> - escape($_SERVER['REQUEST_URI']) ?> + escape($server['REQUEST_URI']) ?> \ No newline at end of file diff --git a/Solar/App/HelloAjax/View/main.php b/Solar/App/HelloAjax/View/main.php index e237fce6..5a010fad 100755 --- a/Solar/App/HelloAjax/View/main.php +++ b/Solar/App/HelloAjax/View/main.php @@ -17,14 +17,22 @@ * */ ?> -JsPrototype()->event +JsPrototype()->event ->observe('#top', 'click', 'function() { location.href = "http://solarphp.com"; }') ->observe('#top', 'mouseover', 'function() { this.style.cursor = "pointer"; }') - ->observe('#top', 'mouseout', 'function() { this.style.cursor = "auto"; }');?> + ->observe('#top', 'mouseout', 'function() { this.style.cursor = "auto"; }'); + +// sample onclick +$this->JsPrototype()->event->observe('#ahello', 'click', 'function(evt) { alert("hello!"); Event.stop(evt); }'); + + +?>

escape($this->text) ?>

-jsScriptaculous()->effect->highlight('#hello', array('duration' => 1.0));?> -jsScriptaculous()->control->inPlaceEditor('#hello', 'index.php', array( +JsScriptaculous()->effect->highlight('#hello', array('duration' => 1.0));?> +JsScriptaculous()->control->inPlaceEditor('#hello', 'index.php', array( 'rows' => 15, 'cols' => 40, 'ajaxOptions' => array( @@ -34,12 +42,16 @@ 'on404' => 'function(t) { alert(\'Error 404: location not found\'); }' ) ));?> + +

hello! ... an example of an unobtrusive "onclick" hyperlink.

+ +

escape($this->code) ?>

diff --git a/Solar/App/HelloAjax/View/rss.php b/Solar/App/HelloAjax/View/rss.php index d8f40f85..b9fa222d 100755 --- a/Solar/App/HelloAjax/View/rss.php +++ b/Solar/App/HelloAjax/View/rss.php @@ -18,11 +18,13 @@ */ header('Content-Type: text/xml; charset=iso-8859-1'); echo '' . "\n"; +$request = Solar::factory('Solar_Request'); +$server = $request->server(); ?> Solar: Hello World - escape($_SERVER['REQUEST_URI']) ?> + escape($server['REQUEST_URI']) ?> Example hello world RSS feed date('', DATE_RFC822) ?> @@ -30,7 +32,7 @@ <?php echo $this->escape($this->text) ?> date(time(), DATE_RFC822) ?> escape($this->text) ?> - escape($_SERVER['REQUEST_URI']) ?> + escape($server['REQUEST_URI']) ?> \ No newline at end of file diff --git a/Solar/App/Layout/_head.php b/Solar/App/Layout/_head.php index df89b1fc..01b7d4e6 100644 --- a/Solar/App/Layout/_head.php +++ b/Solar/App/Layout/_head.php @@ -39,6 +39,10 @@ } } +// JavaScript Helper-required styles before App styles, so that App styles may +// override bundled style files. +echo $this->js()->fetchStyles(); + // styles if (! empty($this->layout_style)) { foreach ((array) $this->layout_style as $val) { diff --git a/Solar/App/Public/scripts-min/prototype/prototype.js b/Solar/App/Public/scripts-min/prototype/prototype.js new file mode 100644 index 00000000..b519de7a --- /dev/null +++ b/Solar/App/Public/scripts-min/prototype/prototype.js @@ -0,0 +1,166 @@ +// Prototype 1.5.0_rc1, compressed by jsmin (http://www.crockford.com/javascript/jsmin.html). + +var Prototype={Version:'1.5.0_rc1',ScriptFragment:'(?:)((\n|\r|.)*?)(?:<\/script>)',emptyFunction:function(){},K:function(x){return x}} +var Class={create:function(){return function(){this.initialize.apply(this,arguments);}}} +var Abstract=new Object();Object.extend=function(destination,source){for(var property in source){destination[property]=source[property];} +return destination;} +Object.extend(Object,{inspect:function(object){try{if(object==undefined)return'undefined';if(object==null)return'null';return object.inspect?object.inspect():object.toString();}catch(e){if(e instanceof RangeError)return'...';throw e;}},keys:function(object){var keys=[];for(var property in object) +keys.push(property);return keys;},values:function(object){var values=[];for(var property in object) +values.push(object[property]);return values;},clone:function(object){return Object.extend({},object);}});Function.prototype.bind=function(){var __method=this,args=$A(arguments),object=args.shift();return function(){return __method.apply(object,args.concat($A(arguments)));}} +Function.prototype.bindAsEventListener=function(object){var __method=this,args=$A(arguments),object=args.shift();return function(event){return __method.apply(object,[(event||window.event)].concat(args).concat($A(arguments)));}} +Object.extend(Number.prototype,{toColorPart:function(){var digits=this.toString(16);if(this<16)return'0'+digits;return digits;},succ:function(){return this+1;},times:function(iterator){$R(0,this,true).each(iterator);return this;}});var Try={these:function(){var returnValue;for(var i=0;i0){if(match=source.match(pattern)){result+=source.slice(0,match.index);result+=(replacement(match)||'').toString();source=source.slice(match.index+match[0].length);}else{result+=source,source='';}} +return result;},sub:function(pattern,replacement,count){replacement=this.gsub.prepareReplacement(replacement);count=count===undefined?1:count;return this.gsub(pattern,function(match){if(--count<0)return match[0];return replacement(match);});},scan:function(pattern,iterator){this.gsub(pattern,iterator);return this;},truncate:function(length,truncation){length=length||30;truncation=truncation===undefined?'...':truncation;return this.length>length?this.slice(0,length-truncation.length)+truncation:this;},strip:function(){return this.replace(/^\s+/,'').replace(/\s+$/,'');},stripTags:function(){return this.replace(/<\/?[^>]+>/gi,'');},stripScripts:function(){return this.replace(new RegExp(Prototype.ScriptFragment,'img'),'');},extractScripts:function(){var matchAll=new RegExp(Prototype.ScriptFragment,'img');var matchOne=new RegExp(Prototype.ScriptFragment,'im');return(this.match(matchAll)||[]).map(function(scriptTag){return(scriptTag.match(matchOne)||['',''])[1];});},evalScripts:function(){return this.extractScripts().map(function(script){return eval(script)});},escapeHTML:function(){var div=document.createElement('div');var text=document.createTextNode(this);div.appendChild(text);return div.innerHTML;},unescapeHTML:function(){var div=document.createElement('div');div.innerHTML=this.stripTags();return div.childNodes[0]?div.childNodes[0].nodeValue:'';},toQueryParams:function(){var pairs=this.match(/^\??(.*)$/)[1].split('&');return pairs.inject({},function(params,pairString){var pair=pairString.split('=');var value=pair[1]?decodeURIComponent(pair[1]):undefined;params[decodeURIComponent(pair[0])]=value;return params;});},toArray:function(){return this.split('');},camelize:function(){var oStringList=this.split('-');if(oStringList.length==1)return oStringList[0];var camelizedString=this.indexOf('-')==0?oStringList[0].charAt(0).toUpperCase()+oStringList[0].substring(1):oStringList[0];for(var i=1,len=oStringList.length;i=result) +result=value;});return result;},min:function(iterator){var result;this.each(function(value,index){value=(iterator||Prototype.K)(value,index);if(result==undefined||valueb?1:0;}).pluck('value');},toArray:function(){return this.collect(Prototype.K);},zip:function(){var iterator=Prototype.K,args=$A(arguments);if(typeof args.last()=='function') +iterator=args.pop();var collections=[this].concat(args).map($A);return this.map(function(value,index){return iterator(collections.pluck(index));});},inspect:function(){return'#';}} +Object.extend(Enumerable,{map:Enumerable.collect,find:Enumerable.detect,select:Enumerable.findAll,member:Enumerable.include,entries:Enumerable.toArray});var $A=Array.from=function(iterable){if(!iterable)return[];if(iterable.toArray){return iterable.toArray();}else{var results=[];for(var i=0;i1?this:this[0];},uniq:function(){return this.inject([],function(array,value){return array.include(value)?array:array.concat([value]);});},inspect:function(){return'['+this.map(Object.inspect).join(', ')+']';}});var Hash={_each:function(iterator){for(var key in this){var value=this[key];if(typeof value=='function')continue;var pair=[key,value];pair.key=key;pair.value=value;iterator(pair);}},keys:function(){return this.pluck('key');},values:function(){return this.pluck('value');},merge:function(hash){return $H(hash).inject($H(this),function(mergedHash,pair){mergedHash[pair.key]=pair.value;return mergedHash;});},toQueryString:function(){return this.map(function(pair){return pair.map(encodeURIComponent).join('=');}).join('&');},inspect:function(){return'#';}} +function $H(object){var hash=Object.extend({},object||{});Object.extend(hash,Enumerable);Object.extend(hash,Hash);return hash;} +ObjectRange=Class.create();Object.extend(ObjectRange.prototype,Enumerable);Object.extend(ObjectRange.prototype,{initialize:function(start,end,exclusive){this.start=start;this.end=end;this.exclusive=exclusive;},_each:function(iterator){var value=this.start;while(this.include(value)){iterator(value);value=value.succ();}},include:function(value){if(value=200&&this.transport.status<300);},responseIsFailure:function(){return!this.responseIsSuccess();}} +Ajax.Request=Class.create();Ajax.Request.Events=['Uninitialized','Loading','Loaded','Interactive','Complete'];Ajax.Request.prototype=Object.extend(new Ajax.Base(),{initialize:function(url,options){this.transport=Ajax.getTransport();this.setOptions(options);this.request(url);},request:function(url){var parameters=this.options.parameters||'';if(parameters.length>0)parameters+='&_=';if(this.options.method!='get'&&this.options.method!='post'){parameters+=(parameters.length>0?'&':'')+'_method='+this.options.method;this.options.method='post';} +try{this.url=url;if(this.options.method=='get'&¶meters.length>0) +this.url+=(this.url.match(/\?/)?'&':'?')+parameters;Ajax.Responders.dispatch('onCreate',this,this.transport);this.transport.open(this.options.method,this.url,this.options.asynchronous);if(this.options.asynchronous) +setTimeout(function(){this.respondToReadyState(1)}.bind(this),10);this.transport.onreadystatechange=this.onStateChange.bind(this);this.setRequestHeaders();var body=this.options.postBody?this.options.postBody:parameters;this.transport.send(this.options.method=='post'?body:null);if(!this.options.asynchronous&&this.transport.overrideMimeType) +this.onStateChange();}catch(e){this.dispatchException(e);}},setRequestHeaders:function(){var requestHeaders=['X-Requested-With','XMLHttpRequest','X-Prototype-Version',Prototype.Version,'Accept','text/javascript, text/html, application/xml, text/xml, */*'];if(this.options.method=='post'){requestHeaders.push('Content-type',this.options.contentType);if(this.transport.overrideMimeType) +requestHeaders.push('Connection','close');} +if(this.options.requestHeaders) +requestHeaders.push.apply(requestHeaders,this.options.requestHeaders);for(var i=0;i';},recursivelyCollect:function(element,property){element=$(element);var elements=[];while(element=element[property]) +if(element.nodeType==1) +elements.push(Element.extend(element));return elements;},ancestors:function(element){return $(element).recursivelyCollect('parentNode');},descendants:function(element){element=$(element);return $A(element.getElementsByTagName('*'));},previousSiblings:function(element){return $(element).recursivelyCollect('previousSibling');},nextSiblings:function(element){return $(element).recursivelyCollect('nextSibling');},siblings:function(element){element=$(element);return element.previousSiblings().reverse().concat(element.nextSiblings());},match:function(element,selector){element=$(element);if(typeof selector=='string') +selector=new Selector(selector);return selector.match(element);},up:function(element,expression,index){return Selector.findElement($(element).ancestors(),expression,index);},down:function(element,expression,index){return Selector.findElement($(element).descendants(),expression,index);},previous:function(element,expression,index){return Selector.findElement($(element).previousSiblings(),expression,index);},next:function(element,expression,index){return Selector.findElement($(element).nextSiblings(),expression,index);},getElementsBySelector:function(){var args=$A(arguments),element=$(args.shift());return Selector.findChildElements(element,args);},getElementsByClassName:function(element,className){element=$(element);return document.getElementsByClassName(className,element);},getHeight:function(element){element=$(element);return element.offsetHeight;},classNames:function(element){return new Element.ClassNames(element);},hasClassName:function(element,className){if(!(element=$(element)))return;return Element.classNames(element).include(className);},addClassName:function(element,className){if(!(element=$(element)))return;Element.classNames(element).add(className);return element;},removeClassName:function(element,className){if(!(element=$(element)))return;Element.classNames(element).remove(className);return element;},observe:function(){Event.observe.apply(Event,arguments);return $A(arguments).first();},stopObserving:function(){Event.stopObserving.apply(Event,arguments);return $A(arguments).first();},cleanWhitespace:function(element){element=$(element);var node=element.firstChild;while(node){var nextNode=node.nextSibling;if(node.nodeType==3&&!/\S/.test(node.nodeValue)) +element.removeChild(node);node=nextNode;} +return element;},empty:function(element){return $(element).innerHTML.match(/^\s*$/);},childOf:function(element,ancestor){element=$(element),ancestor=$(ancestor);while(element=element.parentNode) +if(element==ancestor)return true;return false;},scrollTo:function(element){element=$(element);var x=element.x?element.x:element.offsetLeft,y=element.y?element.y:element.offsetTop;window.scrollTo(x,y);return element;},getStyle:function(element,style){element=$(element);var value=element.style[style.camelize()];if(!value){if(document.defaultView&&document.defaultView.getComputedStyle){var css=document.defaultView.getComputedStyle(element,null);value=css?css.getPropertyValue(style):null;}else if(element.currentStyle){value=element.currentStyle[style.camelize()];}} +if(window.opera&&['left','top','right','bottom'].include(style)) +if(Element.getStyle(element,'position')=='static')value='auto';return value=='auto'?null:value;},setStyle:function(element,style){element=$(element);for(var name in style) +element.style[name.camelize()]=style[name];return element;},getDimensions:function(element){element=$(element);if(Element.getStyle(element,'display')!='none') +return{width:element.offsetWidth,height:element.offsetHeight};var els=element.style;var originalVisibility=els.visibility;var originalPosition=els.position;els.visibility='hidden';els.position='absolute';els.display='';var originalWidth=element.clientWidth;var originalHeight=element.clientHeight;els.display='none';els.position=originalPosition;els.visibility=originalVisibility;return{width:originalWidth,height:originalHeight};},makePositioned:function(element){element=$(element);var pos=Element.getStyle(element,'position');if(pos=='static'||!pos){element._madePositioned=true;element.style.position='relative';if(window.opera){element.style.top=0;element.style.left=0;}} +return element;},undoPositioned:function(element){element=$(element);if(element._madePositioned){element._madePositioned=undefined;element.style.position=element.style.top=element.style.left=element.style.bottom=element.style.right='';} +return element;},makeClipping:function(element){element=$(element);if(element._overflow)return;element._overflow=element.style.overflow||'auto';if((Element.getStyle(element,'overflow')||'visible')!='hidden') +element.style.overflow='hidden';return element;},undoClipping:function(element){element=$(element);if(!element._overflow)return;element.style.overflow=element._overflow=='auto'?'':element._overflow;element._overflow=null;return element;}} +if(document.all){Element.Methods.update=function(element,html){element=$(element);var tagName=element.tagName.toUpperCase();if(['THEAD','TBODY','TR','TD'].indexOf(tagName)>-1){var div=document.createElement('div');switch(tagName){case'THEAD':case'TBODY':div.innerHTML=''+html.stripScripts()+'
';depth=2;break;case'TR':div.innerHTML=''+html.stripScripts()+'
';depth=3;break;case'TD':div.innerHTML='
'+html.stripScripts()+'
';depth=4;} +$A(element.childNodes).each(function(node){element.removeChild(node)});depth.times(function(){div=div.firstChild});$A(div.childNodes).each(function(node){element.appendChild(node)});}else{element.innerHTML=html.stripScripts();} +setTimeout(function(){html.evalScripts()},10);return element;}} +Object.extend(Element,Element.Methods);var _nativeExtensions=false;if(!window.HTMLElement&&/Konqueror|Safari|KHTML/.test(navigator.userAgent)){['','Form','Input','TextArea','Select'].each(function(tag){var klass=window['HTML'+tag+'Element']={};klass.prototype=document.createElement(tag?tag.toLowerCase():'div').__proto__;});} +Element.addMethods=function(methods){Object.extend(Element.Methods,methods||{});function copy(methods,destination){var cache=Element.extend.cache;for(var property in methods){var value=methods[property];destination[property]=cache.findOrStore(value);}} +if(typeof HTMLElement!='undefined'){copy(Element.Methods,HTMLElement.prototype);copy(Form.Methods,HTMLFormElement.prototype);[HTMLInputElement,HTMLTextAreaElement,HTMLSelectElement].each(function(klass){copy(Form.Element.Methods,klass.prototype);});_nativeExtensions=true;}} +var Toggle=new Object();Toggle.display=Element.toggle;Abstract.Insertion=function(adjacency){this.adjacency=adjacency;} +Abstract.Insertion.prototype={initialize:function(element,content){this.element=$(element);this.content=content.stripScripts();if(this.adjacency&&this.element.insertAdjacentHTML){try{this.element.insertAdjacentHTML(this.adjacency,this.content);}catch(e){var tagName=this.element.tagName.toLowerCase();if(tagName=='tbody'||tagName=='tr'){this.insertContent(this.contentFromAnonymousTable());}else{throw e;}}}else{this.range=this.element.ownerDocument.createRange();if(this.initializeRange)this.initializeRange();this.insertContent([this.range.createContextualFragment(this.content)]);} +setTimeout(function(){content.evalScripts()},10);},contentFromAnonymousTable:function(){var div=document.createElement('div');div.innerHTML=''+this.content+'
';return $A(div.childNodes[0].childNodes[0].childNodes);}} +var Insertion=new Object();Insertion.Before=Class.create();Insertion.Before.prototype=Object.extend(new Abstract.Insertion('beforeBegin'),{initializeRange:function(){this.range.setStartBefore(this.element);},insertContent:function(fragments){fragments.each((function(fragment){this.element.parentNode.insertBefore(fragment,this.element);}).bind(this));}});Insertion.Top=Class.create();Insertion.Top.prototype=Object.extend(new Abstract.Insertion('afterBegin'),{initializeRange:function(){this.range.selectNodeContents(this.element);this.range.collapse(true);},insertContent:function(fragments){fragments.reverse(false).each((function(fragment){this.element.insertBefore(fragment,this.element.firstChild);}).bind(this));}});Insertion.Bottom=Class.create();Insertion.Bottom.prototype=Object.extend(new Abstract.Insertion('beforeEnd'),{initializeRange:function(){this.range.selectNodeContents(this.element);this.range.collapse(this.element);},insertContent:function(fragments){fragments.each((function(fragment){this.element.appendChild(fragment);}).bind(this));}});Insertion.After=Class.create();Insertion.After.prototype=Object.extend(new Abstract.Insertion('afterEnd'),{initializeRange:function(){this.range.setStartAfter(this.element);},insertContent:function(fragments){fragments.each((function(fragment){this.element.parentNode.insertBefore(fragment,this.element.nextSibling);}).bind(this));}});Element.ClassNames=Class.create();Element.ClassNames.prototype={initialize:function(element){this.element=$(element);},_each:function(iterator){this.element.className.split(/\s+/).select(function(name){return name.length>0;})._each(iterator);},set:function(className){this.element.className=className;},add:function(classNameToAdd){if(this.include(classNameToAdd))return;this.set(this.toArray().concat(classNameToAdd).join(' '));},remove:function(classNameToRemove){if(!this.include(classNameToRemove))return;this.set(this.select(function(className){return className!=classNameToRemove;}).join(' '));},toString:function(){return this.toArray().join(' ');}} +Object.extend(Element.ClassNames.prototype,Enumerable);var Selector=Class.create();Selector.prototype={initialize:function(expression){this.params={classNames:[]};this.expression=expression.toString().strip();this.parseExpression();this.compileMatcher();},parseExpression:function(){function abort(message){throw'Parse error in selector: '+message;} +if(this.expression=='')abort('empty expression');var params=this.params,expr=this.expression,match,modifier,clause,rest;while(match=expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)){params.attributes=params.attributes||[];params.attributes.push({name:match[2],operator:match[3],value:match[4]||match[5]||''});expr=match[1];} +if(expr=='*')return this.params.wildcard=true;while(match=expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)){modifier=match[1],clause=match[2],rest=match[3];switch(modifier){case'#':params.id=clause;break;case'.':params.classNames.push(clause);break;case'':case undefined:params.tagName=clause.toUpperCase();break;default:abort(expr.inspect());} +expr=rest;} +if(expr.length>0)abort(expr.inspect());},buildMatchExpression:function(){var params=this.params,conditions=[],clause;if(params.wildcard) +conditions.push('true');if(clause=params.id) +conditions.push('element.id == '+clause.inspect());if(clause=params.tagName) +conditions.push('element.tagName.toUpperCase() == '+clause.inspect());if((clause=params.classNames).length>0) +for(var i=0;i=0){opt=element.options[index];value=opt.value||opt.text;} +return[element.name,value];},selectMany:function(element){var value=[];for(var i=0;i=this.offset[1]&&y=this.offset[0]&&x=this.offset[1]&&this.ycomp=this.offset[0]&&this.xcomp";}catch(e){} +var element=parentElement.firstChild||null;if(element&&(element.tagName!=elementName)) +element=element.getElementsByTagName(elementName)[0];if(!element)element=document.createElement(elementName);if(!element)return;if(arguments[1]) +if(this._isStringOrNumber(arguments[1])||(arguments[1]instanceof Array)){this._children(element,arguments[1]);}else{var attrs=this._attributes(arguments[1]);if(attrs.length){try{parentElement.innerHTML="<"+elementName+" "+ +attrs+">";}catch(e){} +element=parentElement.firstChild||null;if(!element){element=document.createElement(elementName);for(attr in arguments[1]) +element[attr=='class'?'className':attr]=arguments[1][attr];} +if(element.tagName!=elementName) +element=parentElement.getElementsByTagName(elementName)[0];}} +if(arguments[2]) +this._children(element,arguments[2]);return element;},_text:function(text){return document.createTextNode(text);},_attributes:function(attributes){var attrs=[];for(attribute in attributes) +attrs.push((attribute=='className'?'class':attribute)+'="'+attributes[attribute].toString().escapeHTML()+'"');return attrs.join(" ");},_children:function(element,children){if(typeof children=='object'){children.flatten().each(function(e){if(typeof e=='object') +element.appendChild(e) +else +if(Builder._isStringOrNumber(e)) +element.appendChild(Builder._text(e));});}else +if(Builder._isStringOrNumber(children)) +element.appendChild(Builder._text(children));},_isStringOrNumber:function(param){return(typeof param=='string'||typeof param=='number');},dump:function(scope){if(typeof scope!='object'&&typeof scope!='function')scope=window;var tags=("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY "+"BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET "+"FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX "+"KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P "+"PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD "+"TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/);tags.each(function(tag){scope[tag]=function(){return Builder.node.apply(Builder,[tag].concat($A(arguments)));}});}} \ No newline at end of file diff --git a/Solar/App/Public/scripts-min/scriptaculous/controls.js b/Solar/App/Public/scripts-min/scriptaculous/controls.js new file mode 100644 index 00000000..84fe9b47 --- /dev/null +++ b/Solar/App/Public/scripts-min/scriptaculous/controls.js @@ -0,0 +1,60 @@ +// From script.aculo.us 1.6.4, compressed by jsmin (http://www.crockford.com/javascript/jsmin.html). + +if(typeof Effect=='undefined') +throw("controls.js requires including script.aculo.us' effects.js library");var Autocompleter={} +Autocompleter.Base=function(){};Autocompleter.Base.prototype={baseInitialize:function(element,update,options){this.element=$(element);this.update=$(update);this.hasFocus=false;this.changed=false;this.active=false;this.index=0;this.entryCount=0;if(this.setOptions) +this.setOptions(options);else +this.options=options||{};this.options.paramName=this.options.paramName||this.element.name;this.options.tokens=this.options.tokens||[];this.options.frequency=this.options.frequency||0.4;this.options.minChars=this.options.minChars||1;this.options.onShow=this.options.onShow||function(element,update){if(!update.style.position||update.style.position=='absolute'){update.style.position='absolute';Position.clone(element,update,{setHeight:false,offsetTop:element.offsetHeight});} +Effect.Appear(update,{duration:0.15});};this.options.onHide=this.options.onHide||function(element,update){new Effect.Fade(update,{duration:0.15})};if(typeof(this.options.tokens)=='string') +this.options.tokens=new Array(this.options.tokens);this.observer=null;this.element.setAttribute('autocomplete','off');Element.hide(this.update);Event.observe(this.element,"blur",this.onBlur.bindAsEventListener(this));Event.observe(this.element,"keypress",this.onKeyPress.bindAsEventListener(this));},show:function(){if(Element.getStyle(this.update,'display')=='none')this.options.onShow(this.element,this.update);if(!this.iefix&&(navigator.appVersion.indexOf('MSIE')>0)&&(navigator.userAgent.indexOf('Opera')<0)&&(Element.getStyle(this.update,'position')=='absolute')){new Insertion.After(this.update,'');this.iefix=$(this.update.id+'_iefix');} +if(this.iefix)setTimeout(this.fixIEOverlapping.bind(this),50);},fixIEOverlapping:function(){Position.clone(this.update,this.iefix,{setTop:(!this.update.style.height)});this.iefix.style.zIndex=1;this.update.style.zIndex=2;Element.show(this.iefix);},hide:function(){this.stopIndicator();if(Element.getStyle(this.update,'display')!='none')this.options.onHide(this.element,this.update);if(this.iefix)Element.hide(this.iefix);},startIndicator:function(){if(this.options.indicator)Element.show(this.options.indicator);},stopIndicator:function(){if(this.options.indicator)Element.hide(this.options.indicator);},onKeyPress:function(event){if(this.active) +switch(event.keyCode){case Event.KEY_TAB:case Event.KEY_RETURN:this.selectEntry();Event.stop(event);case Event.KEY_ESC:this.hide();this.active=false;Event.stop(event);return;case Event.KEY_LEFT:case Event.KEY_RIGHT:return;case Event.KEY_UP:this.markPrevious();this.render();if(navigator.appVersion.indexOf('AppleWebKit')>0)Event.stop(event);return;case Event.KEY_DOWN:this.markNext();this.render();if(navigator.appVersion.indexOf('AppleWebKit')>0)Event.stop(event);return;} +else +if(event.keyCode==Event.KEY_TAB||event.keyCode==Event.KEY_RETURN||(navigator.appVersion.indexOf('AppleWebKit')>0&&event.keyCode==0))return;this.changed=true;this.hasFocus=true;if(this.observer)clearTimeout(this.observer);this.observer=setTimeout(this.onObserverEvent.bind(this),this.options.frequency*1000);},activate:function(){this.changed=false;this.hasFocus=true;this.getUpdatedChoices();},onHover:function(event){var element=Event.findElement(event,'LI');if(this.index!=element.autocompleteIndex) +{this.index=element.autocompleteIndex;this.render();} +Event.stop(event);},onClick:function(event){var element=Event.findElement(event,'LI');this.index=element.autocompleteIndex;this.selectEntry();this.hide();},onBlur:function(event){setTimeout(this.hide.bind(this),250);this.hasFocus=false;this.active=false;},render:function(){if(this.entryCount>0){for(var i=0;i0)this.index-- +else this.index=this.entryCount-1;this.getEntry(this.index).scrollIntoView(true);},markNext:function(){if(this.index0)value=Element.collectTextNodes(nodes[0],this.options.select);}else +value=Element.collectTextNodesIgnoreClass(selectedElement,'informal');var lastTokenPos=this.findLastToken();if(lastTokenPos!=-1){var newValue=this.element.value.substr(0,lastTokenPos+1);var whitespace=this.element.value.substr(lastTokenPos+1).match(/^\s+/);if(whitespace) +newValue+=whitespace[0];this.element.value=newValue+value;}else{this.element.value=value;} +this.element.focus();if(this.options.afterUpdateElement) +this.options.afterUpdateElement(this.element,selectedElement);},updateChoices:function(choices){if(!this.changed&&this.hasFocus){this.update.innerHTML=choices;Element.cleanWhitespace(this.update);Element.cleanWhitespace(this.update.firstChild);if(this.update.firstChild&&this.update.firstChild.childNodes){this.entryCount=this.update.firstChild.childNodes.length;for(var i=0;i=this.options.minChars){this.startIndicator();this.getUpdatedChoices();}else{this.active=false;this.hide();}},getToken:function(){var tokenPos=this.findLastToken();if(tokenPos!=-1) +var ret=this.element.value.substr(tokenPos+1).replace(/^\s+/,'').replace(/\s+$/,'');else +var ret=this.element.value;return/\n/.test(ret)?'':ret;},findLastToken:function(){var lastTokenPos=-1;for(var i=0;ilastTokenPos) +lastTokenPos=thisTokenPos;} +return lastTokenPos;}} +Ajax.Autocompleter=Class.create();Object.extend(Object.extend(Ajax.Autocompleter.prototype,Autocompleter.Base.prototype),{initialize:function(element,update,url,options){this.baseInitialize(element,update,options);this.options.asynchronous=true;this.options.onComplete=this.onComplete.bind(this);this.options.defaultParams=this.options.parameters||null;this.url=url;},getUpdatedChoices:function(){entry=encodeURIComponent(this.options.paramName)+'='+ +encodeURIComponent(this.getToken());this.options.parameters=this.options.callback?this.options.callback(this.element,entry):entry;if(this.options.defaultParams) +this.options.parameters+='&'+this.options.defaultParams;new Ajax.Request(this.url,this.options);},onComplete:function(request){this.updateChoices(request.responseText);}});Autocompleter.Local=Class.create();Autocompleter.Local.prototype=Object.extend(new Autocompleter.Base(),{initialize:function(element,update,array,options){this.baseInitialize(element,update,options);this.options.array=array;},getUpdatedChoices:function(){this.updateChoices(this.options.selector(this));},setOptions:function(options){this.options=Object.extend({choices:10,partialSearch:true,partialChars:2,ignoreCase:true,fullSearch:false,selector:function(instance){var ret=[];var partial=[];var entry=instance.getToken();var count=0;for(var i=0;i"+elem.substr(0,entry.length)+""+ +elem.substr(entry.length)+"");break;}else if(entry.length>=instance.options.partialChars&&instance.options.partialSearch&&foundPos!=-1){if(instance.options.fullSearch||/\s/.test(elem.substr(foundPos-1,1))){partial.push("
  • "+elem.substr(0,foundPos)+""+ +elem.substr(foundPos,entry.length)+""+elem.substr(foundPos+entry.length)+"
  • ");break;}} +foundPos=instance.options.ignoreCase?elem.toLowerCase().indexOf(entry.toLowerCase(),foundPos+1):elem.indexOf(entry,foundPos+1);}} +if(partial.length) +ret=ret.concat(partial.slice(0,instance.options.choices-ret.length)) +return"
      "+ret.join('')+"
    ";}},options||{});}});Field.scrollFreeActivate=function(field){setTimeout(function(){Field.activate(field);},1);} +Ajax.InPlaceEditor=Class.create();Ajax.InPlaceEditor.defaultHighlightColor="#FFFF99";Ajax.InPlaceEditor.prototype={initialize:function(element,url,options){this.url=url;this.element=$(element);this.options=Object.extend({okButton:true,okText:"ok",cancelLink:true,cancelText:"cancel",savingText:"Saving...",clickToEditText:"Click to edit",okText:"ok",rows:1,onComplete:function(transport,element){new Effect.Highlight(element,{startcolor:this.options.highlightcolor});},onFailure:function(transport){alert("Error communicating with the server: "+transport.responseText.stripTags());},callback:function(form){return Form.serialize(form);},handleLineBreaks:true,loadingText:'Loading...',savingClassName:'inplaceeditor-saving',loadingClassName:'inplaceeditor-loading',formClassName:'inplaceeditor-form',highlightcolor:Ajax.InPlaceEditor.defaultHighlightColor,highlightendcolor:"#FFFFFF",externalControl:null,submitOnBlur:false,ajaxOptions:{},evalScripts:false},options||{});if(!this.options.formId&&this.element.id){this.options.formId=this.element.id+"-inplaceeditor";if($(this.options.formId)){this.options.formId=null;}} +if(this.options.externalControl){this.options.externalControl=$(this.options.externalControl);} +this.originalBackground=Element.getStyle(this.element,'background-color');if(!this.originalBackground){this.originalBackground="transparent";} +this.element.title=this.options.clickToEditText;this.onclickListener=this.enterEditMode.bindAsEventListener(this);this.mouseoverListener=this.enterHover.bindAsEventListener(this);this.mouseoutListener=this.leaveHover.bindAsEventListener(this);Event.observe(this.element,'click',this.onclickListener);Event.observe(this.element,'mouseover',this.mouseoverListener);Event.observe(this.element,'mouseout',this.mouseoutListener);if(this.options.externalControl){Event.observe(this.options.externalControl,'click',this.onclickListener);Event.observe(this.options.externalControl,'mouseover',this.mouseoverListener);Event.observe(this.options.externalControl,'mouseout',this.mouseoutListener);}},enterEditMode:function(evt){if(this.saving)return;if(this.editing)return;this.editing=true;this.onEnterEditMode();if(this.options.externalControl){Element.hide(this.options.externalControl);} +Element.hide(this.element);this.createForm();this.element.parentNode.insertBefore(this.form,this.element);if(!this.options.loadTextURL)Field.scrollFreeActivate(this.editField);if(evt){Event.stop(evt);} +return false;},createForm:function(){this.form=document.createElement("form");this.form.id=this.options.formId;Element.addClassName(this.form,this.options.formClassName) +this.form.onsubmit=this.onSubmit.bind(this);this.createEditField();if(this.options.textarea){var br=document.createElement("br");this.form.appendChild(br);} +if(this.options.okButton){okButton=document.createElement("input");okButton.type="submit";okButton.value=this.options.okText;okButton.className='editor_ok_button';this.form.appendChild(okButton);} +if(this.options.cancelLink){cancelLink=document.createElement("a");cancelLink.href="#";cancelLink.appendChild(document.createTextNode(this.options.cancelText));cancelLink.onclick=this.onclickCancel.bind(this);cancelLink.className='editor_cancel';this.form.appendChild(cancelLink);}},hasHTMLLineBreaks:function(string){if(!this.options.handleLineBreaks)return false;return string.match(/
    /i);},convertHTMLLineBreaks:function(string){return string.replace(/
    /gi,"\n").replace(//gi,"\n").replace(/<\/p>/gi,"\n").replace(/

    /gi,"");},createEditField:function(){var text;if(this.options.loadTextURL){text=this.options.loadingText;}else{text=this.getText();} +var obj=this;if(this.options.rows==1&&!this.hasHTMLLineBreaks(text)){this.options.textarea=false;var textField=document.createElement("input");textField.obj=this;textField.type="text";textField.name="value";textField.value=text;textField.style.backgroundColor=this.options.highlightcolor;textField.className='editor_field';var size=this.options.size||this.options.cols||0;if(size!=0)textField.size=size;if(this.options.submitOnBlur) +textField.onblur=this.onSubmit.bind(this);this.editField=textField;}else{this.options.textarea=true;var textArea=document.createElement("textarea");textArea.obj=this;textArea.name="value";textArea.value=this.convertHTMLLineBreaks(text);textArea.rows=this.options.rows;textArea.cols=this.options.cols||40;textArea.className='editor_field';if(this.options.submitOnBlur) +textArea.onblur=this.onSubmit.bind(this);this.editField=textArea;} +if(this.options.loadTextURL){this.loadExternalText();} +this.form.appendChild(this.editField);},getText:function(){return this.element.innerHTML;},loadExternalText:function(){Element.addClassName(this.form,this.options.loadingClassName);this.editField.disabled=true;new Ajax.Request(this.options.loadTextURL,Object.extend({asynchronous:true,onComplete:this.onLoadedExternalText.bind(this)},this.options.ajaxOptions));},onLoadedExternalText:function(transport){Element.removeClassName(this.form,this.options.loadingClassName);this.editField.disabled=false;this.editField.value=transport.responseText.stripTags();Field.scrollFreeActivate(this.editField);},onclickCancel:function(){this.onComplete();this.leaveEditMode();return false;},onFailure:function(transport){this.options.onFailure(transport);if(this.oldInnerHTML){this.element.innerHTML=this.oldInnerHTML;this.oldInnerHTML=null;} +return false;},onSubmit:function(){var form=this.form;var value=this.editField.value;this.onLoading();if(this.options.evalScripts){new Ajax.Request(this.url,Object.extend({parameters:this.options.callback(form,value),onComplete:this.onComplete.bind(this),onFailure:this.onFailure.bind(this),asynchronous:true,evalScripts:true},this.options.ajaxOptions));}else{new Ajax.Updater({success:this.element,failure:null},this.url,Object.extend({parameters:this.options.callback(form,value),onComplete:this.onComplete.bind(this),onFailure:this.onFailure.bind(this)},this.options.ajaxOptions));} +if(arguments.length>1){Event.stop(arguments[0]);} +return false;},onLoading:function(){this.saving=true;this.removeForm();this.leaveHover();this.showSaving();},showSaving:function(){this.oldInnerHTML=this.element.innerHTML;this.element.innerHTML=this.options.savingText;Element.addClassName(this.element,this.options.savingClassName);this.element.style.backgroundColor=this.originalBackground;Element.show(this.element);},removeForm:function(){if(this.form){if(this.form.parentNode)Element.remove(this.form);this.form=null;}},enterHover:function(){if(this.saving)return;this.element.style.backgroundColor=this.options.highlightcolor;if(this.effect){this.effect.cancel();} +Element.addClassName(this.element,this.options.hoverClassName)},leaveHover:function(){if(this.options.backgroundColor){this.element.style.backgroundColor=this.oldBackground;} +Element.removeClassName(this.element,this.options.hoverClassName) +if(this.saving)return;this.effect=new Effect.Highlight(this.element,{startcolor:this.options.highlightcolor,endcolor:this.options.highlightendcolor,restorecolor:this.originalBackground});},leaveEditMode:function(){Element.removeClassName(this.element,this.options.savingClassName);this.removeForm();this.leaveHover();this.element.style.backgroundColor=this.originalBackground;Element.show(this.element);if(this.options.externalControl){Element.show(this.options.externalControl);} +this.editing=false;this.saving=false;this.oldInnerHTML=null;this.onLeaveEditMode();},onComplete:function(transport){this.leaveEditMode();this.options.onComplete.bind(this)(transport,this.element);},onEnterEditMode:function(){},onLeaveEditMode:function(){},dispose:function(){if(this.oldInnerHTML){this.element.innerHTML=this.oldInnerHTML;} +this.leaveEditMode();Event.stopObserving(this.element,'click',this.onclickListener);Event.stopObserving(this.element,'mouseover',this.mouseoverListener);Event.stopObserving(this.element,'mouseout',this.mouseoutListener);if(this.options.externalControl){Event.stopObserving(this.options.externalControl,'click',this.onclickListener);Event.stopObserving(this.options.externalControl,'mouseover',this.mouseoverListener);Event.stopObserving(this.options.externalControl,'mouseout',this.mouseoutListener);}}};Ajax.InPlaceCollectionEditor=Class.create();Object.extend(Ajax.InPlaceCollectionEditor.prototype,Ajax.InPlaceEditor.prototype);Object.extend(Ajax.InPlaceCollectionEditor.prototype,{createEditField:function(){if(!this.cached_selectTag){var selectTag=document.createElement("select");var collection=this.options.collection||[];var optionTag;collection.each(function(e,i){optionTag=document.createElement("option");optionTag.value=(e instanceof Array)?e[0]:e;if((typeof this.options.value=='undefined')&&((e instanceof Array)?this.element.innerHTML==e[1]:e==optionTag.value))optionTag.selected=true;if(this.options.value==optionTag.value)optionTag.selected=true;optionTag.appendChild(document.createTextNode((e instanceof Array)?e[1]:e));selectTag.appendChild(optionTag);}.bind(this));this.cached_selectTag=selectTag;} +this.editField=this.cached_selectTag;if(this.options.loadTextURL)this.loadExternalText();this.form.appendChild(this.editField);this.options.callback=function(form,value){return"value="+encodeURIComponent(value);}}});Form.Element.DelayedObserver=Class.create();Form.Element.DelayedObserver.prototype={initialize:function(element,delay,callback){this.delay=delay||0.5;this.element=$(element);this.callback=callback;this.timer=null;this.lastValue=$F(this.element);Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));},delayedListener:function(event){if(this.lastValue==$F(this.element))return;if(this.timer)clearTimeout(this.timer);this.timer=setTimeout(this.onTimerEvent.bind(this),this.delay*1000);this.lastValue=$F(this.element);},onTimerEvent:function(){this.timer=null;this.callback(this.element,$F(this.element));}}; \ No newline at end of file diff --git a/Solar/App/Public/scripts-min/scriptaculous/dragdrop.js b/Solar/App/Public/scripts-min/scriptaculous/dragdrop.js new file mode 100644 index 00000000..485fe689 --- /dev/null +++ b/Solar/App/Public/scripts-min/scriptaculous/dragdrop.js @@ -0,0 +1,79 @@ +// From script.aculo.us 1.6.4, compressed by jsmin (http://www.crockford.com/javascript/jsmin.html). + +if(typeof Effect=='undefined') +throw("dragdrop.js requires including script.aculo.us' effects.js library");var Droppables={drops:[],remove:function(element){this.drops=this.drops.reject(function(d){return d.element==$(element)});},add:function(element){element=$(element);var options=Object.extend({greedy:true,hoverclass:null,tree:false},arguments[1]||{});if(options.containment){options._containers=[];var containment=options.containment;if((typeof containment=='object')&&(containment.constructor==Array)){containment.each(function(c){options._containers.push($(c))});}else{options._containers.push($(containment));}} +if(options.accept)options.accept=[options.accept].flatten();Element.makePositioned(element);options.element=element;this.drops.push(options);},findDeepestChild:function(drops){deepest=drops[0];for(i=1;i0){drop=Droppables.findDeepestChild(affected);Position.within(drop.element,point[0],point[1]);if(drop.onHover) +drop.onHover(element,drop.element,Position.overlap(drop.overlap,drop.element));Droppables.activate(drop);}},fire:function(event,element){if(!this.last_active)return;Position.prepare();if(this.isAffected([Event.pointerX(event),Event.pointerY(event)],element,this.last_active)) +if(this.last_active.onDrop) +this.last_active.onDrop(element,this.last_active.element,event);},reset:function(){if(this.last_active) +this.deactivate(this.last_active);}} +var Draggables={drags:[],observers:[],register:function(draggable){if(this.drags.length==0){this.eventMouseUp=this.endDrag.bindAsEventListener(this);this.eventMouseMove=this.updateDrag.bindAsEventListener(this);this.eventKeypress=this.keyPress.bindAsEventListener(this);Event.observe(document,"mouseup",this.eventMouseUp);Event.observe(document,"mousemove",this.eventMouseMove);Event.observe(document,"keypress",this.eventKeypress);} +this.drags.push(draggable);},unregister:function(draggable){this.drags=this.drags.reject(function(d){return d==draggable});if(this.drags.length==0){Event.stopObserving(document,"mouseup",this.eventMouseUp);Event.stopObserving(document,"mousemove",this.eventMouseMove);Event.stopObserving(document,"keypress",this.eventKeypress);}},activate:function(draggable){if(draggable.options.delay){this._timeout=setTimeout(function(){Draggables._timeout=null;window.focus();Draggables.activeDraggable=draggable;}.bind(this),draggable.options.delay);}else{window.focus();this.activeDraggable=draggable;}},deactivate:function(){this.activeDraggable=null;},updateDrag:function(event){if(!this.activeDraggable)return;var pointer=[Event.pointerX(event),Event.pointerY(event)];if(this._lastPointer&&(this._lastPointer.inspect()==pointer.inspect()))return;this._lastPointer=pointer;this.activeDraggable.updateDrag(event,pointer);},endDrag:function(event){if(this._timeout){clearTimeout(this._timeout);this._timeout=null;} +if(!this.activeDraggable)return;this._lastPointer=null;this.activeDraggable.endDrag(event);this.activeDraggable=null;},keyPress:function(event){if(this.activeDraggable) +this.activeDraggable.keyPress(event);},addObserver:function(observer){this.observers.push(observer);this._cacheObserverCallbacks();},removeObserver:function(element){this.observers=this.observers.reject(function(o){return o.element==element});this._cacheObserverCallbacks();},notify:function(eventName,draggable,event){if(this[eventName+'Count']>0) +this.observers.each(function(o){if(o[eventName])o[eventName](eventName,draggable,event);});if(draggable.options[eventName])draggable.options[eventName](draggable,event);},_cacheObserverCallbacks:function(){['onStart','onEnd','onDrag'].each(function(eventName){Draggables[eventName+'Count']=Draggables.observers.select(function(o){return o[eventName];}).length;});}} +var Draggable=Class.create();Draggable._dragging={};Draggable.prototype={initialize:function(element){var defaults={handle:false,reverteffect:function(element,top_offset,left_offset){var dur=Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;new Effect.Move(element,{x:-left_offset,y:-top_offset,duration:dur,queue:{scope:'_draggable',position:'end'}});},endeffect:function(element){var toOpacity=typeof element._opacity=='number'?element._opacity:1.0;new Effect.Opacity(element,{duration:0.2,from:0.7,to:toOpacity,queue:{scope:'_draggable',position:'end'},afterFinish:function(){Draggable._dragging[element]=false}});},zindex:1000,revert:false,scroll:false,scrollSensitivity:20,scrollSpeed:15,snap:false,delay:0};if(arguments[1]&&typeof arguments[1].endeffect=='undefined') +Object.extend(defaults,{starteffect:function(element){element._opacity=Element.getOpacity(element);Draggable._dragging[element]=true;new Effect.Opacity(element,{duration:0.2,from:element._opacity,to:0.7});}});var options=Object.extend(defaults,arguments[1]||{});this.element=$(element);if(options.handle&&(typeof options.handle=='string')){var h=Element.childrenWithClassName(this.element,options.handle,true);if(h.length>0)this.handle=h[0];} +if(!this.handle)this.handle=$(options.handle);if(!this.handle)this.handle=this.element;if(options.scroll&&!options.scroll.scrollTo&&!options.scroll.outerHTML){options.scroll=$(options.scroll);this._isScrollChild=Element.childOf(this.element,options.scroll);} +Element.makePositioned(this.element);this.delta=this.currentDelta();this.options=options;this.dragging=false;this.eventMouseDown=this.initDrag.bindAsEventListener(this);Event.observe(this.handle,"mousedown",this.eventMouseDown);Draggables.register(this);},destroy:function(){Event.stopObserving(this.handle,"mousedown",this.eventMouseDown);Draggables.unregister(this);},currentDelta:function(){return([parseInt(Element.getStyle(this.element,'left')||'0'),parseInt(Element.getStyle(this.element,'top')||'0')]);},initDrag:function(event){if(typeof Draggable._dragging[this.element]!='undefined'&&Draggable._dragging[this.element])return;if(Event.isLeftClick(event)){var src=Event.element(event);if(src.tagName&&(src.tagName=='INPUT'||src.tagName=='SELECT'||src.tagName=='OPTION'||src.tagName=='BUTTON'||src.tagName=='TEXTAREA'))return;var pointer=[Event.pointerX(event),Event.pointerY(event)];var pos=Position.cumulativeOffset(this.element);this.offset=[0,1].map(function(i){return(pointer[i]-pos[i])});Draggables.activate(this);Event.stop(event);}},startDrag:function(event){this.dragging=true;if(this.options.zindex){this.originalZ=parseInt(Element.getStyle(this.element,'z-index')||0);this.element.style.zIndex=this.options.zindex;} +if(this.options.ghosting){this._clone=this.element.cloneNode(true);Position.absolutize(this.element);this.element.parentNode.insertBefore(this._clone,this.element);} +if(this.options.scroll){if(this.options.scroll==window){var where=this._getWindowScroll(this.options.scroll);this.originalScrollLeft=where.left;this.originalScrollTop=where.top;}else{this.originalScrollLeft=this.options.scroll.scrollLeft;this.originalScrollTop=this.options.scroll.scrollTop;}} +Draggables.notify('onStart',this,event);if(this.options.starteffect)this.options.starteffect(this.element);},updateDrag:function(event,pointer){if(!this.dragging)this.startDrag(event);Position.prepare();Droppables.show(pointer,this.element);Draggables.notify('onDrag',this,event);this.draw(pointer);if(this.options.change)this.options.change(this);if(this.options.scroll){this.stopScrolling();var p;if(this.options.scroll==window){with(this._getWindowScroll(this.options.scroll)){p=[left,top,left+width,top+height];}}else{p=Position.page(this.options.scroll);p[0]+=this.options.scroll.scrollLeft;p[1]+=this.options.scroll.scrollTop;p[0]+=(window.pageXOffset||document.documentElement.scrollLeft||document.body.scrollLeft||0);p[1]+=(window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0);p.push(p[0]+this.options.scroll.offsetWidth);p.push(p[1]+this.options.scroll.offsetHeight);} +var speed=[0,0];if(pointer[0]<(p[0]+this.options.scrollSensitivity))speed[0]=pointer[0]-(p[0]+this.options.scrollSensitivity);if(pointer[1]<(p[1]+this.options.scrollSensitivity))speed[1]=pointer[1]-(p[1]+this.options.scrollSensitivity);if(pointer[0]>(p[2]-this.options.scrollSensitivity))speed[0]=pointer[0]-(p[2]-this.options.scrollSensitivity);if(pointer[1]>(p[3]-this.options.scrollSensitivity))speed[1]=pointer[1]-(p[3]-this.options.scrollSensitivity);this.startScrolling(speed);} +if(navigator.appVersion.indexOf('AppleWebKit')>0)window.scrollBy(0,0);Event.stop(event);},finishDrag:function(event,success){this.dragging=false;if(this.options.ghosting){Position.relativize(this.element);Element.remove(this._clone);this._clone=null;} +if(success)Droppables.fire(event,this.element);Draggables.notify('onEnd',this,event);var revert=this.options.revert;if(revert&&typeof revert=='function')revert=revert(this.element);var d=this.currentDelta();if(revert&&this.options.reverteffect){this.options.reverteffect(this.element,d[1]-this.delta[1],d[0]-this.delta[0]);}else{this.delta=d;} +if(this.options.zindex) +this.element.style.zIndex=this.originalZ;if(this.options.endeffect) +this.options.endeffect(this.element);Draggables.deactivate(this);Droppables.reset();},keyPress:function(event){if(event.keyCode!=Event.KEY_ESC)return;this.finishDrag(event,false);Event.stop(event);},endDrag:function(event){if(!this.dragging)return;this.stopScrolling();this.finishDrag(event,true);Event.stop(event);},draw:function(point){var pos=Position.cumulativeOffset(this.element);if(this.options.ghosting){var r=Position.realOffset(this.element);window.status=r.inspect();pos[0]+=r[0]-Position.deltaX;pos[1]+=r[1]-Position.deltaY;} +var d=this.currentDelta();pos[0]-=d[0];pos[1]-=d[1];if(this.options.scroll&&(this.options.scroll!=window&&this._isScrollChild)){pos[0]-=this.options.scroll.scrollLeft-this.originalScrollLeft;pos[1]-=this.options.scroll.scrollTop-this.originalScrollTop;} +var p=[0,1].map(function(i){return(point[i]-pos[i]-this.offset[i])}.bind(this));if(this.options.snap){if(typeof this.options.snap=='function'){p=this.options.snap(p[0],p[1],this);}else{if(this.options.snap instanceof Array){p=p.map(function(v,i){return Math.round(v/this.options.snap[i])*this.options.snap[i]}.bind(this))}else{p=p.map(function(v){return Math.round(v/this.options.snap)*this.options.snap}.bind(this))}}} +var style=this.element.style;if((!this.options.constraint)||(this.options.constraint=='horizontal')) +style.left=p[0]+"px";if((!this.options.constraint)||(this.options.constraint=='vertical')) +style.top=p[1]+"px";if(style.visibility=="hidden")style.visibility="";},stopScrolling:function(){if(this.scrollInterval){clearInterval(this.scrollInterval);this.scrollInterval=null;Draggables._lastScrollPointer=null;}},startScrolling:function(speed){if(!(speed[0]||speed[1]))return;this.scrollSpeed=[speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];this.lastScrolled=new Date();this.scrollInterval=setInterval(this.scroll.bind(this),10);},scroll:function(){var current=new Date();var delta=current-this.lastScrolled;this.lastScrolled=current;if(this.options.scroll==window){with(this._getWindowScroll(this.options.scroll)){if(this.scrollSpeed[0]||this.scrollSpeed[1]){var d=delta/1000;this.options.scroll.scrollTo(left+d*this.scrollSpeed[0],top+d*this.scrollSpeed[1]);}}}else{this.options.scroll.scrollLeft+=this.scrollSpeed[0]*delta/1000;this.options.scroll.scrollTop+=this.scrollSpeed[1]*delta/1000;} +Position.prepare();Droppables.show(Draggables._lastPointer,this.element);Draggables.notify('onDrag',this);if(this._isScrollChild){Draggables._lastScrollPointer=Draggables._lastScrollPointer||$A(Draggables._lastPointer);Draggables._lastScrollPointer[0]+=this.scrollSpeed[0]*delta/1000;Draggables._lastScrollPointer[1]+=this.scrollSpeed[1]*delta/1000;if(Draggables._lastScrollPointer[0]<0) +Draggables._lastScrollPointer[0]=0;if(Draggables._lastScrollPointer[1]<0) +Draggables._lastScrollPointer[1]=0;this.draw(Draggables._lastScrollPointer);} +if(this.options.change)this.options.change(this);},_getWindowScroll:function(w){var T,L,W,H;with(w.document){if(w.document.documentElement&&documentElement.scrollTop){T=documentElement.scrollTop;L=documentElement.scrollLeft;}else if(w.document.body){T=body.scrollTop;L=body.scrollLeft;} +if(w.innerWidth){W=w.innerWidth;H=w.innerHeight;}else if(w.document.documentElement&&documentElement.clientWidth){W=documentElement.clientWidth;H=documentElement.clientHeight;}else{W=body.offsetWidth;H=body.offsetHeight}} +return{top:T,left:L,width:W,height:H};}} +var SortableObserver=Class.create();SortableObserver.prototype={initialize:function(element,observer){this.element=$(element);this.observer=observer;this.lastValue=Sortable.serialize(this.element);},onStart:function(){this.lastValue=Sortable.serialize(this.element);},onEnd:function(){Sortable.unmark();if(this.lastValue!=Sortable.serialize(this.element)) +this.observer(this.element)}} +var Sortable={SERIALIZE_RULE:/^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,sortables:{},_findRootElement:function(element){while(element.tagName!="BODY"){if(element.id&&Sortable.sortables[element.id])return element;element=element.parentNode;}},options:function(element){element=Sortable._findRootElement($(element));if(!element)return;return Sortable.sortables[element.id];},destroy:function(element){var s=Sortable.options(element);if(s){Draggables.removeObserver(s.element);s.droppables.each(function(d){Droppables.remove(d)});s.draggables.invoke('destroy');delete Sortable.sortables[s.element.id];}},create:function(element){element=$(element);var options=Object.extend({element:element,tag:'li',dropOnEmpty:false,tree:false,treeTag:'ul',overlap:'vertical',constraint:'vertical',containment:element,handle:false,only:false,delay:0,hoverclass:null,ghosting:false,scroll:false,scrollSensitivity:20,scrollSpeed:15,format:this.SERIALIZE_RULE,onChange:Prototype.emptyFunction,onUpdate:Prototype.emptyFunction},arguments[1]||{});this.destroy(element);var options_for_draggable={revert:true,scroll:options.scroll,scrollSpeed:options.scrollSpeed,scrollSensitivity:options.scrollSensitivity,delay:options.delay,ghosting:options.ghosting,constraint:options.constraint,handle:options.handle};if(options.starteffect) +options_for_draggable.starteffect=options.starteffect;if(options.reverteffect) +options_for_draggable.reverteffect=options.reverteffect;else +if(options.ghosting)options_for_draggable.reverteffect=function(element){element.style.top=0;element.style.left=0;};if(options.endeffect) +options_for_draggable.endeffect=options.endeffect;if(options.zindex) +options_for_draggable.zindex=options.zindex;var options_for_droppable={overlap:options.overlap,containment:options.containment,tree:options.tree,hoverclass:options.hoverclass,onHover:Sortable.onHover} +var options_for_tree={onHover:Sortable.onEmptyHover,overlap:options.overlap,containment:options.containment,hoverclass:options.hoverclass} +Element.cleanWhitespace(element);options.draggables=[];options.droppables=[];if(options.dropOnEmpty||options.tree){Droppables.add(element,options_for_tree);options.droppables.push(element);} +(this.findElements(element,options)||[]).each(function(e){var handle=options.handle?Element.childrenWithClassName(e,options.handle)[0]:e;options.draggables.push(new Draggable(e,Object.extend(options_for_draggable,{handle:handle})));Droppables.add(e,options_for_droppable);if(options.tree)e.treeNode=element;options.droppables.push(e);});if(options.tree){(Sortable.findTreeElements(element,options)||[]).each(function(e){Droppables.add(e,options_for_tree);e.treeNode=element;options.droppables.push(e);});} +this.sortables[element.id]=options;Draggables.addObserver(new SortableObserver(element,options.onUpdate));},findElements:function(element,options){return Element.findChildren(element,options.only,options.tree?true:false,options.tag);},findTreeElements:function(element,options){return Element.findChildren(element,options.only,options.tree?true:false,options.treeTag);},onHover:function(element,dropon,overlap){if(Element.isParent(dropon,element))return;if(overlap>.33&&overlap<.66&&Sortable.options(dropon).tree){return;}else if(overlap>0.5){Sortable.mark(dropon,'before');if(dropon.previousSibling!=element){var oldParentNode=element.parentNode;element.style.visibility="hidden";dropon.parentNode.insertBefore(element,dropon);if(dropon.parentNode!=oldParentNode) +Sortable.options(oldParentNode).onChange(element);Sortable.options(dropon.parentNode).onChange(element);}}else{Sortable.mark(dropon,'after');var nextElement=dropon.nextSibling||null;if(nextElement!=element){var oldParentNode=element.parentNode;element.style.visibility="hidden";dropon.parentNode.insertBefore(element,nextElement);if(dropon.parentNode!=oldParentNode) +Sortable.options(oldParentNode).onChange(element);Sortable.options(dropon.parentNode).onChange(element);}}},onEmptyHover:function(element,dropon,overlap){var oldParentNode=element.parentNode;var droponOptions=Sortable.options(dropon);if(!Element.isParent(dropon,element)){var index;var children=Sortable.findElements(dropon,{tag:droponOptions.tag,only:droponOptions.only});var child=null;if(children){var offset=Element.offsetSize(dropon,droponOptions.overlap)*(1.0-overlap);for(index=0;index=0){offset-=Element.offsetSize(children[index],droponOptions.overlap);}else if(offset-(Element.offsetSize(children[index],droponOptions.overlap)/2)>=0){child=index+1=this.startOn){if(timePos>=this.finishOn){this.render(1.0);this.cancel();this.event('beforeFinish');if(this.finish)this.finish();this.event('afterFinish');return;} +var pos=(timePos-this.startOn)/(this.finishOn-this.startOn);var frame=Math.round(pos*this.options.fps*this.options.duration);if(frame>this.currentFrame){this.render(pos);this.currentFrame=frame;}}},render:function(pos){if(this.state=='idle'){this.state='running';this.event('beforeSetup');if(this.setup)this.setup();this.event('afterSetup');} +if(this.state=='running'){if(this.options.transition)pos=this.options.transition(pos);pos*=(this.options.to-this.options.from);pos+=this.options.from;this.position=pos;this.event('beforeUpdate');if(this.update)this.update(pos);this.event('afterUpdate');}},cancel:function(){if(!this.options.sync) +Effect.Queues.get(typeof this.options.queue=='string'?'global':this.options.queue.scope).remove(this);this.state='finished';},event:function(eventName){if(this.options[eventName+'Internal'])this.options[eventName+'Internal'](this);if(this.options[eventName])this.options[eventName](this);},inspect:function(){return'#';}} +Effect.Parallel=Class.create();Object.extend(Object.extend(Effect.Parallel.prototype,Effect.Base.prototype),{initialize:function(effects){this.effects=effects||[];this.start(arguments[1]);},update:function(position){this.effects.invoke('render',position);},finish:function(position){this.effects.each(function(effect){effect.render(1.0);effect.cancel();effect.event('beforeFinish');if(effect.finish)effect.finish(position);effect.event('afterFinish');});}});Effect.Opacity=Class.create();Object.extend(Object.extend(Effect.Opacity.prototype,Effect.Base.prototype),{initialize:function(element){this.element=$(element);if(!this.element)throw(Effect._elementDoesNotExistError);if(/MSIE/.test(navigator.userAgent)&&!window.opera&&(!this.element.currentStyle.hasLayout)) +this.element.setStyle({zoom:1});var options=Object.extend({from:this.element.getOpacity()||0.0,to:1.0},arguments[1]||{});this.start(options);},update:function(position){this.element.setOpacity(position);}});Effect.Move=Class.create();Object.extend(Object.extend(Effect.Move.prototype,Effect.Base.prototype),{initialize:function(element){this.element=$(element);if(!this.element)throw(Effect._elementDoesNotExistError);var options=Object.extend({x:0,y:0,mode:'relative'},arguments[1]||{});this.start(options);},setup:function(){this.element.makePositioned();this.originalLeft=parseFloat(this.element.getStyle('left')||'0');this.originalTop=parseFloat(this.element.getStyle('top')||'0');if(this.options.mode=='absolute'){this.options.x=this.options.x-this.originalLeft;this.options.y=this.options.y-this.originalTop;}},update:function(position){this.element.setStyle({left:Math.round(this.options.x*position+this.originalLeft)+'px',top:Math.round(this.options.y*position+this.originalTop)+'px'});}});Effect.MoveBy=function(element,toTop,toLeft){return new Effect.Move(element,Object.extend({x:toLeft,y:toTop},arguments[3]||{}));};Effect.Scale=Class.create();Object.extend(Object.extend(Effect.Scale.prototype,Effect.Base.prototype),{initialize:function(element,percent){this.element=$(element);if(!this.element)throw(Effect._elementDoesNotExistError);var options=Object.extend({scaleX:true,scaleY:true,scaleContent:true,scaleFromCenter:false,scaleMode:'box',scaleFrom:100.0,scaleTo:percent},arguments[2]||{});this.start(options);},setup:function(){this.restoreAfterFinish=this.options.restoreAfterFinish||false;this.elementPositioning=this.element.getStyle('position');this.originalStyle={};['top','left','width','height','fontSize'].each(function(k){this.originalStyle[k]=this.element.style[k];}.bind(this));this.originalTop=this.element.offsetTop;this.originalLeft=this.element.offsetLeft;var fontSize=this.element.getStyle('font-size')||'100%';['em','px','%','pt'].each(function(fontSizeType){if(fontSize.indexOf(fontSizeType)>0){this.fontSize=parseFloat(fontSize);this.fontSizeType=fontSizeType;}}.bind(this));this.factor=(this.options.scaleTo-this.options.scaleFrom)/100;this.dims=null;if(this.options.scaleMode=='box') +this.dims=[this.element.offsetHeight,this.element.offsetWidth];if(/^content/.test(this.options.scaleMode)) +this.dims=[this.element.scrollHeight,this.element.scrollWidth];if(!this.dims) +this.dims=[this.options.scaleMode.originalHeight,this.options.scaleMode.originalWidth];},update:function(position){var currentScale=(this.options.scaleFrom/100.0)+(this.factor*position);if(this.options.scaleContent&&this.fontSize) +this.element.setStyle({fontSize:this.fontSize*currentScale+this.fontSizeType});this.setDimensions(this.dims[0]*currentScale,this.dims[1]*currentScale);},finish:function(position){if(this.restoreAfterFinish)this.element.setStyle(this.originalStyle);},setDimensions:function(height,width){var d={};if(this.options.scaleX)d.width=Math.round(width)+'px';if(this.options.scaleY)d.height=Math.round(height)+'px';if(this.options.scaleFromCenter){var topd=(height-this.dims[0])/2;var leftd=(width-this.dims[1])/2;if(this.elementPositioning=='absolute'){if(this.options.scaleY)d.top=this.originalTop-topd+'px';if(this.options.scaleX)d.left=this.originalLeft-leftd+'px';}else{if(this.options.scaleY)d.top=-topd+'px';if(this.options.scaleX)d.left=-leftd+'px';}} +this.element.setStyle(d);}});Effect.Highlight=Class.create();Object.extend(Object.extend(Effect.Highlight.prototype,Effect.Base.prototype),{initialize:function(element){this.element=$(element);if(!this.element)throw(Effect._elementDoesNotExistError);var options=Object.extend({startcolor:'#ffff99'},arguments[1]||{});this.start(options);},setup:function(){if(this.element.getStyle('display')=='none'){this.cancel();return;} +this.oldStyle={backgroundImage:this.element.getStyle('background-image')};this.element.setStyle({backgroundImage:'none'});if(!this.options.endcolor) +this.options.endcolor=this.element.getStyle('background-color').parseColor('#ffffff');if(!this.options.restorecolor) +this.options.restorecolor=this.element.getStyle('background-color');this._base=$R(0,2).map(function(i){return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16)}.bind(this));this._delta=$R(0,2).map(function(i){return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i]}.bind(this));},update:function(position){this.element.setStyle({backgroundColor:$R(0,2).inject('#',function(m,v,i){return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart());}.bind(this))});},finish:function(){this.element.setStyle(Object.extend(this.oldStyle,{backgroundColor:this.options.restorecolor}));}});Effect.ScrollTo=Class.create();Object.extend(Object.extend(Effect.ScrollTo.prototype,Effect.Base.prototype),{initialize:function(element){this.element=$(element);this.start(arguments[1]||{});},setup:function(){Position.prepare();var offsets=Position.cumulativeOffset(this.element);if(this.options.offset)offsets[1]+=this.options.offset;var max=window.innerHeight?window.height-window.innerHeight:document.body.scrollHeight- +(document.documentElement.clientHeight?document.documentElement.clientHeight:document.body.clientHeight);this.scrollStart=Position.deltaY;this.delta=(offsets[1]>max?max:offsets[1])-this.scrollStart;},update:function(position){Position.prepare();window.scrollTo(Position.deltaX,this.scrollStart+(position*this.delta));}});Effect.Fade=function(element){element=$(element);var oldOpacity=element.getInlineOpacity();var options=Object.extend({from:element.getOpacity()||1.0,to:0.0,afterFinishInternal:function(effect){if(effect.options.to!=0)return;effect.element.hide();effect.element.setStyle({opacity:oldOpacity});}},arguments[1]||{});return new Effect.Opacity(element,options);} +Effect.Appear=function(element){element=$(element);var options=Object.extend({from:(element.getStyle('display')=='none'?0.0:element.getOpacity()||0.0),to:1.0,afterFinishInternal:function(effect){effect.element.forceRerendering();},beforeSetup:function(effect){effect.element.setOpacity(effect.options.from);effect.element.show();}},arguments[1]||{});return new Effect.Opacity(element,options);} +Effect.Puff=function(element){element=$(element);var oldStyle={opacity:element.getInlineOpacity(),position:element.getStyle('position'),top:element.style.top,left:element.style.left,width:element.style.width,height:element.style.height};return new Effect.Parallel([new Effect.Scale(element,200,{sync:true,scaleFromCenter:true,scaleContent:true,restoreAfterFinish:true}),new Effect.Opacity(element,{sync:true,to:0.0})],Object.extend({duration:1.0,beforeSetupInternal:function(effect){Position.absolutize(effect.effects[0].element)},afterFinishInternal:function(effect){effect.effects[0].element.hide();effect.effects[0].element.setStyle(oldStyle);}},arguments[1]||{}));} +Effect.BlindUp=function(element){element=$(element);element.makeClipping();return new Effect.Scale(element,0,Object.extend({scaleContent:false,scaleX:false,restoreAfterFinish:true,afterFinishInternal:function(effect){effect.element.hide();effect.element.undoClipping();}},arguments[1]||{}));} +Effect.BlindDown=function(element){element=$(element);var elementDimensions=element.getDimensions();return new Effect.Scale(element,100,Object.extend({scaleContent:false,scaleX:false,scaleFrom:0,scaleMode:{originalHeight:elementDimensions.height,originalWidth:elementDimensions.width},restoreAfterFinish:true,afterSetup:function(effect){effect.element.makeClipping();effect.element.setStyle({height:'0px'});effect.element.show();},afterFinishInternal:function(effect){effect.element.undoClipping();}},arguments[1]||{}));} +Effect.SwitchOff=function(element){element=$(element);var oldOpacity=element.getInlineOpacity();return new Effect.Appear(element,Object.extend({duration:0.4,from:0,transition:Effect.Transitions.flicker,afterFinishInternal:function(effect){new Effect.Scale(effect.element,1,{duration:0.3,scaleFromCenter:true,scaleX:false,scaleContent:false,restoreAfterFinish:true,beforeSetup:function(effect){effect.element.makePositioned();effect.element.makeClipping();},afterFinishInternal:function(effect){effect.element.hide();effect.element.undoClipping();effect.element.undoPositioned();effect.element.setStyle({opacity:oldOpacity});}})}},arguments[1]||{}));} +Effect.DropOut=function(element){element=$(element);var oldStyle={top:element.getStyle('top'),left:element.getStyle('left'),opacity:element.getInlineOpacity()};return new Effect.Parallel([new Effect.Move(element,{x:0,y:100,sync:true}),new Effect.Opacity(element,{sync:true,to:0.0})],Object.extend({duration:0.5,beforeSetup:function(effect){effect.effects[0].element.makePositioned();},afterFinishInternal:function(effect){effect.effects[0].element.hide();effect.effects[0].element.undoPositioned();effect.effects[0].element.setStyle(oldStyle);}},arguments[1]||{}));} +Effect.Shake=function(element){element=$(element);var oldStyle={top:element.getStyle('top'),left:element.getStyle('left')};return new Effect.Move(element,{x:20,y:0,duration:0.05,afterFinishInternal:function(effect){new Effect.Move(effect.element,{x:-40,y:0,duration:0.1,afterFinishInternal:function(effect){new Effect.Move(effect.element,{x:40,y:0,duration:0.1,afterFinishInternal:function(effect){new Effect.Move(effect.element,{x:-40,y:0,duration:0.1,afterFinishInternal:function(effect){new Effect.Move(effect.element,{x:40,y:0,duration:0.1,afterFinishInternal:function(effect){new Effect.Move(effect.element,{x:-20,y:0,duration:0.05,afterFinishInternal:function(effect){effect.element.undoPositioned();effect.element.setStyle(oldStyle);}})}})}})}})}})}});} +Effect.SlideDown=function(element){element=$(element);element.cleanWhitespace();var oldInnerBottom=$(element.firstChild).getStyle('bottom');var elementDimensions=element.getDimensions();return new Effect.Scale(element,100,Object.extend({scaleContent:false,scaleX:false,scaleFrom:window.opera?0:1,scaleMode:{originalHeight:elementDimensions.height,originalWidth:elementDimensions.width},restoreAfterFinish:true,afterSetup:function(effect){effect.element.makePositioned();effect.element.firstChild.makePositioned();if(window.opera)effect.element.setStyle({top:''});effect.element.makeClipping();effect.element.setStyle({height:'0px'});effect.element.show();},afterUpdateInternal:function(effect){effect.element.firstChild.setStyle({bottom:(effect.dims[0]-effect.element.clientHeight)+'px'});},afterFinishInternal:function(effect){effect.element.undoClipping();if(/MSIE/.test(navigator.userAgent)&&!window.opera){effect.element.undoPositioned();effect.element.firstChild.undoPositioned();}else{effect.element.firstChild.undoPositioned();effect.element.undoPositioned();} +effect.element.firstChild.setStyle({bottom:oldInnerBottom});}},arguments[1]||{}));} +Effect.SlideUp=function(element){element=$(element);element.cleanWhitespace();var oldInnerBottom=$(element.firstChild).getStyle('bottom');return new Effect.Scale(element,window.opera?0:1,Object.extend({scaleContent:false,scaleX:false,scaleMode:'box',scaleFrom:100,restoreAfterFinish:true,beforeStartInternal:function(effect){effect.element.makePositioned();effect.element.firstChild.makePositioned();if(window.opera)effect.element.setStyle({top:''});effect.element.makeClipping();effect.element.show();},afterUpdateInternal:function(effect){effect.element.firstChild.setStyle({bottom:(effect.dims[0]-effect.element.clientHeight)+'px'});},afterFinishInternal:function(effect){effect.element.hide();effect.element.undoClipping();effect.element.firstChild.undoPositioned();effect.element.undoPositioned();effect.element.setStyle({bottom:oldInnerBottom});}},arguments[1]||{}));} +Effect.Squish=function(element){return new Effect.Scale(element,window.opera?1:0,{restoreAfterFinish:true,beforeSetup:function(effect){effect.element.makeClipping(effect.element);},afterFinishInternal:function(effect){effect.element.hide(effect.element);effect.element.undoClipping(effect.element);}});} +Effect.Grow=function(element){element=$(element);var options=Object.extend({direction:'center',moveTransition:Effect.Transitions.sinoidal,scaleTransition:Effect.Transitions.sinoidal,opacityTransition:Effect.Transitions.full},arguments[1]||{});var oldStyle={top:element.style.top,left:element.style.left,height:element.style.height,width:element.style.width,opacity:element.getInlineOpacity()};var dims=element.getDimensions();var initialMoveX,initialMoveY;var moveX,moveY;switch(options.direction){case'top-left':initialMoveX=initialMoveY=moveX=moveY=0;break;case'top-right':initialMoveX=dims.width;initialMoveY=moveY=0;moveX=-dims.width;break;case'bottom-left':initialMoveX=moveX=0;initialMoveY=dims.height;moveY=-dims.height;break;case'bottom-right':initialMoveX=dims.width;initialMoveY=dims.height;moveX=-dims.width;moveY=-dims.height;break;case'center':initialMoveX=dims.width/2;initialMoveY=dims.height/2;moveX=-dims.width/2;moveY=-dims.height/2;break;} +return new Effect.Move(element,{x:initialMoveX,y:initialMoveY,duration:0.01,beforeSetup:function(effect){effect.element.hide();effect.element.makeClipping();effect.element.makePositioned();},afterFinishInternal:function(effect){new Effect.Parallel([new Effect.Opacity(effect.element,{sync:true,to:1.0,from:0.0,transition:options.opacityTransition}),new Effect.Move(effect.element,{x:moveX,y:moveY,sync:true,transition:options.moveTransition}),new Effect.Scale(effect.element,100,{scaleMode:{originalHeight:dims.height,originalWidth:dims.width},sync:true,scaleFrom:window.opera?1:0,transition:options.scaleTransition,restoreAfterFinish:true})],Object.extend({beforeSetup:function(effect){effect.effects[0].element.setStyle({height:'0px'});effect.effects[0].element.show();},afterFinishInternal:function(effect){effect.effects[0].element.undoClipping();effect.effects[0].element.undoPositioned();effect.effects[0].element.setStyle(oldStyle);}},options))}});} +Effect.Shrink=function(element){element=$(element);var options=Object.extend({direction:'center',moveTransition:Effect.Transitions.sinoidal,scaleTransition:Effect.Transitions.sinoidal,opacityTransition:Effect.Transitions.none},arguments[1]||{});var oldStyle={top:element.style.top,left:element.style.left,height:element.style.height,width:element.style.width,opacity:element.getInlineOpacity()};var dims=element.getDimensions();var moveX,moveY;switch(options.direction){case'top-left':moveX=moveY=0;break;case'top-right':moveX=dims.width;moveY=0;break;case'bottom-left':moveX=0;moveY=dims.height;break;case'bottom-right':moveX=dims.width;moveY=dims.height;break;case'center':moveX=dims.width/2;moveY=dims.height/2;break;} +return new Effect.Parallel([new Effect.Opacity(element,{sync:true,to:0.0,from:1.0,transition:options.opacityTransition}),new Effect.Scale(element,window.opera?1:0,{sync:true,transition:options.scaleTransition,restoreAfterFinish:true}),new Effect.Move(element,{x:moveX,y:moveY,sync:true,transition:options.moveTransition})],Object.extend({beforeStartInternal:function(effect){effect.effects[0].element.makePositioned();effect.effects[0].element.makeClipping();},afterFinishInternal:function(effect){effect.effects[0].element.hide();effect.effects[0].element.undoClipping();effect.effects[0].element.undoPositioned();effect.effects[0].element.setStyle(oldStyle);}},options));} +Effect.Pulsate=function(element){element=$(element);var options=arguments[1]||{};var oldOpacity=element.getInlineOpacity();var transition=options.transition||Effect.Transitions.sinoidal;var reverser=function(pos){return transition(1-Effect.Transitions.pulse(pos))};reverser.bind(transition);return new Effect.Opacity(element,Object.extend(Object.extend({duration:3.0,from:0,afterFinishInternal:function(effect){effect.element.setStyle({opacity:oldOpacity});}},options),{transition:reverser}));} +Effect.Fold=function(element){element=$(element);var oldStyle={top:element.style.top,left:element.style.left,width:element.style.width,height:element.style.height};Element.makeClipping(element);return new Effect.Scale(element,5,Object.extend({scaleContent:false,scaleX:false,afterFinishInternal:function(effect){new Effect.Scale(element,1,{scaleContent:false,scaleY:false,afterFinishInternal:function(effect){effect.element.hide();effect.element.undoClipping();effect.element.setStyle(oldStyle);}});}},arguments[1]||{}));};['setOpacity','getOpacity','getInlineOpacity','forceRerendering','setContentZoom','collectTextNodes','collectTextNodesIgnoreClass','childrenWithClassName'].each(function(f){Element.Methods[f]=Element[f];});Element.Methods.visualEffect=function(element,effect,options){s=effect.gsub(/_/,'-').camelize();effect_class=s.charAt(0).toUpperCase()+s.substring(1);new Effect[effect_class](element,options);return $(element);};Element.addMethods(); \ No newline at end of file diff --git a/Solar/App/Public/scripts-min/scriptaculous/scriptaculous.js b/Solar/App/Public/scripts-min/scriptaculous/scriptaculous.js new file mode 100644 index 00000000..393240a3 --- /dev/null +++ b/Solar/App/Public/scripts-min/scriptaculous/scriptaculous.js @@ -0,0 +1,6 @@ +// From script.aculo.us 1.6.4, compressed by jsmin (http://www.crockford.com/javascript/jsmin.html). + +var Scriptaculous={Version:'1.6.4',require:function(libraryName){document.write('');},load:function(){if((typeof Prototype=='undefined')||(typeof Element=='undefined')||(typeof Element.Methods=='undefined')||parseFloat(Prototype.Version.split(".")[0]+"."+ +Prototype.Version.split(".")[1])<1.5) +throw("script.aculo.us requires the Prototype JavaScript framework >= 1.5.0");$A(document.getElementsByTagName("script")).findAll(function(s){return(s.src&&s.src.match(/scriptaculous\.js(\?.*)?$/))}).each(function(s){var path=s.src.replace(/scriptaculous\.js(\?.*)?$/,'');var includes=s.src.match(/\?.*load=([a-z,]*)/);(includes?includes[1]:'builder,effects,dragdrop,controls,slider').split(',').each(function(include){Scriptaculous.require(path+include+'.js')});});}} +Scriptaculous.load(); \ No newline at end of file diff --git a/Solar/App/Public/scripts-min/scriptaculous/slider.js b/Solar/App/Public/scripts-min/scriptaculous/slider.js new file mode 100644 index 00000000..8d24de3a --- /dev/null +++ b/Solar/App/Public/scripts-min/scriptaculous/slider.js @@ -0,0 +1,18 @@ +// From script.aculo.us 1.6.4, compressed by jsmin (http://www.crockford.com/javascript/jsmin.html). + +if(!Control)var Control={};Control.Slider=Class.create();Control.Slider.prototype={initialize:function(handle,track,options){var slider=this;if(handle instanceof Array){this.handles=handle.collect(function(e){return $(e)});}else{this.handles=[$(handle)];} +this.track=$(track);this.options=options||{};this.axis=this.options.axis||'horizontal';this.increment=this.options.increment||1;this.step=parseInt(this.options.step||'1');this.range=this.options.range||$R(0,1);this.value=0;this.values=this.handles.map(function(){return 0});this.spans=this.options.spans?this.options.spans.map(function(s){return $(s)}):false;this.options.startSpan=$(this.options.startSpan||null);this.options.endSpan=$(this.options.endSpan||null);this.restricted=this.options.restricted||false;this.maximum=this.options.maximum||this.range.end;this.minimum=this.options.minimum||this.range.start;this.alignX=parseInt(this.options.alignX||'0');this.alignY=parseInt(this.options.alignY||'0');this.trackLength=this.maximumOffset()-this.minimumOffset();this.handleLength=this.isVertical()?(this.handles[0].offsetHeight!=0?this.handles[0].offsetHeight:this.handles[0].style.height.replace(/px$/,"")):(this.handles[0].offsetWidth!=0?this.handles[0].offsetWidth:this.handles[0].style.width.replace(/px$/,""));this.active=false;this.dragging=false;this.disabled=false;if(this.options.disabled)this.setDisabled();this.allowedValues=this.options.values?this.options.values.sortBy(Prototype.K):false;if(this.allowedValues){this.minimum=this.allowedValues.min();this.maximum=this.allowedValues.max();} +this.eventMouseDown=this.startDrag.bindAsEventListener(this);this.eventMouseUp=this.endDrag.bindAsEventListener(this);this.eventMouseMove=this.update.bindAsEventListener(this);this.handles.each(function(h,i){i=slider.handles.length-1-i;slider.setValue(parseFloat((slider.options.sliderValue instanceof Array?slider.options.sliderValue[i]:slider.options.sliderValue)||slider.range.start),i);Element.makePositioned(h);Event.observe(h,"mousedown",slider.eventMouseDown);});Event.observe(this.track,"mousedown",this.eventMouseDown);Event.observe(document,"mouseup",this.eventMouseUp);Event.observe(document,"mousemove",this.eventMouseMove);this.initialized=true;},dispose:function(){var slider=this;Event.stopObserving(this.track,"mousedown",this.eventMouseDown);Event.stopObserving(document,"mouseup",this.eventMouseUp);Event.stopObserving(document,"mousemove",this.eventMouseMove);this.handles.each(function(h){Event.stopObserving(h,"mousedown",slider.eventMouseDown);});},setDisabled:function(){this.disabled=true;},setEnabled:function(){this.disabled=false;},getNearestValue:function(value){if(this.allowedValues){if(value>=this.allowedValues.max())return(this.allowedValues.max());if(value<=this.allowedValues.min())return(this.allowedValues.min());var offset=Math.abs(this.allowedValues[0]-value);var newValue=this.allowedValues[0];this.allowedValues.each(function(v){var currentOffset=Math.abs(v-value);if(currentOffset<=offset){newValue=v;offset=currentOffset;}});return newValue;} +if(value>this.range.end)return this.range.end;if(value0)&&(sliderValuethis.values[handleIdx+1])) +sliderValue=this.values[handleIdx+1];} +sliderValue=this.getNearestValue(sliderValue);this.values[handleIdx]=sliderValue;this.value=this.values[0];this.handles[handleIdx].style[this.isVertical()?'top':'left']=this.translateToPx(sliderValue);this.drawSpans();if(!this.dragging||!this.event)this.updateFinished();},setValueBy:function(delta,handleIdx){this.setValue(this.values[handleIdx||this.activeHandleIdx||0]+delta,handleIdx||this.activeHandleIdx||0);},translateToPx:function(value){return Math.round(((this.trackLength-this.handleLength)/(this.range.end-this.range.start))*(value-this.range.start))+"px";},translateToValue:function(offset){return((offset/(this.trackLength-this.handleLength)*(this.range.end-this.range.start))+this.range.start);},getRange:function(range){var v=this.values.sortBy(Prototype.K);range=range||0;return $R(v[range],v[range+1]);},minimumOffset:function(){return(this.isVertical()?this.alignY:this.alignX);},maximumOffset:function(){return(this.isVertical()?(this.track.offsetHeight!=0?this.track.offsetHeight:this.track.style.height.replace(/px$/,""))-this.alignY:(this.track.offsetWidth!=0?this.track.offsetWidth:this.track.style.width.replace(/px$/,""))-this.alignY);},isVertical:function(){return(this.axis=='vertical');},drawSpans:function(){var slider=this;if(this.spans) +$R(0,this.spans.length-1).each(function(r){slider.setSpan(slider.spans[r],slider.getRange(r))});if(this.options.startSpan) +this.setSpan(this.options.startSpan,$R(0,this.values.length>1?this.getRange(0).min():this.value));if(this.options.endSpan) +this.setSpan(this.options.endSpan,$R(this.values.length>1?this.getRange(this.spans.length-1).max():this.value,this.maximum));},setSpan:function(span,range){if(this.isVertical()){span.style.top=this.translateToPx(range.start);span.style.height=this.translateToPx(range.end-range.start+this.range.start);}else{span.style.left=this.translateToPx(range.start);span.style.width=this.translateToPx(range.end-range.start+this.range.start);}},updateStyles:function(){this.handles.each(function(h){Element.removeClassName(h,'selected')});Element.addClassName(this.activeHandle,'selected');},startDrag:function(event){if(Event.isLeftClick(event)){if(!this.disabled){this.active=true;var handle=Event.element(event);var pointer=[Event.pointerX(event),Event.pointerY(event)];var track=handle;if(track==this.track){var offsets=Position.cumulativeOffset(this.track);this.event=event;this.setValue(this.translateToValue((this.isVertical()?pointer[1]-offsets[1]:pointer[0]-offsets[0])-(this.handleLength/2)));var offsets=Position.cumulativeOffset(this.activeHandle);this.offsetX=(pointer[0]-offsets[0]);this.offsetY=(pointer[1]-offsets[1]);}else{while((this.handles.indexOf(handle)==-1)&&handle.parentNode) +handle=handle.parentNode;this.activeHandle=handle;this.activeHandleIdx=this.handles.indexOf(this.activeHandle);this.updateStyles();var offsets=Position.cumulativeOffset(this.activeHandle);this.offsetX=(pointer[0]-offsets[0]);this.offsetY=(pointer[1]-offsets[1]);}} +Event.stop(event);}},update:function(event){if(this.active){if(!this.dragging)this.dragging=true;this.draw(event);if(navigator.appVersion.indexOf('AppleWebKit')>0)window.scrollBy(0,0);Event.stop(event);}},draw:function(event){var pointer=[Event.pointerX(event),Event.pointerY(event)];var offsets=Position.cumulativeOffset(this.track);pointer[0]-=this.offsetX+offsets[0];pointer[1]-=this.offsetY+offsets[1];this.event=event;this.setValue(this.translateToValue(this.isVertical()?pointer[1]:pointer[0]));if(this.initialized&&this.options.onSlide) +this.options.onSlide(this.values.length>1?this.values:this.value,this);},endDrag:function(event){if(this.active&&this.dragging){this.finishDrag(event,true);Event.stop(event);} +this.active=false;this.dragging=false;},finishDrag:function(event,success){this.active=false;this.dragging=false;this.updateFinished();},updateFinished:function(){if(this.initialized&&this.options.onChange) +this.options.onChange(this.values.length>1?this.values:this.value,this);this.event=null;}} \ No newline at end of file diff --git a/Solar/App/Public/scripts-min/scriptaculous/unittest.js b/Solar/App/Public/scripts-min/scriptaculous/unittest.js new file mode 100644 index 00000000..3a6e88be --- /dev/null +++ b/Solar/App/Public/scripts-min/scriptaculous/unittest.js @@ -0,0 +1,51 @@ +// From script.aculo.us 1.6.4, compressed by jsmin (http://www.crockford.com/javascript/jsmin.html). + +Event.simulateMouse=function(element,eventName){var options=Object.extend({pointerX:0,pointerY:0,buttons:0},arguments[2]||{});var oEvent=document.createEvent("MouseEvents");oEvent.initMouseEvent(eventName,true,true,document.defaultView,options.buttons,options.pointerX,options.pointerY,options.pointerX,options.pointerY,false,false,false,false,0,$(element));if(this.mark)Element.remove(this.mark);this.mark=document.createElement('div');this.mark.appendChild(document.createTextNode(" "));document.body.appendChild(this.mark);this.mark.style.position='absolute';this.mark.style.top=options.pointerY+"px";this.mark.style.left=options.pointerX+"px";this.mark.style.width="5px";this.mark.style.height="5px;";this.mark.style.borderTop="1px solid red;" +this.mark.style.borderLeft="1px solid red;" +if(this.step) +alert('['+new Date().getTime().toString()+'] '+eventName+'/'+Test.Unit.inspect(options));$(element).dispatchEvent(oEvent);};Event.simulateKey=function(element,eventName){var options=Object.extend({ctrlKey:false,altKey:false,shiftKey:false,metaKey:false,keyCode:0,charCode:0},arguments[2]||{});var oEvent=document.createEvent("KeyEvents");oEvent.initKeyEvent(eventName,true,true,window,options.ctrlKey,options.altKey,options.shiftKey,options.metaKey,options.keyCode,options.charCode);$(element).dispatchEvent(oEvent);};Event.simulateKeys=function(element,command){for(var i=0;i'+''+''+''+'
    StatusTestMessage
    ';this.logsummary=$('logsummary') +this.loglines=$('loglines');},_toHTML:function(txt){return txt.escapeHTML().replace(/\n/g,"
    ");}} +Test.Unit.Runner=Class.create();Test.Unit.Runner.prototype={initialize:function(testcases){this.options=Object.extend({testLog:'testlog'},arguments[1]||{});this.options.resultsURL=this.parseResultsURLQueryParameter();if(this.options.testLog){this.options.testLog=$(this.options.testLog)||null;} +if(this.options.tests){this.tests=[];for(var i=0;i '+this.options.titles[testcase]:testcase,testcases[testcase],testcases["setup"],testcases["teardown"]));}}}} +this.currentTest=0;this.logger=new Test.Unit.Logger(this.options.testLog);setTimeout(this.runTests.bind(this),1000);},parseResultsURLQueryParameter:function(){return window.location.search.parseQuery()["resultsURL"];},getResult:function(){var hasFailure=false;for(var i=0;i0){return"ERROR";} +if(this.tests[i].failures>0){hasFailure=true;}} +if(hasFailure){return"FAILURE";}else{return"SUCCESS";}},postResults:function(){if(this.options.resultsURL){new Ajax.Request(this.options.resultsURL,{method:'get',parameters:'result='+this.getResult(),asynchronous:false});}},runTests:function(){var test=this.tests[this.currentTest];if(!test){this.postResults();this.logger.summary(this.summary());return;} +if(!test.isWaiting){this.logger.start(test.name);} +test.run();if(test.isWaiting){this.logger.message("Waiting for "+test.timeToWait+"ms");setTimeout(this.runTests.bind(this),test.timeToWait||1000);}else{this.logger.finish(test.status(),test.summary());this.currentTest++;this.runTests();}},summary:function(){var assertions=0;var failures=0;var errors=0;var messages=[];for(var i=0;i0)return'failed';if(this.errors>0)return'error';return'passed';},assert:function(expression){var message=arguments[1]||'assert: got "'+Test.Unit.inspect(expression)+'"';try{expression?this.pass():this.fail(message);} +catch(e){this.error(e);}},assertEqual:function(expected,actual){var message=arguments[2]||"assertEqual";try{(expected==actual)?this.pass():this.fail(message+': expected "'+Test.Unit.inspect(expected)+'", actual "'+Test.Unit.inspect(actual)+'"');} +catch(e){this.error(e);}},assertEnumEqual:function(expected,actual){var message=arguments[2]||"assertEnumEqual";try{$A(expected).length==$A(actual).length&&expected.zip(actual).all(function(pair){return pair[0]==pair[1]})?this.pass():this.fail(message+': expected '+Test.Unit.inspect(expected)+', actual '+Test.Unit.inspect(actual));} +catch(e){this.error(e);}},assertNotEqual:function(expected,actual){var message=arguments[2]||"assertNotEqual";try{(expected!=actual)?this.pass():this.fail(message+': got "'+Test.Unit.inspect(actual)+'"');} +catch(e){this.error(e);}},assertIdentical:function(expected,actual){var message=arguments[2]||"assertIdentical";try{(expected===actual)?this.pass():this.fail(message+': expected "'+Test.Unit.inspect(expected)+'", actual "'+Test.Unit.inspect(actual)+'"');} +catch(e){this.error(e);}},assertNotIdentical:function(expected,actual){var message=arguments[2]||"assertNotIdentical";try{!(expected===actual)?this.pass():this.fail(message+': expected "'+Test.Unit.inspect(expected)+'", actual "'+Test.Unit.inspect(actual)+'"');} +catch(e){this.error(e);}},assertNull:function(obj){var message=arguments[1]||'assertNull' +try{(obj==null)?this.pass():this.fail(message+': got "'+Test.Unit.inspect(obj)+'"');} +catch(e){this.error(e);}},assertMatch:function(expected,actual){var message=arguments[2]||'assertMatch';var regex=new RegExp(expected);try{(regex.exec(actual))?this.pass():this.fail(message+' : regex: "'+Test.Unit.inspect(expected)+' did not match: '+Test.Unit.inspect(actual)+'"');} +catch(e){this.error(e);}},assertHidden:function(element){var message=arguments[1]||'assertHidden';this.assertEqual("none",element.style.display,message);},assertNotNull:function(object){var message=arguments[1]||'assertNotNull';this.assert(object!=null,message);},assertType:function(expected,actual){var message=arguments[2]||'assertType';try{(actual.constructor==expected)?this.pass():this.fail(message+': expected "'+Test.Unit.inspect(expected)+'", actual "'+(actual.constructor)+'"');} +catch(e){this.error(e);}},assertNotOfType:function(expected,actual){var message=arguments[2]||'assertNotOfType';try{(actual.constructor!=expected)?this.pass():this.fail(message+': expected "'+Test.Unit.inspect(expected)+'", actual "'+(actual.constructor)+'"');} +catch(e){this.error(e);}},assertInstanceOf:function(expected,actual){var message=arguments[2]||'assertInstanceOf';try{(actual instanceof expected)?this.pass():this.fail(message+": object was not an instance of the expected type");} +catch(e){this.error(e);}},assertNotInstanceOf:function(expected,actual){var message=arguments[2]||'assertNotInstanceOf';try{!(actual instanceof expected)?this.pass():this.fail(message+": object was an instance of the not expected type");} +catch(e){this.error(e);}},assertRespondsTo:function(method,obj){var message=arguments[2]||'assertRespondsTo';try{(obj[method]&&typeof obj[method]=='function')?this.pass():this.fail(message+": object doesn't respond to ["+method+"]");} +catch(e){this.error(e);}},assertReturnsTrue:function(method,obj){var message=arguments[2]||'assertReturnsTrue';try{var m=obj[method];if(!m)m=obj['is'+method.charAt(0).toUpperCase()+method.slice(1)];m()?this.pass():this.fail(message+": method returned false");} +catch(e){this.error(e);}},assertReturnsFalse:function(method,obj){var message=arguments[2]||'assertReturnsFalse';try{var m=obj[method];if(!m)m=obj['is'+method.charAt(0).toUpperCase()+method.slice(1)];!m()?this.pass():this.fail(message+": method returned true");} +catch(e){this.error(e);}},assertRaise:function(exceptionName,method){var message=arguments[2]||'assertRaise';try{method();this.fail(message+": exception expected but none was raised");} +catch(e){(e.name==exceptionName)?this.pass():this.error(e);}},assertElementsMatch:function(){var expressions=$A(arguments),elements=$A(expressions.shift());if(elements.length!=expressions.length){this.fail('assertElementsMatch: size mismatch: '+elements.length+' elements, '+expressions.length+' expressions');return false;} +elements.zip(expressions).all(function(pair,index){var element=$(pair.first()),expression=pair.last();if(element.match(expression))return true;this.fail('assertElementsMatch: (in index '+index+') expected '+expression.inspect()+' but got '+element.inspect());}.bind(this))&&this.pass();},assertElementMatches:function(element,expression){this.assertElementsMatch([element],expression);},benchmark:function(operation,iterations){var startAt=new Date();(iterations||1).times(operation);var timeTaken=((new Date())-startAt);this.info((arguments[2]||'Operation')+' finished '+ +iterations+' iterations in '+(timeTaken/1000)+'s');return timeTaken;},_isVisible:function(element){element=$(element);if(!element.parentNode)return true;this.assertNotNull(element);if(element.style&&Element.getStyle(element,'display')=='none') +return false;return this._isVisible(element.parentNode);},assertNotVisible:function(element){this.assert(!this._isVisible(element),Test.Unit.inspect(element)+" was not hidden and didn't have a hidden parent either. "+(""||arguments[1]));},assertVisible:function(element){this.assert(this._isVisible(element),Test.Unit.inspect(element)+" was not visible. "+(""||arguments[1]));},benchmark:function(operation,iterations){var startAt=new Date();(iterations||1).times(operation);var timeTaken=((new Date())-startAt);this.info((arguments[2]||'Operation')+' finished '+ +iterations+' iterations in '+(timeTaken/1000)+'s');return timeTaken;}} +Test.Unit.Testcase=Class.create();Object.extend(Object.extend(Test.Unit.Testcase.prototype,Test.Unit.Assertions.prototype),{initialize:function(name,test,setup,teardown){Test.Unit.Assertions.prototype.initialize.bind(this)();this.name=name;if(typeof test=='string'){test=test.gsub(/(\.should[^\(]+\()/,'#{0}this,');test=test.gsub(/(\.should[^\(]+)\(this,\)/,'#{1}(this)');this.test=function(){eval('with(this){'+test+'}');}}else{this.test=test||function(){};} +this.setup=setup||function(){};this.teardown=teardown||function(){};this.isWaiting=false;this.timeToWait=1000;},wait:function(time,nextPart){this.isWaiting=true;this.test=nextPart;this.timeToWait=time;},run:function(){try{try{if(!this.isWaiting)this.setup.bind(this)();this.isWaiting=false;this.test.bind(this)();}finally{if(!this.isWaiting){this.teardown.bind(this)();}}} +catch(e){this.error(e);}}});Test.setupBDDExtensionMethods=function(){var METHODMAP={shouldEqual:'assertEqual',shouldNotEqual:'assertNotEqual',shouldEqualEnum:'assertEnumEqual',shouldBeA:'assertType',shouldNotBeA:'assertNotOfType',shouldBeAn:'assertType',shouldNotBeAn:'assertNotOfType',shouldBeNull:'assertNull',shouldNotBeNull:'assertNotNull',shouldBe:'assertReturnsTrue',shouldNotBe:'assertReturnsFalse',shouldRespondTo:'assertRespondsTo'};Test.BDDMethods={};for(m in METHODMAP){Test.BDDMethods[m]=eval('function(){'+'var args = $A(arguments);'+'var scope = args.shift();'+'scope.'+METHODMAP[m]+'.apply(scope,(args || []).concat([this])); }');} +[Array.prototype,String.prototype,Number.prototype].each(function(p){Object.extend(p,Test.BDDMethods)});} +Test.context=function(name,spec,log){Test.setupBDDExtensionMethods();var compiledSpec={};var titles={};for(specName in spec){switch(specName){case"setup":case"teardown":compiledSpec[specName]=spec[specName];break;default:var testName='test'+specName.gsub(/\s+/,'-').camelize();var body=spec[specName].toString().split('\n').slice(1);if(/^\{/.test(body[0]))body=body.slice(1);body.pop();body=body.map(function(statement){return statement.strip()});compiledSpec[testName]=body.join('\n');titles[testName]=specName;}} +new Test.Unit.Runner(compiledSpec,{titles:titles,testLog:log||'testlog',context:name});}; \ No newline at end of file diff --git a/Solar/App/Public/scripts/prototype/prototype.js b/Solar/App/Public/scripts/prototype/prototype.js index 6154b492..14edec81 100755 --- a/Solar/App/Public/scripts/prototype/prototype.js +++ b/Solar/App/Public/scripts/prototype/prototype.js @@ -1,150 +1,2241 @@ -// Prototype 1.5.0_rc0, compressed by jsmin (http://www.crockford.com/javascript/jsmin.html). -var Prototype={Version:'1.5.0_rc0',ScriptFragment:'(?:)((\n|\r|.)*?)(?:<\/script>)',emptyFunction:function(){},K:function(x){return x}} -var Class={create:function(){return function(){this.initialize.apply(this,arguments);}}} -var Abstract=new Object();Object.extend=function(destination,source){for(var property in source){destination[property]=source[property];} -return destination;} -Object.inspect=function(object){try{if(object==undefined)return'undefined';if(object==null)return'null';return object.inspect?object.inspect():object.toString();}catch(e){if(e instanceof RangeError)return'...';throw e;}} -Function.prototype.bind=function(){var __method=this,args=$A(arguments),object=args.shift();return function(){return __method.apply(object,args.concat($A(arguments)));}} -Function.prototype.bindAsEventListener=function(object){var __method=this;return function(event){return __method.call(object,event||window.event);}} -Object.extend(Number.prototype,{toColorPart:function(){var digits=this.toString(16);if(this<16)return'0'+digits;return digits;},succ:function(){return this+1;},times:function(iterator){$R(0,this,true).each(iterator);return this;}});var Try={these:function(){var returnValue;for(var i=0;i0){if(match=source.match(pattern)){result+=source.slice(0,match.index);result+=(replacement(match)||'').toString();source=source.slice(match.index+match[0].length);}else{result+=source,source='';}} -return result;},sub:function(pattern,replacement,count){replacement=this.gsub.prepareReplacement(replacement);count=count===undefined?1:count;return this.gsub(pattern,function(match){if(--count<0)return match[0];return replacement(match);});},scan:function(pattern,iterator){this.gsub(pattern,iterator);return this;},truncate:function(length,truncation){length=length||30;truncation=truncation===undefined?'...':truncation;return this.length>length?this.slice(0,length-truncation.length)+truncation:this;},strip:function(){return this.replace(/^\s+/,'').replace(/\s+$/,'');},stripTags:function(){return this.replace(/<\/?[^>]+>/gi,'');},stripScripts:function(){return this.replace(new RegExp(Prototype.ScriptFragment,'img'),'');},extractScripts:function(){var matchAll=new RegExp(Prototype.ScriptFragment,'img');var matchOne=new RegExp(Prototype.ScriptFragment,'im');return(this.match(matchAll)||[]).map(function(scriptTag){return(scriptTag.match(matchOne)||['',''])[1];});},evalScripts:function(){return this.extractScripts().map(function(script){return eval(script)});},escapeHTML:function(){var div=document.createElement('div');var text=document.createTextNode(this);div.appendChild(text);return div.innerHTML;},unescapeHTML:function(){var div=document.createElement('div');div.innerHTML=this.stripTags();return div.childNodes[0]?div.childNodes[0].nodeValue:'';},toQueryParams:function(){var pairs=this.match(/^\??(.*)$/)[1].split('&');return pairs.inject({},function(params,pairString){var pair=pairString.split('=');params[pair[0]]=pair[1];return params;});},toArray:function(){return this.split('');},camelize:function(){var oStringList=this.split('-');if(oStringList.length==1)return oStringList[0];var camelizedString=this.indexOf('-')==0?oStringList[0].charAt(0).toUpperCase()+oStringList[0].substring(1):oStringList[0];for(var i=1,len=oStringList.length;i=result) -result=value;});return result;},min:function(iterator){var result;this.each(function(value,index){value=(iterator||Prototype.K)(value,index);if(result==undefined||valueb?1:0;}).pluck('value');},toArray:function(){return this.collect(Prototype.K);},zip:function(){var iterator=Prototype.K,args=$A(arguments);if(typeof args.last()=='function') -iterator=args.pop();var collections=[this].concat(args).map($A);return this.map(function(value,index){return iterator(collections.pluck(index));});},inspect:function(){return'#';}} -Object.extend(Enumerable,{map:Enumerable.collect,find:Enumerable.detect,select:Enumerable.findAll,member:Enumerable.include,entries:Enumerable.toArray});var $A=Array.from=function(iterable){if(!iterable)return[];if(iterable.toArray){return iterable.toArray();}else{var results=[];for(var i=0;i';}} -function $H(object){var hash=Object.extend({},object||{});Object.extend(hash,Enumerable);Object.extend(hash,Hash);return hash;} -ObjectRange=Class.create();Object.extend(ObjectRange.prototype,Enumerable);Object.extend(ObjectRange.prototype,{initialize:function(start,end,exclusive){this.start=start;this.end=end;this.exclusive=exclusive;},_each:function(iterator){var value=this.start;do{iterator(value);value=value.succ();}while(this.include(value));},include:function(value){if(value=200&&this.transport.status<300);},responseIsFailure:function(){return!this.responseIsSuccess();}} -Ajax.Request=Class.create();Ajax.Request.Events=['Uninitialized','Loading','Loaded','Interactive','Complete'];Ajax.Request.prototype=Object.extend(new Ajax.Base(),{initialize:function(url,options){this.transport=Ajax.getTransport();this.setOptions(options);this.request(url);},request:function(url){var parameters=this.options.parameters||'';if(parameters.length>0)parameters+='&_=';try{this.url=url;if(this.options.method=='get'&¶meters.length>0) -this.url+=(this.url.match(/\?/)?'&':'?')+parameters;Ajax.Responders.dispatch('onCreate',this,this.transport);this.transport.open(this.options.method,this.url,this.options.asynchronous);if(this.options.asynchronous){this.transport.onreadystatechange=this.onStateChange.bind(this);setTimeout((function(){this.respondToReadyState(1)}).bind(this),10);} -this.setRequestHeaders();var body=this.options.postBody?this.options.postBody:parameters;this.transport.send(this.options.method=='post'?body:null);}catch(e){this.dispatchException(e);}},setRequestHeaders:function(){var requestHeaders=['X-Requested-With','XMLHttpRequest','X-Prototype-Version',Prototype.Version,'Accept','text/javascript, text/html, application/xml, text/xml, */*'];if(this.options.method=='post'){requestHeaders.push('Content-type',this.options.contentType);if(this.transport.overrideMimeType) -requestHeaders.push('Connection','close');} -if(this.options.requestHeaders) -requestHeaders.push.apply(requestHeaders,this.options.requestHeaders);for(var i=0;i'+this.content+'';return $A(div.childNodes[0].childNodes[0].childNodes);}} -var Insertion=new Object();Insertion.Before=Class.create();Insertion.Before.prototype=Object.extend(new Abstract.Insertion('beforeBegin'),{initializeRange:function(){this.range.setStartBefore(this.element);},insertContent:function(fragments){fragments.each((function(fragment){this.element.parentNode.insertBefore(fragment,this.element);}).bind(this));}});Insertion.Top=Class.create();Insertion.Top.prototype=Object.extend(new Abstract.Insertion('afterBegin'),{initializeRange:function(){this.range.selectNodeContents(this.element);this.range.collapse(true);},insertContent:function(fragments){fragments.reverse(false).each((function(fragment){this.element.insertBefore(fragment,this.element.firstChild);}).bind(this));}});Insertion.Bottom=Class.create();Insertion.Bottom.prototype=Object.extend(new Abstract.Insertion('beforeEnd'),{initializeRange:function(){this.range.selectNodeContents(this.element);this.range.collapse(this.element);},insertContent:function(fragments){fragments.each((function(fragment){this.element.appendChild(fragment);}).bind(this));}});Insertion.After=Class.create();Insertion.After.prototype=Object.extend(new Abstract.Insertion('afterEnd'),{initializeRange:function(){this.range.setStartAfter(this.element);},insertContent:function(fragments){fragments.each((function(fragment){this.element.parentNode.insertBefore(fragment,this.element.nextSibling);}).bind(this));}});Element.ClassNames=Class.create();Element.ClassNames.prototype={initialize:function(element){this.element=$(element);},_each:function(iterator){this.element.className.split(/\s+/).select(function(name){return name.length>0;})._each(iterator);},set:function(className){this.element.className=className;},add:function(classNameToAdd){if(this.include(classNameToAdd))return;this.set(this.toArray().concat(classNameToAdd).join(' '));},remove:function(classNameToRemove){if(!this.include(classNameToRemove))return;this.set(this.select(function(className){return className!=classNameToRemove;}).join(' '));},toString:function(){return this.toArray().join(' ');}} -Object.extend(Element.ClassNames.prototype,Enumerable);var Selector=Class.create();Selector.prototype={initialize:function(expression){this.params={classNames:[]};this.expression=expression.toString().strip();this.parseExpression();this.compileMatcher();},parseExpression:function(){function abort(message){throw'Parse error in selector: '+message;} -if(this.expression=='')abort('empty expression');var params=this.params,expr=this.expression,match,modifier,clause,rest;while(match=expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)){params.attributes=params.attributes||[];params.attributes.push({name:match[2],operator:match[3],value:match[4]||match[5]||''});expr=match[1];} -if(expr=='*')return this.params.wildcard=true;while(match=expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)){modifier=match[1],clause=match[2],rest=match[3];switch(modifier){case'#':params.id=clause;break;case'.':params.classNames.push(clause);break;case'':case undefined:params.tagName=clause.toUpperCase();break;default:abort(expr.inspect());} -expr=rest;} -if(expr.length>0)abort(expr.inspect());},buildMatchExpression:function(){var params=this.params,conditions=[],clause;if(params.wildcard) -conditions.push('true');if(clause=params.id) -conditions.push('element.id == '+clause.inspect());if(clause=params.tagName) -conditions.push('element.tagName.toUpperCase() == '+clause.inspect());if((clause=params.classNames).length>0) -for(var i=0;i=0){opt=element.options[index];value=opt.value||opt.text;} -return[element.name,value];},selectMany:function(element){var value=[];for(var i=0;i=this.offset[1]&&y=this.offset[0]&&x=this.offset[1]&&this.ycomp=this.offset[0]&&this.xcomp + * + * Prototype is freely distributable under the terms of an MIT-style license. + * For details, see the Prototype web site: http://prototype.conio.net/ + * +/*--------------------------------------------------------------------------*/ + +var Prototype = { + Version: '1.5.0_rc1', + ScriptFragment: '(?:)((\n|\r|.)*?)(?:<\/script>)', + + emptyFunction: function() {}, + K: function(x) {return x} +} + +var Class = { + create: function() { + return function() { + this.initialize.apply(this, arguments); + } + } +} + +var Abstract = new Object(); + +Object.extend = function(destination, source) { + for (var property in source) { + destination[property] = source[property]; + } + return destination; +} + +Object.extend(Object, { + inspect: function(object) { + try { + if (object == undefined) return 'undefined'; + if (object == null) return 'null'; + return object.inspect ? object.inspect() : object.toString(); + } catch (e) { + if (e instanceof RangeError) return '...'; + throw e; + } + }, + + keys: function(object) { + var keys = []; + for (var property in object) + keys.push(property); + return keys; + }, + + values: function(object) { + var values = []; + for (var property in object) + values.push(object[property]); + return values; + }, + + clone: function(object) { + return Object.extend({}, object); + } +}); + +Function.prototype.bind = function() { + var __method = this, args = $A(arguments), object = args.shift(); + return function() { + return __method.apply(object, args.concat($A(arguments))); + } +} + +Function.prototype.bindAsEventListener = function(object) { + var __method = this, args = $A(arguments), object = args.shift(); + return function(event) { + return __method.apply(object, [( event || window.event)].concat(args).concat($A(arguments))); + } +} + +Object.extend(Number.prototype, { + toColorPart: function() { + var digits = this.toString(16); + if (this < 16) return '0' + digits; + return digits; + }, + + succ: function() { + return this + 1; + }, + + times: function(iterator) { + $R(0, this, true).each(iterator); + return this; + } +}); + +var Try = { + these: function() { + var returnValue; + + for (var i = 0; i < arguments.length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); + break; + } catch (e) {} + } + + return returnValue; + } +} + +/*--------------------------------------------------------------------------*/ + +var PeriodicalExecuter = Class.create(); +PeriodicalExecuter.prototype = { + initialize: function(callback, frequency) { + this.callback = callback; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + stop: function() { + if (!this.timer) return; + clearInterval(this.timer); + this.timer = null; + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.callback(this); + } finally { + this.currentlyExecuting = false; + } + } + } +} +Object.extend(String.prototype, { + gsub: function(pattern, replacement) { + var result = '', source = this, match; + replacement = arguments.callee.prepareReplacement(replacement); + + while (source.length > 0) { + if (match = source.match(pattern)) { + result += source.slice(0, match.index); + result += (replacement(match) || '').toString(); + source = source.slice(match.index + match[0].length); + } else { + result += source, source = ''; + } + } + return result; + }, + + sub: function(pattern, replacement, count) { + replacement = this.gsub.prepareReplacement(replacement); + count = count === undefined ? 1 : count; + + return this.gsub(pattern, function(match) { + if (--count < 0) return match[0]; + return replacement(match); + }); + }, + + scan: function(pattern, iterator) { + this.gsub(pattern, iterator); + return this; + }, + + truncate: function(length, truncation) { + length = length || 30; + truncation = truncation === undefined ? '...' : truncation; + return this.length > length ? + this.slice(0, length - truncation.length) + truncation : this; + }, + + strip: function() { + return this.replace(/^\s+/, '').replace(/\s+$/, ''); + }, + + stripTags: function() { + return this.replace(/<\/?[^>]+>/gi, ''); + }, + + stripScripts: function() { + return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); + }, + + extractScripts: function() { + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); + var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + return (this.match(matchAll) || []).map(function(scriptTag) { + return (scriptTag.match(matchOne) || ['', ''])[1]; + }); + }, + + evalScripts: function() { + return this.extractScripts().map(function(script) { return eval(script) }); + }, + + escapeHTML: function() { + var div = document.createElement('div'); + var text = document.createTextNode(this); + div.appendChild(text); + return div.innerHTML; + }, + + unescapeHTML: function() { + var div = document.createElement('div'); + div.innerHTML = this.stripTags(); + return div.childNodes[0] ? div.childNodes[0].nodeValue : ''; + }, + + toQueryParams: function() { + var pairs = this.match(/^\??(.*)$/)[1].split('&'); + return pairs.inject({}, function(params, pairString) { + var pair = pairString.split('='); + var value = pair[1] ? decodeURIComponent(pair[1]) : undefined; + params[decodeURIComponent(pair[0])] = value; + return params; + }); + }, + + toArray: function() { + return this.split(''); + }, + + camelize: function() { + var oStringList = this.split('-'); + if (oStringList.length == 1) return oStringList[0]; + + var camelizedString = this.indexOf('-') == 0 + ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1) + : oStringList[0]; + + for (var i = 1, len = oStringList.length; i < len; i++) { + var s = oStringList[i]; + camelizedString += s.charAt(0).toUpperCase() + s.substring(1); + } + + return camelizedString; + }, + + inspect: function(useDoubleQuotes) { + var escapedString = this.replace(/\\/g, '\\\\'); + if (useDoubleQuotes) + return '"' + escapedString.replace(/"/g, '\\"') + '"'; + else + return "'" + escapedString.replace(/'/g, '\\\'') + "'"; + } +}); + +String.prototype.gsub.prepareReplacement = function(replacement) { + if (typeof replacement == 'function') return replacement; + var template = new Template(replacement); + return function(match) { return template.evaluate(match) }; +} + +String.prototype.parseQuery = String.prototype.toQueryParams; + +var Template = Class.create(); +Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; +Template.prototype = { + initialize: function(template, pattern) { + this.template = template.toString(); + this.pattern = pattern || Template.Pattern; + }, + + evaluate: function(object) { + return this.template.gsub(this.pattern, function(match) { + var before = match[1]; + if (before == '\\') return match[2]; + return before + (object[match[3]] || '').toString(); + }); + } +} + +var $break = new Object(); +var $continue = new Object(); + +var Enumerable = { + each: function(iterator) { + var index = 0; + try { + this._each(function(value) { + try { + iterator(value, index++); + } catch (e) { + if (e != $continue) throw e; + } + }); + } catch (e) { + if (e != $break) throw e; + } + }, + + all: function(iterator) { + var result = true; + this.each(function(value, index) { + result = result && !!(iterator || Prototype.K)(value, index); + if (!result) throw $break; + }); + return result; + }, + + any: function(iterator) { + var result = false; + this.each(function(value, index) { + if (result = !!(iterator || Prototype.K)(value, index)) + throw $break; + }); + return result; + }, + + collect: function(iterator) { + var results = []; + this.each(function(value, index) { + results.push(iterator(value, index)); + }); + return results; + }, + + detect: function (iterator) { + var result; + this.each(function(value, index) { + if (iterator(value, index)) { + result = value; + throw $break; + } + }); + return result; + }, + + findAll: function(iterator) { + var results = []; + this.each(function(value, index) { + if (iterator(value, index)) + results.push(value); + }); + return results; + }, + + grep: function(pattern, iterator) { + var results = []; + this.each(function(value, index) { + var stringValue = value.toString(); + if (stringValue.match(pattern)) + results.push((iterator || Prototype.K)(value, index)); + }) + return results; + }, + + include: function(object) { + var found = false; + this.each(function(value) { + if (value == object) { + found = true; + throw $break; + } + }); + return found; + }, + + inject: function(memo, iterator) { + this.each(function(value, index) { + memo = iterator(memo, value, index); + }); + return memo; + }, + + invoke: function(method) { + var args = $A(arguments).slice(1); + return this.collect(function(value) { + return value[method].apply(value, args); + }); + }, + + max: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (result == undefined || value >= result) + result = value; + }); + return result; + }, + + min: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (result == undefined || value < result) + result = value; + }); + return result; + }, + + partition: function(iterator) { + var trues = [], falses = []; + this.each(function(value, index) { + ((iterator || Prototype.K)(value, index) ? + trues : falses).push(value); + }); + return [trues, falses]; + }, + + pluck: function(property) { + var results = []; + this.each(function(value, index) { + results.push(value[property]); + }); + return results; + }, + + reject: function(iterator) { + var results = []; + this.each(function(value, index) { + if (!iterator(value, index)) + results.push(value); + }); + return results; + }, + + sortBy: function(iterator) { + return this.collect(function(value, index) { + return {value: value, criteria: iterator(value, index)}; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }).pluck('value'); + }, + + toArray: function() { + return this.collect(Prototype.K); + }, + + zip: function() { + var iterator = Prototype.K, args = $A(arguments); + if (typeof args.last() == 'function') + iterator = args.pop(); + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { + return iterator(collections.pluck(index)); + }); + }, + + inspect: function() { + return '#'; + } +} + +Object.extend(Enumerable, { + map: Enumerable.collect, + find: Enumerable.detect, + select: Enumerable.findAll, + member: Enumerable.include, + entries: Enumerable.toArray +}); +var $A = Array.from = function(iterable) { + if (!iterable) return []; + if (iterable.toArray) { + return iterable.toArray(); + } else { + var results = []; + for (var i = 0; i < iterable.length; i++) + results.push(iterable[i]); + return results; + } +} + +Object.extend(Array.prototype, Enumerable); + +if (!Array.prototype._reverse) + Array.prototype._reverse = Array.prototype.reverse; + +Object.extend(Array.prototype, { + _each: function(iterator) { + for (var i = 0; i < this.length; i++) + iterator(this[i]); + }, + + clear: function() { + this.length = 0; + return this; + }, + + first: function() { + return this[0]; + }, + + last: function() { + return this[this.length - 1]; + }, + + compact: function() { + return this.select(function(value) { + return value != undefined || value != null; + }); + }, + + flatten: function() { + return this.inject([], function(array, value) { + return array.concat(value && value.constructor == Array ? + value.flatten() : [value]); + }); + }, + + without: function() { + var values = $A(arguments); + return this.select(function(value) { + return !values.include(value); + }); + }, + + indexOf: function(object) { + for (var i = 0; i < this.length; i++) + if (this[i] == object) return i; + return -1; + }, + + reverse: function(inline) { + return (inline !== false ? this : this.toArray())._reverse(); + }, + + reduce: function() { + return this.length > 1 ? this : this[0]; + }, + + uniq: function() { + return this.inject([], function(array, value) { + return array.include(value) ? array : array.concat([value]); + }); + }, + + inspect: function() { + return '[' + this.map(Object.inspect).join(', ') + ']'; + } +}); +var Hash = { + _each: function(iterator) { + for (var key in this) { + var value = this[key]; + if (typeof value == 'function') continue; + + var pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } + }, + + keys: function() { + return this.pluck('key'); + }, + + values: function() { + return this.pluck('value'); + }, + + merge: function(hash) { + return $H(hash).inject($H(this), function(mergedHash, pair) { + mergedHash[pair.key] = pair.value; + return mergedHash; + }); + }, + + toQueryString: function() { + return this.map(function(pair) { + return pair.map(encodeURIComponent).join('='); + }).join('&'); + }, + + inspect: function() { + return '#'; + } +} + +function $H(object) { + var hash = Object.extend({}, object || {}); + Object.extend(hash, Enumerable); + Object.extend(hash, Hash); + return hash; +} +ObjectRange = Class.create(); +Object.extend(ObjectRange.prototype, Enumerable); +Object.extend(ObjectRange.prototype, { + initialize: function(start, end, exclusive) { + this.start = start; + this.end = end; + this.exclusive = exclusive; + }, + + _each: function(iterator) { + var value = this.start; + while (this.include(value)) { + iterator(value); + value = value.succ(); + } + }, + + include: function(value) { + if (value < this.start) + return false; + if (this.exclusive) + return value < this.end; + return value <= this.end; + } +}); + +var $R = function(start, end, exclusive) { + return new ObjectRange(start, end, exclusive); +} + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new XMLHttpRequest()}, + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')} + ) || false; + }, + + activeRequestCount: 0 +} + +Ajax.Responders = { + responders: [], + + _each: function(iterator) { + this.responders._each(iterator); + }, + + register: function(responderToAdd) { + if (!this.include(responderToAdd)) + this.responders.push(responderToAdd); + }, + + unregister: function(responderToRemove) { + this.responders = this.responders.without(responderToRemove); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { + if (responder[callback] && typeof responder[callback] == 'function') { + try { + responder[callback].apply(responder, [request, transport, json]); + } catch (e) {} + } + }); + } +}; + +Object.extend(Ajax.Responders, Enumerable); + +Ajax.Responders.register({ + onCreate: function() { + Ajax.activeRequestCount++; + }, + + onComplete: function() { + Ajax.activeRequestCount--; + } +}); + +Ajax.Base = function() {}; +Ajax.Base.prototype = { + setOptions: function(options) { + this.options = { + method: 'post', + asynchronous: true, + contentType: 'application/x-www-form-urlencoded', + parameters: '' + } + Object.extend(this.options, options || {}); + }, + + responseIsSuccess: function() { + return this.transport.status == undefined + || this.transport.status == 0 + || (this.transport.status >= 200 && this.transport.status < 300); + }, + + responseIsFailure: function() { + return !this.responseIsSuccess(); + } +} + +Ajax.Request = Class.create(); +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + +Ajax.Request.prototype = Object.extend(new Ajax.Base(), { + initialize: function(url, options) { + this.transport = Ajax.getTransport(); + this.setOptions(options); + this.request(url); + }, + + request: function(url) { + var parameters = this.options.parameters || ''; + if (parameters.length > 0) parameters += '&_='; + + /* Simulate other verbs over post */ + if (this.options.method != 'get' && this.options.method != 'post') { + parameters += (parameters.length > 0 ? '&' : '') + '_method=' + this.options.method; + this.options.method = 'post'; + } + + try { + this.url = url; + if (this.options.method == 'get' && parameters.length > 0) + this.url += (this.url.match(/\?/) ? '&' : '?') + parameters; + + Ajax.Responders.dispatch('onCreate', this, this.transport); + + this.transport.open(this.options.method, this.url, + this.options.asynchronous); + + if (this.options.asynchronous) + setTimeout(function() { this.respondToReadyState(1) }.bind(this), 10); + + this.transport.onreadystatechange = this.onStateChange.bind(this); + this.setRequestHeaders(); + + var body = this.options.postBody ? this.options.postBody : parameters; + this.transport.send(this.options.method == 'post' ? body : null); + + /* Force Firefox to handle ready state 4 for synchronous requests */ + if (!this.options.asynchronous && this.transport.overrideMimeType) + this.onStateChange(); + + } catch (e) { + this.dispatchException(e); + } + }, + + setRequestHeaders: function() { + var requestHeaders = + ['X-Requested-With', 'XMLHttpRequest', + 'X-Prototype-Version', Prototype.Version, + 'Accept', 'text/javascript, text/html, application/xml, text/xml, */*']; + + if (this.options.method == 'post') { + requestHeaders.push('Content-type', this.options.contentType); + + /* Force "Connection: close" for Mozilla browsers to work around + * a bug where XMLHttpReqeuest sends an incorrect Content-length + * header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType) + requestHeaders.push('Connection', 'close'); + } + + if (this.options.requestHeaders) + requestHeaders.push.apply(requestHeaders, this.options.requestHeaders); + + for (var i = 0; i < requestHeaders.length; i += 2) + this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]); + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState != 1) + this.respondToReadyState(this.transport.readyState); + }, + + header: function(name) { + try { + return this.transport.getResponseHeader(name); + } catch (e) {} + }, + + evalJSON: function() { + try { + return eval('(' + this.header('X-JSON') + ')'); + } catch (e) {} + }, + + evalResponse: function() { + try { + return eval(this.transport.responseText); + } catch (e) { + this.dispatchException(e); + } + }, + + respondToReadyState: function(readyState) { + var event = Ajax.Request.Events[readyState]; + var transport = this.transport, json = this.evalJSON(); + + if (event == 'Complete') { + try { + (this.options['on' + this.transport.status] + || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(transport, json); + } catch (e) { + this.dispatchException(e); + } + + if ((this.header('Content-type') || '').match(/^text\/javascript/i)) + this.evalResponse(); + } + + try { + (this.options['on' + event] || Prototype.emptyFunction)(transport, json); + Ajax.Responders.dispatch('on' + event, this, transport, json); + } catch (e) { + this.dispatchException(e); + } + + /* Avoid memory leak in MSIE: clean up the oncomplete event handler */ + if (event == 'Complete') + this.transport.onreadystatechange = Prototype.emptyFunction; + }, + + dispatchException: function(exception) { + (this.options.onException || Prototype.emptyFunction)(this, exception); + Ajax.Responders.dispatch('onException', this, exception); + } +}); + +Ajax.Updater = Class.create(); + +Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { + initialize: function(container, url, options) { + this.containers = { + success: container.success ? $(container.success) : $(container), + failure: container.failure ? $(container.failure) : + (container.success ? null : $(container)) + } + + this.transport = Ajax.getTransport(); + this.setOptions(options); + + var onComplete = this.options.onComplete || Prototype.emptyFunction; + this.options.onComplete = (function(transport, object) { + this.updateContent(); + onComplete(transport, object); + }).bind(this); + + this.request(url); + }, + + updateContent: function() { + var receiver = this.responseIsSuccess() ? + this.containers.success : this.containers.failure; + var response = this.transport.responseText; + + if (!this.options.evalScripts) + response = response.stripScripts(); + + if (receiver) { + if (this.options.insertion) { + new this.options.insertion(receiver, response); + } else { + Element.update(receiver, response); + } + } + + if (this.responseIsSuccess()) { + if (this.onComplete) + setTimeout(this.onComplete.bind(this), 10); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(); +Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { + initialize: function(container, url, options) { + this.setOptions(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = (this.options.decay || 1); + + this.updater = {}; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.options.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Prototype.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(request) { + if (this.options.decay) { + this.decay = (request.responseText == this.lastText ? + this.decay * this.options.decay : 1); + + this.lastText = request.responseText; + } + this.timer = setTimeout(this.onTimerEvent.bind(this), + this.decay * this.frequency * 1000); + }, + + onTimerEvent: function() { + this.updater = new Ajax.Updater(this.container, this.url, this.options); + } +}); +function $() { + var results = [], element; + for (var i = 0; i < arguments.length; i++) { + element = arguments[i]; + if (typeof element == 'string') + element = document.getElementById(element); + results.push(Element.extend(element)); + } + return results.reduce(); +} + +document.getElementsByClassName = function(className, parentElement) { + var children = ($(parentElement) || document.body).getElementsByTagName('*'); + return $A(children).inject([], function(elements, child) { + if (child.className.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) + elements.push(Element.extend(child)); + return elements; + }); +} + +/*--------------------------------------------------------------------------*/ + +if (!window.Element) + var Element = new Object(); + +Element.extend = function(element) { + if (!element) return; + if (_nativeExtensions || element.nodeType == 3) return element; + + if (!element._extended && element.tagName && element != window) { + var methods = Object.clone(Element.Methods), cache = Element.extend.cache; + + if (element.tagName == 'FORM') + Object.extend(methods, Form.Methods); + if (['INPUT', 'TEXTAREA', 'SELECT'].include(element.tagName)) + Object.extend(methods, Form.Element.Methods); + + for (var property in methods) { + var value = methods[property]; + if (typeof value == 'function') + element[property] = cache.findOrStore(value); + } + } + + element._extended = true; + return element; +} + +Element.extend.cache = { + findOrStore: function(value) { + return this[value] = this[value] || function() { + return value.apply(null, [this].concat($A(arguments))); + } + } +} + +Element.Methods = { + visible: function(element) { + return $(element).style.display != 'none'; + }, + + toggle: function(element) { + element = $(element); + Element[Element.visible(element) ? 'hide' : 'show'](element); + return element; + }, + + hide: function(element) { + $(element).style.display = 'none'; + return element; + }, + + show: function(element) { + $(element).style.display = ''; + return element; + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + return element; + }, + + update: function(element, html) { + $(element).innerHTML = html.stripScripts(); + setTimeout(function() {html.evalScripts()}, 10); + return element; + }, + + replace: function(element, html) { + element = $(element); + if (element.outerHTML) { + element.outerHTML = html.stripScripts(); + } else { + var range = element.ownerDocument.createRange(); + range.selectNodeContents(element); + element.parentNode.replaceChild( + range.createContextualFragment(html.stripScripts()), element); + } + setTimeout(function() {html.evalScripts()}, 10); + return element; + }, + + inspect: function(element) { + element = $(element); + var result = '<' + element.tagName.toLowerCase(); + $H({'id': 'id', 'className': 'class'}).each(function(pair) { + var property = pair.first(), attribute = pair.last(); + var value = (element[property] || '').toString(); + if (value) result += ' ' + attribute + '=' + value.inspect(true); + }); + return result + '>'; + }, + + recursivelyCollect: function(element, property) { + element = $(element); + var elements = []; + while (element = element[property]) + if (element.nodeType == 1) + elements.push(Element.extend(element)); + return elements; + }, + + ancestors: function(element) { + return $(element).recursivelyCollect('parentNode'); + }, + + descendants: function(element) { + element = $(element); + return $A(element.getElementsByTagName('*')); + }, + + previousSiblings: function(element) { + return $(element).recursivelyCollect('previousSibling'); + }, + + nextSiblings: function(element) { + return $(element).recursivelyCollect('nextSibling'); + }, + + siblings: function(element) { + element = $(element); + return element.previousSiblings().reverse().concat(element.nextSiblings()); + }, + + match: function(element, selector) { + element = $(element); + if (typeof selector == 'string') + selector = new Selector(selector); + return selector.match(element); + }, + + up: function(element, expression, index) { + return Selector.findElement($(element).ancestors(), expression, index); + }, + + down: function(element, expression, index) { + return Selector.findElement($(element).descendants(), expression, index); + }, + + previous: function(element, expression, index) { + return Selector.findElement($(element).previousSiblings(), expression, index); + }, + + next: function(element, expression, index) { + return Selector.findElement($(element).nextSiblings(), expression, index); + }, + + getElementsBySelector: function() { + var args = $A(arguments), element = $(args.shift()); + return Selector.findChildElements(element, args); + }, + + getElementsByClassName: function(element, className) { + element = $(element); + return document.getElementsByClassName(className, element); + }, + + getHeight: function(element) { + element = $(element); + return element.offsetHeight; + }, + + classNames: function(element) { + return new Element.ClassNames(element); + }, + + hasClassName: function(element, className) { + if (!(element = $(element))) return; + return Element.classNames(element).include(className); + }, + + addClassName: function(element, className) { + if (!(element = $(element))) return; + Element.classNames(element).add(className); + return element; + }, + + removeClassName: function(element, className) { + if (!(element = $(element))) return; + Element.classNames(element).remove(className); + return element; + }, + + observe: function() { + Event.observe.apply(Event, arguments); + return $A(arguments).first(); + }, + + stopObserving: function() { + Event.stopObserving.apply(Event, arguments); + return $A(arguments).first(); + }, + + // removes whitespace-only text node children + cleanWhitespace: function(element) { + element = $(element); + var node = element.firstChild; + while (node) { + var nextNode = node.nextSibling; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + element.removeChild(node); + node = nextNode; + } + return element; + }, + + empty: function(element) { + return $(element).innerHTML.match(/^\s*$/); + }, + + childOf: function(element, ancestor) { + element = $(element), ancestor = $(ancestor); + while (element = element.parentNode) + if (element == ancestor) return true; + return false; + }, + + scrollTo: function(element) { + element = $(element); + var x = element.x ? element.x : element.offsetLeft, + y = element.y ? element.y : element.offsetTop; + window.scrollTo(x, y); + return element; + }, + + getStyle: function(element, style) { + element = $(element); + var value = element.style[style.camelize()]; + if (!value) { + if (document.defaultView && document.defaultView.getComputedStyle) { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css.getPropertyValue(style) : null; + } else if (element.currentStyle) { + value = element.currentStyle[style.camelize()]; + } + } + + if (window.opera && ['left', 'top', 'right', 'bottom'].include(style)) + if (Element.getStyle(element, 'position') == 'static') value = 'auto'; + + return value == 'auto' ? null : value; + }, + + setStyle: function(element, style) { + element = $(element); + for (var name in style) + element.style[name.camelize()] = style[name]; + return element; + }, + + getDimensions: function(element) { + element = $(element); + if (Element.getStyle(element, 'display') != 'none') + return {width: element.offsetWidth, height: element.offsetHeight}; + + // All *Width and *Height properties give 0 on elements with display none, + // so enable the element temporarily + var els = element.style; + var originalVisibility = els.visibility; + var originalPosition = els.position; + els.visibility = 'hidden'; + els.position = 'absolute'; + els.display = ''; + var originalWidth = element.clientWidth; + var originalHeight = element.clientHeight; + els.display = 'none'; + els.position = originalPosition; + els.visibility = originalVisibility; + return {width: originalWidth, height: originalHeight}; + }, + + makePositioned: function(element) { + element = $(element); + var pos = Element.getStyle(element, 'position'); + if (pos == 'static' || !pos) { + element._madePositioned = true; + element.style.position = 'relative'; + // Opera returns the offset relative to the positioning context, when an + // element is position relative but top and left have not been defined + if (window.opera) { + element.style.top = 0; + element.style.left = 0; + } + } + return element; + }, + + undoPositioned: function(element) { + element = $(element); + if (element._madePositioned) { + element._madePositioned = undefined; + element.style.position = + element.style.top = + element.style.left = + element.style.bottom = + element.style.right = ''; + } + return element; + }, + + makeClipping: function(element) { + element = $(element); + if (element._overflow) return; + element._overflow = element.style.overflow || 'auto'; + if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') + element.style.overflow = 'hidden'; + return element; + }, + + undoClipping: function(element) { + element = $(element); + if (!element._overflow) return; + element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; + element._overflow = null; + return element; + } +} + +// IE is missing .innerHTML support for TABLE-related elements +if(document.all){ + Element.Methods.update = function(element, html) { + element = $(element); + var tagName = element.tagName.toUpperCase(); + if (['THEAD','TBODY','TR','TD'].indexOf(tagName) > -1) { + var div = document.createElement('div'); + switch (tagName) { + case 'THEAD': + case 'TBODY': + div.innerHTML = '' + html.stripScripts() + '
    '; + depth = 2; + break; + case 'TR': + div.innerHTML = '' + html.stripScripts() + '
    '; + depth = 3; + break; + case 'TD': + div.innerHTML = '
    ' + html.stripScripts() + '
    '; + depth = 4; + } + $A(element.childNodes).each(function(node){ + element.removeChild(node) + }); + depth.times(function(){ div = div.firstChild }); + + $A(div.childNodes).each( + function(node){ element.appendChild(node) }); + } else { + element.innerHTML = html.stripScripts(); + } + setTimeout(function() {html.evalScripts()}, 10); + return element; + } +} + +Object.extend(Element, Element.Methods); + +var _nativeExtensions = false; + +if (!window.HTMLElement && /Konqueror|Safari|KHTML/.test(navigator.userAgent)) { + /* Emulate HTMLElement, HTMLFormElement, HTMLInputElement, HTMLTextAreaElement, + and HTMLSelectElement in Safari */ + ['', 'Form', 'Input', 'TextArea', 'Select'].each(function(tag) { + var klass = window['HTML' + tag + 'Element'] = {}; + klass.prototype = document.createElement(tag ? tag.toLowerCase() : 'div').__proto__; + }); +} + +Element.addMethods = function(methods) { + Object.extend(Element.Methods, methods || {}); + + function copy(methods, destination) { + var cache = Element.extend.cache; + for (var property in methods) { + var value = methods[property]; + destination[property] = cache.findOrStore(value); + } + } + + if (typeof HTMLElement != 'undefined') { + copy(Element.Methods, HTMLElement.prototype); + copy(Form.Methods, HTMLFormElement.prototype); + [HTMLInputElement, HTMLTextAreaElement, HTMLSelectElement].each(function(klass) { + copy(Form.Element.Methods, klass.prototype); + }); + _nativeExtensions = true; + } +} + +var Toggle = new Object(); +Toggle.display = Element.toggle; + +/*--------------------------------------------------------------------------*/ + +Abstract.Insertion = function(adjacency) { + this.adjacency = adjacency; +} + +Abstract.Insertion.prototype = { + initialize: function(element, content) { + this.element = $(element); + this.content = content.stripScripts(); + + if (this.adjacency && this.element.insertAdjacentHTML) { + try { + this.element.insertAdjacentHTML(this.adjacency, this.content); + } catch (e) { + var tagName = this.element.tagName.toLowerCase(); + if (tagName == 'tbody' || tagName == 'tr') { + this.insertContent(this.contentFromAnonymousTable()); + } else { + throw e; + } + } + } else { + this.range = this.element.ownerDocument.createRange(); + if (this.initializeRange) this.initializeRange(); + this.insertContent([this.range.createContextualFragment(this.content)]); + } + + setTimeout(function() {content.evalScripts()}, 10); + }, + + contentFromAnonymousTable: function() { + var div = document.createElement('div'); + div.innerHTML = '' + this.content + '
    '; + return $A(div.childNodes[0].childNodes[0].childNodes); + } +} + +var Insertion = new Object(); + +Insertion.Before = Class.create(); +Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), { + initializeRange: function() { + this.range.setStartBefore(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.parentNode.insertBefore(fragment, this.element); + }).bind(this)); + } +}); + +Insertion.Top = Class.create(); +Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), { + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(true); + }, + + insertContent: function(fragments) { + fragments.reverse(false).each((function(fragment) { + this.element.insertBefore(fragment, this.element.firstChild); + }).bind(this)); + } +}); + +Insertion.Bottom = Class.create(); +Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), { + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.appendChild(fragment); + }).bind(this)); + } +}); + +Insertion.After = Class.create(); +Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), { + initializeRange: function() { + this.range.setStartAfter(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.parentNode.insertBefore(fragment, + this.element.nextSibling); + }).bind(this)); + } +}); + +/*--------------------------------------------------------------------------*/ + +Element.ClassNames = Class.create(); +Element.ClassNames.prototype = { + initialize: function(element) { + this.element = $(element); + }, + + _each: function(iterator) { + this.element.className.split(/\s+/).select(function(name) { + return name.length > 0; + })._each(iterator); + }, + + set: function(className) { + this.element.className = className; + }, + + add: function(classNameToAdd) { + if (this.include(classNameToAdd)) return; + this.set(this.toArray().concat(classNameToAdd).join(' ')); + }, + + remove: function(classNameToRemove) { + if (!this.include(classNameToRemove)) return; + this.set(this.select(function(className) { + return className != classNameToRemove; + }).join(' ')); + }, + + toString: function() { + return this.toArray().join(' '); + } +} + +Object.extend(Element.ClassNames.prototype, Enumerable); +var Selector = Class.create(); +Selector.prototype = { + initialize: function(expression) { + this.params = {classNames: []}; + this.expression = expression.toString().strip(); + this.parseExpression(); + this.compileMatcher(); + }, + + parseExpression: function() { + function abort(message) { throw 'Parse error in selector: ' + message; } + + if (this.expression == '') abort('empty expression'); + + var params = this.params, expr = this.expression, match, modifier, clause, rest; + while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) { + params.attributes = params.attributes || []; + params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''}); + expr = match[1]; + } + + if (expr == '*') return this.params.wildcard = true; + + while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) { + modifier = match[1], clause = match[2], rest = match[3]; + switch (modifier) { + case '#': params.id = clause; break; + case '.': params.classNames.push(clause); break; + case '': + case undefined: params.tagName = clause.toUpperCase(); break; + default: abort(expr.inspect()); + } + expr = rest; + } + + if (expr.length > 0) abort(expr.inspect()); + }, + + buildMatchExpression: function() { + var params = this.params, conditions = [], clause; + + if (params.wildcard) + conditions.push('true'); + if (clause = params.id) + conditions.push('element.id == ' + clause.inspect()); + if (clause = params.tagName) + conditions.push('element.tagName.toUpperCase() == ' + clause.inspect()); + if ((clause = params.classNames).length > 0) + for (var i = 0; i < clause.length; i++) + conditions.push('Element.hasClassName(element, ' + clause[i].inspect() + ')'); + if (clause = params.attributes) { + clause.each(function(attribute) { + var value = 'element.getAttribute(' + attribute.name.inspect() + ')'; + var splitValueBy = function(delimiter) { + return value + ' && ' + value + '.split(' + delimiter.inspect() + ')'; + } + + switch (attribute.operator) { + case '=': conditions.push(value + ' == ' + attribute.value.inspect()); break; + case '~=': conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break; + case '|=': conditions.push( + splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect() + ); break; + case '!=': conditions.push(value + ' != ' + attribute.value.inspect()); break; + case '': + case undefined: conditions.push(value + ' != null'); break; + default: throw 'Unknown operator ' + attribute.operator + ' in selector'; + } + }); + } + + return conditions.join(' && '); + }, + + compileMatcher: function() { + this.match = new Function('element', 'if (!element.tagName) return false; \ + return ' + this.buildMatchExpression()); + }, + + findElements: function(scope) { + var element; + + if (element = $(this.params.id)) + if (this.match(element)) + if (!scope || Element.childOf(element, scope)) + return [element]; + + scope = (scope || document).getElementsByTagName(this.params.tagName || '*'); + + var results = []; + for (var i = 0; i < scope.length; i++) + if (this.match(element = scope[i])) + results.push(Element.extend(element)); + + return results; + }, + + toString: function() { + return this.expression; + } +} + +Object.extend(Selector, { + matchElements: function(elements, expression) { + var selector = new Selector(expression); + return elements.select(selector.match.bind(selector)); + }, + + findElement: function(elements, expression, index) { + if (typeof expression == 'number') index = expression, expression = false; + return Selector.matchElements(elements, expression || '*')[index || 0]; + }, + + findChildElements: function(element, expressions) { + return expressions.map(function(expression) { + return expression.strip().split(/\s+/).inject([null], function(results, expr) { + var selector = new Selector(expr); + return results.inject([], function(elements, result) { + return elements.concat(selector.findElements(result || element)); + }); + }); + }).flatten(); + } +}); + +function $$() { + return Selector.findChildElements(document, $A(arguments)); +} +var Form = { + reset: function(form) { + $(form).reset(); + return form; + } +}; + +Form.Methods = { + serialize: function(form) { + var elements = Form.getElements($(form)); + var queryComponents = new Array(); + + for (var i = 0; i < elements.length; i++) { + var queryComponent = Form.Element.serialize(elements[i]); + if (queryComponent) + queryComponents.push(queryComponent); + } + + return queryComponents.join('&'); + }, + + getElements: function(form) { + form = $(form); + var elements = new Array(); + + for (var tagName in Form.Element.Serializers) { + var tagElements = form.getElementsByTagName(tagName); + for (var j = 0; j < tagElements.length; j++) + elements.push(tagElements[j]); + } + return elements; + }, + + getInputs: function(form, typeName, name) { + form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) + return inputs; + + var matchingInputs = new Array(); + for (var i = 0; i < inputs.length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || + (name && input.name != name)) + continue; + matchingInputs.push(input); + } + + return matchingInputs; + }, + + disable: function(form) { + form = $(form); + var elements = Form.getElements(form); + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + element.blur(); + element.disabled = 'true'; + } + return form; + }, + + enable: function(form) { + form = $(form); + var elements = Form.getElements(form); + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + element.disabled = ''; + } + return form; + }, + + findFirstElement: function(form) { + return Form.getElements(form).find(function(element) { + return element.type != 'hidden' && !element.disabled && + ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); + }); + }, + + focusFirstElement: function(form) { + form = $(form); + Field.activate(Form.findFirstElement(form)); + return form; + } +} + +Object.extend(Form, Form.Methods); + +/*--------------------------------------------------------------------------*/ + +Form.Element = { + focus: function(element) { + $(element).focus(); + return element; + }, + + select: function(element) { + $(element).select(); + return element; + } +} + +Form.Element.Methods = { + serialize: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + var parameter = Form.Element.Serializers[method](element); + + if (parameter) { + var key = encodeURIComponent(parameter[0]); + if (key.length == 0) return; + + if (parameter[1].constructor != Array) + parameter[1] = [parameter[1]]; + + return parameter[1].map(function(value) { + return key + '=' + encodeURIComponent(value); + }).join('&'); + } + }, + + getValue: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + var parameter = Form.Element.Serializers[method](element); + + if (parameter) + return parameter[1]; + }, + + clear: function(element) { + $(element).value = ''; + return element; + }, + + present: function(element) { + return $(element).value != ''; + }, + + activate: function(element) { + element = $(element); + element.focus(); + if (element.select) + element.select(); + return element; + }, + + disable: function(element) { + element = $(element); + element.disabled = ''; + return element; + }, + + enable: function(element) { + element = $(element); + element.blur(); + element.disabled = 'true'; + return element; + } +} + +Object.extend(Form.Element, Form.Element.Methods); +var Field = Form.Element; + +/*--------------------------------------------------------------------------*/ + +Form.Element.Serializers = { + input: function(element) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element); + default: + return Form.Element.Serializers.textarea(element); + } + return false; + }, + + inputSelector: function(element) { + if (element.checked) + return [element.name, element.value]; + }, + + textarea: function(element) { + return [element.name, element.value]; + }, + + select: function(element) { + return Form.Element.Serializers[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + }, + + selectOne: function(element) { + var value = '', opt, index = element.selectedIndex; + if (index >= 0) { + opt = element.options[index]; + value = opt.value || opt.text; + } + return [element.name, value]; + }, + + selectMany: function(element) { + var value = []; + for (var i = 0; i < element.length; i++) { + var opt = element.options[i]; + if (opt.selected) + value.push(opt.value || opt.text); + } + return [element.name, value]; + } +} + +/*--------------------------------------------------------------------------*/ + +var $F = Form.Element.getValue; + +/*--------------------------------------------------------------------------*/ + +Abstract.TimedObserver = function() {} +Abstract.TimedObserver.prototype = { + initialize: function(element, frequency, callback) { + this.frequency = frequency; + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + this.registerCallback(); + }, + + registerCallback: function() { + setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + onTimerEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + } +} + +Form.Element.Observer = Class.create(); +Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(); +Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { + getValue: function() { + return Form.serialize(this.element); + } +}); + +/*--------------------------------------------------------------------------*/ + +Abstract.EventObserver = function() {} +Abstract.EventObserver.prototype = { + initialize: function(element, callback) { + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + if (this.element.tagName.toLowerCase() == 'form') + this.registerFormCallbacks(); + else + this.registerCallback(this.element); + }, + + onElementEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + }, + + registerFormCallbacks: function() { + var elements = Form.getElements(this.element); + for (var i = 0; i < elements.length; i++) + this.registerCallback(elements[i]); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + Event.observe(element, 'click', this.onElementEvent.bind(this)); + break; + default: + Event.observe(element, 'change', this.onElementEvent.bind(this)); + break; + } + } + } +} + +Form.Element.EventObserver = Class.create(); +Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.EventObserver = Class.create(); +Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.serialize(this.element); + } +}); +if (!window.Event) { + var Event = new Object(); +} + +Object.extend(Event, { + KEY_BACKSPACE: 8, + KEY_TAB: 9, + KEY_RETURN: 13, + KEY_ESC: 27, + KEY_LEFT: 37, + KEY_UP: 38, + KEY_RIGHT: 39, + KEY_DOWN: 40, + KEY_DELETE: 46, + KEY_HOME: 36, + KEY_END: 35, + KEY_PAGEUP: 33, + KEY_PAGEDOWN: 34, + + element: function(event) { + return event.target || event.srcElement; + }, + + isLeftClick: function(event) { + return (((event.which) && (event.which == 1)) || + ((event.button) && (event.button == 1))); + }, + + pointerX: function(event) { + return event.pageX || (event.clientX + + (document.documentElement.scrollLeft || document.body.scrollLeft)); + }, + + pointerY: function(event) { + return event.pageY || (event.clientY + + (document.documentElement.scrollTop || document.body.scrollTop)); + }, + + stop: function(event) { + if (event.preventDefault) { + event.preventDefault(); + event.stopPropagation(); + } else { + event.returnValue = false; + event.cancelBubble = true; + } + }, + + // find the first node with the given tagName, starting from the + // node the event was triggered on; traverses the DOM upwards + findElement: function(event, tagName) { + var element = Event.element(event); + while (element.parentNode && (!element.tagName || + (element.tagName.toUpperCase() != tagName.toUpperCase()))) + element = element.parentNode; + return element; + }, + + observers: false, + + _observeAndCache: function(element, name, observer, useCapture) { + if (!this.observers) this.observers = []; + if (element.addEventListener) { + this.observers.push([element, name, observer, useCapture]); + element.addEventListener(name, observer, useCapture); + } else if (element.attachEvent) { + this.observers.push([element, name, observer, useCapture]); + element.attachEvent('on' + name, observer); + } + }, + + unloadCache: function() { + if (!Event.observers) return; + for (var i = 0; i < Event.observers.length; i++) { + Event.stopObserving.apply(this, Event.observers[i]); + Event.observers[i][0] = null; + } + Event.observers = false; + }, + + observe: function(element, name, observer, useCapture) { + element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) + || element.attachEvent)) + name = 'keydown'; + + Event._observeAndCache(element, name, observer, useCapture); + }, + + stopObserving: function(element, name, observer, useCapture) { + element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) + || element.detachEvent)) + name = 'keydown'; + + if (element.removeEventListener) { + element.removeEventListener(name, observer, useCapture); + } else if (element.detachEvent) { + try { + element.detachEvent('on' + name, observer); + } catch (e) {} + } + } +}); + +/* prevent memory leaks in IE */ +if (navigator.appVersion.match(/\bMSIE\b/)) + Event.observe(window, 'unload', Event.unloadCache, false); +var Position = { + // set to true if needed, warning: firefox performance problems + // NOT neeeded for page scrolling, only if draggable contained in + // scrollable elements + includeScrollOffsets: false, + + // must be called before calling withinIncludingScrolloffset, every time the + // page is scrolled + prepare: function() { + this.deltaX = window.pageXOffset + || document.documentElement.scrollLeft + || document.body.scrollLeft + || 0; + this.deltaY = window.pageYOffset + || document.documentElement.scrollTop + || document.body.scrollTop + || 0; + }, + + realOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return [valueL, valueT]; + }, + + cumulativeOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return [valueL, valueT]; + }, + + positionedOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + p = Element.getStyle(element, 'position'); + if (p == 'relative' || p == 'absolute') break; + } + } while (element); + return [valueL, valueT]; + }, + + offsetParent: function(element) { + if (element.offsetParent) return element.offsetParent; + if (element == document.body) return element; + + while ((element = element.parentNode) && element != document.body) + if (Element.getStyle(element, 'position') != 'static') + return element; + + return document.body; + }, + + // caches x/y coordinate pair to use with overlap + within: function(element, x, y) { + if (this.includeScrollOffsets) + return this.withinIncludingScrolloffsets(element, x, y); + this.xcomp = x; + this.ycomp = y; + this.offset = this.cumulativeOffset(element); + + return (y >= this.offset[1] && + y < this.offset[1] + element.offsetHeight && + x >= this.offset[0] && + x < this.offset[0] + element.offsetWidth); + }, + + withinIncludingScrolloffsets: function(element, x, y) { + var offsetcache = this.realOffset(element); + + this.xcomp = x + offsetcache[0] - this.deltaX; + this.ycomp = y + offsetcache[1] - this.deltaY; + this.offset = this.cumulativeOffset(element); + + return (this.ycomp >= this.offset[1] && + this.ycomp < this.offset[1] + element.offsetHeight && + this.xcomp >= this.offset[0] && + this.xcomp < this.offset[0] + element.offsetWidth); + }, + + // within must be called directly before + overlap: function(mode, element) { + if (!mode) return 0; + if (mode == 'vertical') + return ((this.offset[1] + element.offsetHeight) - this.ycomp) / + element.offsetHeight; + if (mode == 'horizontal') + return ((this.offset[0] + element.offsetWidth) - this.xcomp) / + element.offsetWidth; + }, + + page: function(forElement) { + var valueT = 0, valueL = 0; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + + // Safari fix + if (element.offsetParent==document.body) + if (Element.getStyle(element,'position')=='absolute') break; + + } while (element = element.offsetParent); + + element = forElement; + do { + if (!window.opera || element.tagName=='BODY') { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } + } while (element = element.parentNode); + + return [valueL, valueT]; + }, + + clone: function(source, target) { + var options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, arguments[2] || {}) + + // find page position of source + source = $(source); + var p = Position.page(source); + + // find coordinate system to use + target = $(target); + var delta = [0, 0]; + var parent = null; + // delta [0,0] will do fine with position: fixed elements, + // position:absolute needs offsetParent deltas + if (Element.getStyle(target,'position') == 'absolute') { + parent = Position.offsetParent(target); + delta = Position.page(parent); + } + + // correct by body offsets (fixes Safari) + if (parent == document.body) { + delta[0] -= document.body.offsetLeft; + delta[1] -= document.body.offsetTop; + } + + // set position + if(options.setLeft) target.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; + if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; + if(options.setWidth) target.style.width = source.offsetWidth + 'px'; + if(options.setHeight) target.style.height = source.offsetHeight + 'px'; + }, + + absolutize: function(element) { + element = $(element); + if (element.style.position == 'absolute') return; + Position.prepare(); + + var offsets = Position.positionedOffset(element); + var top = offsets[1]; + var left = offsets[0]; + var width = element.clientWidth; + var height = element.clientHeight; + + element._originalLeft = left - parseFloat(element.style.left || 0); + element._originalTop = top - parseFloat(element.style.top || 0); + element._originalWidth = element.style.width; + element._originalHeight = element.style.height; + + element.style.position = 'absolute'; + element.style.top = top + 'px';; + element.style.left = left + 'px';; + element.style.width = width + 'px';; + element.style.height = height + 'px';; + }, + + relativize: function(element) { + element = $(element); + if (element.style.position == 'relative') return; + Position.prepare(); + + element.style.position = 'relative'; + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); + var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.height = element._originalHeight; + element.style.width = element._originalWidth; + } +} + +// Safari returns margins on body which is incorrect if the child is absolutely +// positioned. For performance reasons, redefine Position.cumulativeOffset for +// KHTML/WebKit only. +if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) { + Position.cumulativeOffset = function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; + + element = element.offsetParent; + } while (element); + + return [valueL, valueT]; + } +} + +Element.addMethods(); \ No newline at end of file diff --git a/Solar/App/Public/scripts/scriptaculous/builder.js b/Solar/App/Public/scripts/scriptaculous/builder.js index 0d00f6d2..97376210 100755 --- a/Solar/App/Public/scripts/scriptaculous/builder.js +++ b/Solar/App/Public/scripts/scriptaculous/builder.js @@ -1,20 +1,119 @@ -// From script.aculo.us 1.6.2, compressed by jsmin (http://www.crockford.com/javascript/jsmin.html). +// script.aculo.us builder.js v1.6.4, Wed Sep 06 11:30:58 CEST 2006 -var Builder={NODEMAP:{AREA:'map',CAPTION:'table',COL:'table',COLGROUP:'table',LEGEND:'fieldset',OPTGROUP:'select',OPTION:'select',PARAM:'object',TBODY:'table',TD:'table',TFOOT:'table',TH:'table',THEAD:'table',TR:'table'},node:function(elementName){elementName=elementName.toUpperCase();var parentTag=this.NODEMAP[elementName]||'div';var parentElement=document.createElement(parentTag);try{parentElement.innerHTML="<"+elementName+">";}catch(e){} -var element=parentElement.firstChild||null;if(element&&(element.tagName!=elementName)) -element=element.getElementsByTagName(elementName)[0];if(!element)element=document.createElement(elementName);if(!element)return;if(arguments[1]) -if(this._isStringOrNumber(arguments[1])||(arguments[1]instanceof Array)){this._children(element,arguments[1]);}else{var attrs=this._attributes(arguments[1]);if(attrs.length){try{parentElement.innerHTML="<"+elementName+" "+ -attrs+">";}catch(e){} -element=parentElement.firstChild||null;if(!element){element=document.createElement(elementName);for(attr in arguments[1]) -element[attr=='class'?'className':attr]=arguments[1][attr];} -if(element.tagName!=elementName) -element=parentElement.getElementsByTagName(elementName)[0];}} -if(arguments[2]) -this._children(element,arguments[2]);return element;},_text:function(text){return document.createTextNode(text);},_attributes:function(attributes){var attrs=[];for(attribute in attributes) -attrs.push((attribute=='className'?'class':attribute)+'="'+attributes[attribute].toString().escapeHTML()+'"');return attrs.join(" ");},_children:function(element,children){if(typeof children=='object'){children.flatten().each(function(e){if(typeof e=='object') -element.appendChild(e) -else -if(Builder._isStringOrNumber(e)) -element.appendChild(Builder._text(e));});}else -if(Builder._isStringOrNumber(children)) -element.appendChild(Builder._text(children));},_isStringOrNumber:function(param){return(typeof param=='string'||typeof param=='number');}} \ No newline at end of file +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// +// See scriptaculous.js for full license. + +var Builder = { + NODEMAP: { + AREA: 'map', + CAPTION: 'table', + COL: 'table', + COLGROUP: 'table', + LEGEND: 'fieldset', + OPTGROUP: 'select', + OPTION: 'select', + PARAM: 'object', + TBODY: 'table', + TD: 'table', + TFOOT: 'table', + TH: 'table', + THEAD: 'table', + TR: 'table' + }, + // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken, + // due to a Firefox bug + node: function(elementName) { + elementName = elementName.toUpperCase(); + + // try innerHTML approach + var parentTag = this.NODEMAP[elementName] || 'div'; + var parentElement = document.createElement(parentTag); + try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707 + parentElement.innerHTML = "<" + elementName + ">"; + } catch(e) {} + var element = parentElement.firstChild || null; + + // see if browser added wrapping tags + if(element && (element.tagName != elementName)) + element = element.getElementsByTagName(elementName)[0]; + + // fallback to createElement approach + if(!element) element = document.createElement(elementName); + + // abort if nothing could be created + if(!element) return; + + // attributes (or text) + if(arguments[1]) + if(this._isStringOrNumber(arguments[1]) || + (arguments[1] instanceof Array)) { + this._children(element, arguments[1]); + } else { + var attrs = this._attributes(arguments[1]); + if(attrs.length) { + try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707 + parentElement.innerHTML = "<" +elementName + " " + + attrs + ">"; + } catch(e) {} + element = parentElement.firstChild || null; + // workaround firefox 1.0.X bug + if(!element) { + element = document.createElement(elementName); + for(attr in arguments[1]) + element[attr == 'class' ? 'className' : attr] = arguments[1][attr]; + } + if(element.tagName != elementName) + element = parentElement.getElementsByTagName(elementName)[0]; + } + } + + // text, or array of children + if(arguments[2]) + this._children(element, arguments[2]); + + return element; + }, + _text: function(text) { + return document.createTextNode(text); + }, + _attributes: function(attributes) { + var attrs = []; + for(attribute in attributes) + attrs.push((attribute=='className' ? 'class' : attribute) + + '="' + attributes[attribute].toString().escapeHTML() + '"'); + return attrs.join(" "); + }, + _children: function(element, children) { + if(typeof children=='object') { // array can hold nodes and text + children.flatten().each( function(e) { + if(typeof e=='object') + element.appendChild(e) + else + if(Builder._isStringOrNumber(e)) + element.appendChild(Builder._text(e)); + }); + } else + if(Builder._isStringOrNumber(children)) + element.appendChild(Builder._text(children)); + }, + _isStringOrNumber: function(param) { + return(typeof param=='string' || typeof param=='number'); + }, + dump: function(scope) { + if(typeof scope != 'object' && typeof scope != 'function') scope = window; //global scope + + var tags = ("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY " + + "BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET " + + "FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX "+ + "KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P "+ + "PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD "+ + "TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/); + + tags.each( function(tag){ + scope[tag] = function() { + return Builder.node.apply(Builder, [tag].concat($A(arguments))); + } + }); + } +} \ No newline at end of file diff --git a/Solar/App/Public/scripts/scriptaculous/controls.js b/Solar/App/Public/scripts/scriptaculous/controls.js index e71b1210..bb4186df 100755 --- a/Solar/App/Public/scripts/scriptaculous/controls.js +++ b/Solar/App/Public/scripts/scriptaculous/controls.js @@ -1,60 +1,833 @@ -// From script.aculo.us 1.6.2, compressed by jsmin (http://www.crockford.com/javascript/jsmin.html). - -if(typeof Effect=='undefined') -throw("controls.js requires including script.aculo.us' effects.js library");var Autocompleter={} -Autocompleter.Base=function(){};Autocompleter.Base.prototype={baseInitialize:function(element,update,options){this.element=$(element);this.update=$(update);this.hasFocus=false;this.changed=false;this.active=false;this.index=0;this.entryCount=0;if(this.setOptions) -this.setOptions(options);else -this.options=options||{};this.options.paramName=this.options.paramName||this.element.name;this.options.tokens=this.options.tokens||[];this.options.frequency=this.options.frequency||0.4;this.options.minChars=this.options.minChars||1;this.options.onShow=this.options.onShow||function(element,update){if(!update.style.position||update.style.position=='absolute'){update.style.position='absolute';Position.clone(element,update,{setHeight:false,offsetTop:element.offsetHeight});} -Effect.Appear(update,{duration:0.15});};this.options.onHide=this.options.onHide||function(element,update){new Effect.Fade(update,{duration:0.15})};if(typeof(this.options.tokens)=='string') -this.options.tokens=new Array(this.options.tokens);this.observer=null;this.element.setAttribute('autocomplete','off');Element.hide(this.update);Event.observe(this.element,"blur",this.onBlur.bindAsEventListener(this));Event.observe(this.element,"keypress",this.onKeyPress.bindAsEventListener(this));},show:function(){if(Element.getStyle(this.update,'display')=='none')this.options.onShow(this.element,this.update);if(!this.iefix&&(navigator.appVersion.indexOf('MSIE')>0)&&(navigator.userAgent.indexOf('Opera')<0)&&(Element.getStyle(this.update,'position')=='absolute')){new Insertion.After(this.update,'');this.iefix=$(this.update.id+'_iefix');} -if(this.iefix)setTimeout(this.fixIEOverlapping.bind(this),50);},fixIEOverlapping:function(){Position.clone(this.update,this.iefix,{setTop:(!this.update.style.height)});this.iefix.style.zIndex=1;this.update.style.zIndex=2;Element.show(this.iefix);},hide:function(){this.stopIndicator();if(Element.getStyle(this.update,'display')!='none')this.options.onHide(this.element,this.update);if(this.iefix)Element.hide(this.iefix);},startIndicator:function(){if(this.options.indicator)Element.show(this.options.indicator);},stopIndicator:function(){if(this.options.indicator)Element.hide(this.options.indicator);},onKeyPress:function(event){if(this.active) -switch(event.keyCode){case Event.KEY_TAB:case Event.KEY_RETURN:this.selectEntry();Event.stop(event);case Event.KEY_ESC:this.hide();this.active=false;Event.stop(event);return;case Event.KEY_LEFT:case Event.KEY_RIGHT:return;case Event.KEY_UP:this.markPrevious();this.render();if(navigator.appVersion.indexOf('AppleWebKit')>0)Event.stop(event);return;case Event.KEY_DOWN:this.markNext();this.render();if(navigator.appVersion.indexOf('AppleWebKit')>0)Event.stop(event);return;} -else -if(event.keyCode==Event.KEY_TAB||event.keyCode==Event.KEY_RETURN||(navigator.appVersion.indexOf('AppleWebKit')>0&&event.keyCode==0))return;this.changed=true;this.hasFocus=true;if(this.observer)clearTimeout(this.observer);this.observer=setTimeout(this.onObserverEvent.bind(this),this.options.frequency*1000);},activate:function(){this.changed=false;this.hasFocus=true;this.getUpdatedChoices();},onHover:function(event){var element=Event.findElement(event,'LI');if(this.index!=element.autocompleteIndex) -{this.index=element.autocompleteIndex;this.render();} -Event.stop(event);},onClick:function(event){var element=Event.findElement(event,'LI');this.index=element.autocompleteIndex;this.selectEntry();this.hide();},onBlur:function(event){setTimeout(this.hide.bind(this),250);this.hasFocus=false;this.active=false;},render:function(){if(this.entryCount>0){for(var i=0;i0)this.index-- -else this.index=this.entryCount-1;this.getEntry(this.index).scrollIntoView(true);},markNext:function(){if(this.index0)value=Element.collectTextNodes(nodes[0],this.options.select);}else -value=Element.collectTextNodesIgnoreClass(selectedElement,'informal');var lastTokenPos=this.findLastToken();if(lastTokenPos!=-1){var newValue=this.element.value.substr(0,lastTokenPos+1);var whitespace=this.element.value.substr(lastTokenPos+1).match(/^\s+/);if(whitespace) -newValue+=whitespace[0];this.element.value=newValue+value;}else{this.element.value=value;} -this.element.focus();if(this.options.afterUpdateElement) -this.options.afterUpdateElement(this.element,selectedElement);},updateChoices:function(choices){if(!this.changed&&this.hasFocus){this.update.innerHTML=choices;Element.cleanWhitespace(this.update);Element.cleanWhitespace(this.update.firstChild);if(this.update.firstChild&&this.update.firstChild.childNodes){this.entryCount=this.update.firstChild.childNodes.length;for(var i=0;i=this.options.minChars){this.startIndicator();this.getUpdatedChoices();}else{this.active=false;this.hide();}},getToken:function(){var tokenPos=this.findLastToken();if(tokenPos!=-1) -var ret=this.element.value.substr(tokenPos+1).replace(/^\s+/,'').replace(/\s+$/,'');else -var ret=this.element.value;return/\n/.test(ret)?'':ret;},findLastToken:function(){var lastTokenPos=-1;for(var i=0;ilastTokenPos) -lastTokenPos=thisTokenPos;} -return lastTokenPos;}} -Ajax.Autocompleter=Class.create();Object.extend(Object.extend(Ajax.Autocompleter.prototype,Autocompleter.Base.prototype),{initialize:function(element,update,url,options){this.baseInitialize(element,update,options);this.options.asynchronous=true;this.options.onComplete=this.onComplete.bind(this);this.options.defaultParams=this.options.parameters||null;this.url=url;},getUpdatedChoices:function(){entry=encodeURIComponent(this.options.paramName)+'='+ -encodeURIComponent(this.getToken());this.options.parameters=this.options.callback?this.options.callback(this.element,entry):entry;if(this.options.defaultParams) -this.options.parameters+='&'+this.options.defaultParams;new Ajax.Request(this.url,this.options);},onComplete:function(request){this.updateChoices(request.responseText);}});Autocompleter.Local=Class.create();Autocompleter.Local.prototype=Object.extend(new Autocompleter.Base(),{initialize:function(element,update,array,options){this.baseInitialize(element,update,options);this.options.array=array;},getUpdatedChoices:function(){this.updateChoices(this.options.selector(this));},setOptions:function(options){this.options=Object.extend({choices:10,partialSearch:true,partialChars:2,ignoreCase:true,fullSearch:false,selector:function(instance){var ret=[];var partial=[];var entry=instance.getToken();var count=0;for(var i=0;i"+elem.substr(0,entry.length)+""+ -elem.substr(entry.length)+"");break;}else if(entry.length>=instance.options.partialChars&&instance.options.partialSearch&&foundPos!=-1){if(instance.options.fullSearch||/\s/.test(elem.substr(foundPos-1,1))){partial.push("

  • "+elem.substr(0,foundPos)+""+ -elem.substr(foundPos,entry.length)+""+elem.substr(foundPos+entry.length)+"
  • ");break;}} -foundPos=instance.options.ignoreCase?elem.toLowerCase().indexOf(entry.toLowerCase(),foundPos+1):elem.indexOf(entry,foundPos+1);}} -if(partial.length) -ret=ret.concat(partial.slice(0,instance.options.choices-ret.length)) -return"
      "+ret.join('')+"
    ";}},options||{});}});Field.scrollFreeActivate=function(field){setTimeout(function(){Field.activate(field);},1);} -Ajax.InPlaceEditor=Class.create();Ajax.InPlaceEditor.defaultHighlightColor="#FFFF99";Ajax.InPlaceEditor.prototype={initialize:function(element,url,options){this.url=url;this.element=$(element);this.options=Object.extend({okButton:true,okText:"ok",cancelLink:true,cancelText:"cancel",savingText:"Saving...",clickToEditText:"Click to edit",okText:"ok",rows:1,onComplete:function(transport,element){new Effect.Highlight(element,{startcolor:this.options.highlightcolor});},onFailure:function(transport){alert("Error communicating with the server: "+transport.responseText.stripTags());},callback:function(form){return Form.serialize(form);},handleLineBreaks:true,loadingText:'Loading...',savingClassName:'inplaceeditor-saving',loadingClassName:'inplaceeditor-loading',formClassName:'inplaceeditor-form',highlightcolor:Ajax.InPlaceEditor.defaultHighlightColor,highlightendcolor:"#FFFFFF",externalControl:null,submitOnBlur:false,ajaxOptions:{},evalScripts:false},options||{});if(!this.options.formId&&this.element.id){this.options.formId=this.element.id+"-inplaceeditor";if($(this.options.formId)){this.options.formId=null;}} -if(this.options.externalControl){this.options.externalControl=$(this.options.externalControl);} -this.originalBackground=Element.getStyle(this.element,'background-color');if(!this.originalBackground){this.originalBackground="transparent";} -this.element.title=this.options.clickToEditText;this.onclickListener=this.enterEditMode.bindAsEventListener(this);this.mouseoverListener=this.enterHover.bindAsEventListener(this);this.mouseoutListener=this.leaveHover.bindAsEventListener(this);Event.observe(this.element,'click',this.onclickListener);Event.observe(this.element,'mouseover',this.mouseoverListener);Event.observe(this.element,'mouseout',this.mouseoutListener);if(this.options.externalControl){Event.observe(this.options.externalControl,'click',this.onclickListener);Event.observe(this.options.externalControl,'mouseover',this.mouseoverListener);Event.observe(this.options.externalControl,'mouseout',this.mouseoutListener);}},enterEditMode:function(evt){if(this.saving)return;if(this.editing)return;this.editing=true;this.onEnterEditMode();if(this.options.externalControl){Element.hide(this.options.externalControl);} -Element.hide(this.element);this.createForm();this.element.parentNode.insertBefore(this.form,this.element);if(!this.options.loadTextURL)Field.scrollFreeActivate(this.editField);if(evt){Event.stop(evt);} -return false;},createForm:function(){this.form=document.createElement("form");this.form.id=this.options.formId;Element.addClassName(this.form,this.options.formClassName) -this.form.onsubmit=this.onSubmit.bind(this);this.createEditField();if(this.options.textarea){var br=document.createElement("br");this.form.appendChild(br);} -if(this.options.okButton){okButton=document.createElement("input");okButton.type="submit";okButton.value=this.options.okText;okButton.className='editor_ok_button';this.form.appendChild(okButton);} -if(this.options.cancelLink){cancelLink=document.createElement("a");cancelLink.href="#";cancelLink.appendChild(document.createTextNode(this.options.cancelText));cancelLink.onclick=this.onclickCancel.bind(this);cancelLink.className='editor_cancel';this.form.appendChild(cancelLink);}},hasHTMLLineBreaks:function(string){if(!this.options.handleLineBreaks)return false;return string.match(/
    /i);},convertHTMLLineBreaks:function(string){return string.replace(/
    /gi,"\n").replace(//gi,"\n").replace(/<\/p>/gi,"\n").replace(/

    /gi,"");},createEditField:function(){var text;if(this.options.loadTextURL){text=this.options.loadingText;}else{text=this.getText();} -var obj=this;if(this.options.rows==1&&!this.hasHTMLLineBreaks(text)){this.options.textarea=false;var textField=document.createElement("input");textField.obj=this;textField.type="text";textField.name="value";textField.value=text;textField.style.backgroundColor=this.options.highlightcolor;textField.className='editor_field';var size=this.options.size||this.options.cols||0;if(size!=0)textField.size=size;if(this.options.submitOnBlur) -textField.onblur=this.onSubmit.bind(this);this.editField=textField;}else{this.options.textarea=true;var textArea=document.createElement("textarea");textArea.obj=this;textArea.name="value";textArea.value=this.convertHTMLLineBreaks(text);textArea.rows=this.options.rows;textArea.cols=this.options.cols||40;textArea.className='editor_field';if(this.options.submitOnBlur) -textArea.onblur=this.onSubmit.bind(this);this.editField=textArea;} -if(this.options.loadTextURL){this.loadExternalText();} -this.form.appendChild(this.editField);},getText:function(){return this.element.innerHTML;},loadExternalText:function(){Element.addClassName(this.form,this.options.loadingClassName);this.editField.disabled=true;new Ajax.Request(this.options.loadTextURL,Object.extend({asynchronous:true,onComplete:this.onLoadedExternalText.bind(this)},this.options.ajaxOptions));},onLoadedExternalText:function(transport){Element.removeClassName(this.form,this.options.loadingClassName);this.editField.disabled=false;this.editField.value=transport.responseText.stripTags();Field.scrollFreeActivate(this.editField);},onclickCancel:function(){this.onComplete();this.leaveEditMode();return false;},onFailure:function(transport){this.options.onFailure(transport);if(this.oldInnerHTML){this.element.innerHTML=this.oldInnerHTML;this.oldInnerHTML=null;} -return false;},onSubmit:function(){var form=this.form;var value=this.editField.value;this.onLoading();if(this.options.evalScripts){new Ajax.Request(this.url,Object.extend({parameters:this.options.callback(form,value),onComplete:this.onComplete.bind(this),onFailure:this.onFailure.bind(this),asynchronous:true,evalScripts:true},this.options.ajaxOptions));}else{new Ajax.Updater({success:this.element,failure:null},this.url,Object.extend({parameters:this.options.callback(form,value),onComplete:this.onComplete.bind(this),onFailure:this.onFailure.bind(this)},this.options.ajaxOptions));} -if(arguments.length>1){Event.stop(arguments[0]);} -return false;},onLoading:function(){this.saving=true;this.removeForm();this.leaveHover();this.showSaving();},showSaving:function(){this.oldInnerHTML=this.element.innerHTML;this.element.innerHTML=this.options.savingText;Element.addClassName(this.element,this.options.savingClassName);this.element.style.backgroundColor=this.originalBackground;Element.show(this.element);},removeForm:function(){if(this.form){if(this.form.parentNode)Element.remove(this.form);this.form=null;}},enterHover:function(){if(this.saving)return;this.element.style.backgroundColor=this.options.highlightcolor;if(this.effect){this.effect.cancel();} -Element.addClassName(this.element,this.options.hoverClassName)},leaveHover:function(){if(this.options.backgroundColor){this.element.style.backgroundColor=this.oldBackground;} -Element.removeClassName(this.element,this.options.hoverClassName) -if(this.saving)return;this.effect=new Effect.Highlight(this.element,{startcolor:this.options.highlightcolor,endcolor:this.options.highlightendcolor,restorecolor:this.originalBackground});},leaveEditMode:function(){Element.removeClassName(this.element,this.options.savingClassName);this.removeForm();this.leaveHover();this.element.style.backgroundColor=this.originalBackground;Element.show(this.element);if(this.options.externalControl){Element.show(this.options.externalControl);} -this.editing=false;this.saving=false;this.oldInnerHTML=null;this.onLeaveEditMode();},onComplete:function(transport){this.leaveEditMode();this.options.onComplete.bind(this)(transport,this.element);},onEnterEditMode:function(){},onLeaveEditMode:function(){},dispose:function(){if(this.oldInnerHTML){this.element.innerHTML=this.oldInnerHTML;} -this.leaveEditMode();Event.stopObserving(this.element,'click',this.onclickListener);Event.stopObserving(this.element,'mouseover',this.mouseoverListener);Event.stopObserving(this.element,'mouseout',this.mouseoutListener);if(this.options.externalControl){Event.stopObserving(this.options.externalControl,'click',this.onclickListener);Event.stopObserving(this.options.externalControl,'mouseover',this.mouseoverListener);Event.stopObserving(this.options.externalControl,'mouseout',this.mouseoutListener);}}};Ajax.InPlaceCollectionEditor=Class.create();Object.extend(Ajax.InPlaceCollectionEditor.prototype,Ajax.InPlaceEditor.prototype);Object.extend(Ajax.InPlaceCollectionEditor.prototype,{createEditField:function(){if(!this.cached_selectTag){var selectTag=document.createElement("select");var collection=this.options.collection||[];var optionTag;collection.each(function(e,i){optionTag=document.createElement("option");optionTag.value=(e instanceof Array)?e[0]:e;if(this.options.value==optionTag.value)optionTag.selected=true;optionTag.appendChild(document.createTextNode((e instanceof Array)?e[1]:e));selectTag.appendChild(optionTag);}.bind(this));this.cached_selectTag=selectTag;} -this.editField=this.cached_selectTag;if(this.options.loadTextURL)this.loadExternalText();this.form.appendChild(this.editField);this.options.callback=function(form,value){return"value="+encodeURIComponent(value);}}});Form.Element.DelayedObserver=Class.create();Form.Element.DelayedObserver.prototype={initialize:function(element,delay,callback){this.delay=delay||0.5;this.element=$(element);this.callback=callback;this.timer=null;this.lastValue=$F(this.element);Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));},delayedListener:function(event){if(this.lastValue==$F(this.element))return;if(this.timer)clearTimeout(this.timer);this.timer=setTimeout(this.onTimerEvent.bind(this),this.delay*1000);this.lastValue=$F(this.element);},onTimerEvent:function(){this.timer=null;this.callback(this.element,$F(this.element));}}; \ No newline at end of file +// script.aculo.us controls.js v1.6.4, Wed Sep 06 11:30:58 CEST 2006 + +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan) +// (c) 2005 Jon Tirsen (http://www.tirsen.com) +// Contributors: +// Richard Livsey +// Rahul Bhargava +// Rob Wills +// +// See scriptaculous.js for full license. + +// Autocompleter.Base handles all the autocompletion functionality +// that's independent of the data source for autocompletion. This +// includes drawing the autocompletion menu, observing keyboard +// and mouse events, and similar. +// +// Specific autocompleters need to provide, at the very least, +// a getUpdatedChoices function that will be invoked every time +// the text inside the monitored textbox changes. This method +// should get the text for which to provide autocompletion by +// invoking this.getToken(), NOT by directly accessing +// this.element.value. This is to allow incremental tokenized +// autocompletion. Specific auto-completion logic (AJAX, etc) +// belongs in getUpdatedChoices. +// +// Tokenized incremental autocompletion is enabled automatically +// when an autocompleter is instantiated with the 'tokens' option +// in the options parameter, e.g.: +// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); +// will incrementally autocomplete with a comma as the token. +// Additionally, ',' in the above example can be replaced with +// a token array, e.g. { tokens: [',', '\n'] } which +// enables autocompletion on multiple tokens. This is most +// useful when one of the tokens is \n (a newline), as it +// allows smart autocompletion after linebreaks. + +if(typeof Effect == 'undefined') + throw("controls.js requires including script.aculo.us' effects.js library"); + +var Autocompleter = {} +Autocompleter.Base = function() {}; +Autocompleter.Base.prototype = { + baseInitialize: function(element, update, options) { + this.element = $(element); + this.update = $(update); + this.hasFocus = false; + this.changed = false; + this.active = false; + this.index = 0; + this.entryCount = 0; + + if(this.setOptions) + this.setOptions(options); + else + this.options = options || {}; + + this.options.paramName = this.options.paramName || this.element.name; + this.options.tokens = this.options.tokens || []; + this.options.frequency = this.options.frequency || 0.4; + this.options.minChars = this.options.minChars || 1; + this.options.onShow = this.options.onShow || + function(element, update){ + if(!update.style.position || update.style.position=='absolute') { + update.style.position = 'absolute'; + Position.clone(element, update, { + setHeight: false, + offsetTop: element.offsetHeight + }); + } + Effect.Appear(update,{duration:0.15}); + }; + this.options.onHide = this.options.onHide || + function(element, update){ new Effect.Fade(update,{duration:0.15}) }; + + if(typeof(this.options.tokens) == 'string') + this.options.tokens = new Array(this.options.tokens); + + this.observer = null; + + this.element.setAttribute('autocomplete','off'); + + Element.hide(this.update); + + Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this)); + Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this)); + }, + + show: function() { + if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); + if(!this.iefix && + (navigator.appVersion.indexOf('MSIE')>0) && + (navigator.userAgent.indexOf('Opera')<0) && + (Element.getStyle(this.update, 'position')=='absolute')) { + new Insertion.After(this.update, + ''); + this.iefix = $(this.update.id+'_iefix'); + } + if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); + }, + + fixIEOverlapping: function() { + Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)}); + this.iefix.style.zIndex = 1; + this.update.style.zIndex = 2; + Element.show(this.iefix); + }, + + hide: function() { + this.stopIndicator(); + if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); + if(this.iefix) Element.hide(this.iefix); + }, + + startIndicator: function() { + if(this.options.indicator) Element.show(this.options.indicator); + }, + + stopIndicator: function() { + if(this.options.indicator) Element.hide(this.options.indicator); + }, + + onKeyPress: function(event) { + if(this.active) + switch(event.keyCode) { + case Event.KEY_TAB: + case Event.KEY_RETURN: + this.selectEntry(); + Event.stop(event); + case Event.KEY_ESC: + this.hide(); + this.active = false; + Event.stop(event); + return; + case Event.KEY_LEFT: + case Event.KEY_RIGHT: + return; + case Event.KEY_UP: + this.markPrevious(); + this.render(); + if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); + return; + case Event.KEY_DOWN: + this.markNext(); + this.render(); + if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); + return; + } + else + if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || + (navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0)) return; + + this.changed = true; + this.hasFocus = true; + + if(this.observer) clearTimeout(this.observer); + this.observer = + setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); + }, + + activate: function() { + this.changed = false; + this.hasFocus = true; + this.getUpdatedChoices(); + }, + + onHover: function(event) { + var element = Event.findElement(event, 'LI'); + if(this.index != element.autocompleteIndex) + { + this.index = element.autocompleteIndex; + this.render(); + } + Event.stop(event); + }, + + onClick: function(event) { + var element = Event.findElement(event, 'LI'); + this.index = element.autocompleteIndex; + this.selectEntry(); + this.hide(); + }, + + onBlur: function(event) { + // needed to make click events working + setTimeout(this.hide.bind(this), 250); + this.hasFocus = false; + this.active = false; + }, + + render: function() { + if(this.entryCount > 0) { + for (var i = 0; i < this.entryCount; i++) + this.index==i ? + Element.addClassName(this.getEntry(i),"selected") : + Element.removeClassName(this.getEntry(i),"selected"); + + if(this.hasFocus) { + this.show(); + this.active = true; + } + } else { + this.active = false; + this.hide(); + } + }, + + markPrevious: function() { + if(this.index > 0) this.index-- + else this.index = this.entryCount-1; + this.getEntry(this.index).scrollIntoView(true); + }, + + markNext: function() { + if(this.index < this.entryCount-1) this.index++ + else this.index = 0; + this.getEntry(this.index).scrollIntoView(false); + }, + + getEntry: function(index) { + return this.update.firstChild.childNodes[index]; + }, + + getCurrentEntry: function() { + return this.getEntry(this.index); + }, + + selectEntry: function() { + this.active = false; + this.updateElement(this.getCurrentEntry()); + }, + + updateElement: function(selectedElement) { + if (this.options.updateElement) { + this.options.updateElement(selectedElement); + return; + } + var value = ''; + if (this.options.select) { + var nodes = document.getElementsByClassName(this.options.select, selectedElement) || []; + if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); + } else + value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); + + var lastTokenPos = this.findLastToken(); + if (lastTokenPos != -1) { + var newValue = this.element.value.substr(0, lastTokenPos + 1); + var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/); + if (whitespace) + newValue += whitespace[0]; + this.element.value = newValue + value; + } else { + this.element.value = value; + } + this.element.focus(); + + if (this.options.afterUpdateElement) + this.options.afterUpdateElement(this.element, selectedElement); + }, + + updateChoices: function(choices) { + if(!this.changed && this.hasFocus) { + this.update.innerHTML = choices; + Element.cleanWhitespace(this.update); + Element.cleanWhitespace(this.update.firstChild); + + if(this.update.firstChild && this.update.firstChild.childNodes) { + this.entryCount = + this.update.firstChild.childNodes.length; + for (var i = 0; i < this.entryCount; i++) { + var entry = this.getEntry(i); + entry.autocompleteIndex = i; + this.addObservers(entry); + } + } else { + this.entryCount = 0; + } + + this.stopIndicator(); + this.index = 0; + + if(this.entryCount==1 && this.options.autoSelect) { + this.selectEntry(); + this.hide(); + } else { + this.render(); + } + } + }, + + addObservers: function(element) { + Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); + Event.observe(element, "click", this.onClick.bindAsEventListener(this)); + }, + + onObserverEvent: function() { + this.changed = false; + if(this.getToken().length>=this.options.minChars) { + this.startIndicator(); + this.getUpdatedChoices(); + } else { + this.active = false; + this.hide(); + } + }, + + getToken: function() { + var tokenPos = this.findLastToken(); + if (tokenPos != -1) + var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,''); + else + var ret = this.element.value; + + return /\n/.test(ret) ? '' : ret; + }, + + findLastToken: function() { + var lastTokenPos = -1; + + for (var i=0; i lastTokenPos) + lastTokenPos = thisTokenPos; + } + return lastTokenPos; + } +} + +Ajax.Autocompleter = Class.create(); +Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), { + initialize: function(element, update, url, options) { + this.baseInitialize(element, update, options); + this.options.asynchronous = true; + this.options.onComplete = this.onComplete.bind(this); + this.options.defaultParams = this.options.parameters || null; + this.url = url; + }, + + getUpdatedChoices: function() { + entry = encodeURIComponent(this.options.paramName) + '=' + + encodeURIComponent(this.getToken()); + + this.options.parameters = this.options.callback ? + this.options.callback(this.element, entry) : entry; + + if(this.options.defaultParams) + this.options.parameters += '&' + this.options.defaultParams; + + new Ajax.Request(this.url, this.options); + }, + + onComplete: function(request) { + this.updateChoices(request.responseText); + } + +}); + +// The local array autocompleter. Used when you'd prefer to +// inject an array of autocompletion options into the page, rather +// than sending out Ajax queries, which can be quite slow sometimes. +// +// The constructor takes four parameters. The first two are, as usual, +// the id of the monitored textbox, and id of the autocompletion menu. +// The third is the array you want to autocomplete from, and the fourth +// is the options block. +// +// Extra local autocompletion options: +// - choices - How many autocompletion choices to offer +// +// - partialSearch - If false, the autocompleter will match entered +// text only at the beginning of strings in the +// autocomplete array. Defaults to true, which will +// match text at the beginning of any *word* in the +// strings in the autocomplete array. If you want to +// search anywhere in the string, additionally set +// the option fullSearch to true (default: off). +// +// - fullSsearch - Search anywhere in autocomplete array strings. +// +// - partialChars - How many characters to enter before triggering +// a partial match (unlike minChars, which defines +// how many characters are required to do any match +// at all). Defaults to 2. +// +// - ignoreCase - Whether to ignore case when autocompleting. +// Defaults to true. +// +// It's possible to pass in a custom function as the 'selector' +// option, if you prefer to write your own autocompletion logic. +// In that case, the other options above will not apply unless +// you support them. + +Autocompleter.Local = Class.create(); +Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), { + initialize: function(element, update, array, options) { + this.baseInitialize(element, update, options); + this.options.array = array; + }, + + getUpdatedChoices: function() { + this.updateChoices(this.options.selector(this)); + }, + + setOptions: function(options) { + this.options = Object.extend({ + choices: 10, + partialSearch: true, + partialChars: 2, + ignoreCase: true, + fullSearch: false, + selector: function(instance) { + var ret = []; // Beginning matches + var partial = []; // Inside matches + var entry = instance.getToken(); + var count = 0; + + for (var i = 0; i < instance.options.array.length && + ret.length < instance.options.choices ; i++) { + + var elem = instance.options.array[i]; + var foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase()) : + elem.indexOf(entry); + + while (foundPos != -1) { + if (foundPos == 0 && elem.length != entry.length) { + ret.push("

  • " + elem.substr(0, entry.length) + "" + + elem.substr(entry.length) + "
  • "); + break; + } else if (entry.length >= instance.options.partialChars && + instance.options.partialSearch && foundPos != -1) { + if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { + partial.push("
  • " + elem.substr(0, foundPos) + "" + + elem.substr(foundPos, entry.length) + "" + elem.substr( + foundPos + entry.length) + "
  • "); + break; + } + } + + foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : + elem.indexOf(entry, foundPos + 1); + + } + } + if (partial.length) + ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)) + return "
      " + ret.join('') + "
    "; + } + }, options || {}); + } +}); + +// AJAX in-place editor +// +// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor + +// Use this if you notice weird scrolling problems on some browsers, +// the DOM might be a bit confused when this gets called so do this +// waits 1 ms (with setTimeout) until it does the activation +Field.scrollFreeActivate = function(field) { + setTimeout(function() { + Field.activate(field); + }, 1); +} + +Ajax.InPlaceEditor = Class.create(); +Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99"; +Ajax.InPlaceEditor.prototype = { + initialize: function(element, url, options) { + this.url = url; + this.element = $(element); + + this.options = Object.extend({ + okButton: true, + okText: "ok", + cancelLink: true, + cancelText: "cancel", + savingText: "Saving...", + clickToEditText: "Click to edit", + okText: "ok", + rows: 1, + onComplete: function(transport, element) { + new Effect.Highlight(element, {startcolor: this.options.highlightcolor}); + }, + onFailure: function(transport) { + alert("Error communicating with the server: " + transport.responseText.stripTags()); + }, + callback: function(form) { + return Form.serialize(form); + }, + handleLineBreaks: true, + loadingText: 'Loading...', + savingClassName: 'inplaceeditor-saving', + loadingClassName: 'inplaceeditor-loading', + formClassName: 'inplaceeditor-form', + highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor, + highlightendcolor: "#FFFFFF", + externalControl: null, + submitOnBlur: false, + ajaxOptions: {}, + evalScripts: false + }, options || {}); + + if(!this.options.formId && this.element.id) { + this.options.formId = this.element.id + "-inplaceeditor"; + if ($(this.options.formId)) { + // there's already a form with that name, don't specify an id + this.options.formId = null; + } + } + + if (this.options.externalControl) { + this.options.externalControl = $(this.options.externalControl); + } + + this.originalBackground = Element.getStyle(this.element, 'background-color'); + if (!this.originalBackground) { + this.originalBackground = "transparent"; + } + + this.element.title = this.options.clickToEditText; + + this.onclickListener = this.enterEditMode.bindAsEventListener(this); + this.mouseoverListener = this.enterHover.bindAsEventListener(this); + this.mouseoutListener = this.leaveHover.bindAsEventListener(this); + Event.observe(this.element, 'click', this.onclickListener); + Event.observe(this.element, 'mouseover', this.mouseoverListener); + Event.observe(this.element, 'mouseout', this.mouseoutListener); + if (this.options.externalControl) { + Event.observe(this.options.externalControl, 'click', this.onclickListener); + Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener); + Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener); + } + }, + enterEditMode: function(evt) { + if (this.saving) return; + if (this.editing) return; + this.editing = true; + this.onEnterEditMode(); + if (this.options.externalControl) { + Element.hide(this.options.externalControl); + } + Element.hide(this.element); + this.createForm(); + this.element.parentNode.insertBefore(this.form, this.element); + if (!this.options.loadTextURL) Field.scrollFreeActivate(this.editField); + // stop the event to avoid a page refresh in Safari + if (evt) { + Event.stop(evt); + } + return false; + }, + createForm: function() { + this.form = document.createElement("form"); + this.form.id = this.options.formId; + Element.addClassName(this.form, this.options.formClassName) + this.form.onsubmit = this.onSubmit.bind(this); + + this.createEditField(); + + if (this.options.textarea) { + var br = document.createElement("br"); + this.form.appendChild(br); + } + + if (this.options.okButton) { + okButton = document.createElement("input"); + okButton.type = "submit"; + okButton.value = this.options.okText; + okButton.className = 'editor_ok_button'; + this.form.appendChild(okButton); + } + + if (this.options.cancelLink) { + cancelLink = document.createElement("a"); + cancelLink.href = "#"; + cancelLink.appendChild(document.createTextNode(this.options.cancelText)); + cancelLink.onclick = this.onclickCancel.bind(this); + cancelLink.className = 'editor_cancel'; + this.form.appendChild(cancelLink); + } + }, + hasHTMLLineBreaks: function(string) { + if (!this.options.handleLineBreaks) return false; + return string.match(/
    /i); + }, + convertHTMLLineBreaks: function(string) { + return string.replace(/
    /gi, "\n").replace(//gi, "\n").replace(/<\/p>/gi, "\n").replace(/

    /gi, ""); + }, + createEditField: function() { + var text; + if(this.options.loadTextURL) { + text = this.options.loadingText; + } else { + text = this.getText(); + } + + var obj = this; + + if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) { + this.options.textarea = false; + var textField = document.createElement("input"); + textField.obj = this; + textField.type = "text"; + textField.name = "value"; + textField.value = text; + textField.style.backgroundColor = this.options.highlightcolor; + textField.className = 'editor_field'; + var size = this.options.size || this.options.cols || 0; + if (size != 0) textField.size = size; + if (this.options.submitOnBlur) + textField.onblur = this.onSubmit.bind(this); + this.editField = textField; + } else { + this.options.textarea = true; + var textArea = document.createElement("textarea"); + textArea.obj = this; + textArea.name = "value"; + textArea.value = this.convertHTMLLineBreaks(text); + textArea.rows = this.options.rows; + textArea.cols = this.options.cols || 40; + textArea.className = 'editor_field'; + if (this.options.submitOnBlur) + textArea.onblur = this.onSubmit.bind(this); + this.editField = textArea; + } + + if(this.options.loadTextURL) { + this.loadExternalText(); + } + this.form.appendChild(this.editField); + }, + getText: function() { + return this.element.innerHTML; + }, + loadExternalText: function() { + Element.addClassName(this.form, this.options.loadingClassName); + this.editField.disabled = true; + new Ajax.Request( + this.options.loadTextURL, + Object.extend({ + asynchronous: true, + onComplete: this.onLoadedExternalText.bind(this) + }, this.options.ajaxOptions) + ); + }, + onLoadedExternalText: function(transport) { + Element.removeClassName(this.form, this.options.loadingClassName); + this.editField.disabled = false; + this.editField.value = transport.responseText.stripTags(); + Field.scrollFreeActivate(this.editField); + }, + onclickCancel: function() { + this.onComplete(); + this.leaveEditMode(); + return false; + }, + onFailure: function(transport) { + this.options.onFailure(transport); + if (this.oldInnerHTML) { + this.element.innerHTML = this.oldInnerHTML; + this.oldInnerHTML = null; + } + return false; + }, + onSubmit: function() { + // onLoading resets these so we need to save them away for the Ajax call + var form = this.form; + var value = this.editField.value; + + // do this first, sometimes the ajax call returns before we get a chance to switch on Saving... + // which means this will actually switch on Saving... *after* we've left edit mode causing Saving... + // to be displayed indefinitely + this.onLoading(); + + if (this.options.evalScripts) { + new Ajax.Request( + this.url, Object.extend({ + parameters: this.options.callback(form, value), + onComplete: this.onComplete.bind(this), + onFailure: this.onFailure.bind(this), + asynchronous:true, + evalScripts:true + }, this.options.ajaxOptions)); + } else { + new Ajax.Updater( + { success: this.element, + // don't update on failure (this could be an option) + failure: null }, + this.url, Object.extend({ + parameters: this.options.callback(form, value), + onComplete: this.onComplete.bind(this), + onFailure: this.onFailure.bind(this) + }, this.options.ajaxOptions)); + } + // stop the event to avoid a page refresh in Safari + if (arguments.length > 1) { + Event.stop(arguments[0]); + } + return false; + }, + onLoading: function() { + this.saving = true; + this.removeForm(); + this.leaveHover(); + this.showSaving(); + }, + showSaving: function() { + this.oldInnerHTML = this.element.innerHTML; + this.element.innerHTML = this.options.savingText; + Element.addClassName(this.element, this.options.savingClassName); + this.element.style.backgroundColor = this.originalBackground; + Element.show(this.element); + }, + removeForm: function() { + if(this.form) { + if (this.form.parentNode) Element.remove(this.form); + this.form = null; + } + }, + enterHover: function() { + if (this.saving) return; + this.element.style.backgroundColor = this.options.highlightcolor; + if (this.effect) { + this.effect.cancel(); + } + Element.addClassName(this.element, this.options.hoverClassName) + }, + leaveHover: function() { + if (this.options.backgroundColor) { + this.element.style.backgroundColor = this.oldBackground; + } + Element.removeClassName(this.element, this.options.hoverClassName) + if (this.saving) return; + this.effect = new Effect.Highlight(this.element, { + startcolor: this.options.highlightcolor, + endcolor: this.options.highlightendcolor, + restorecolor: this.originalBackground + }); + }, + leaveEditMode: function() { + Element.removeClassName(this.element, this.options.savingClassName); + this.removeForm(); + this.leaveHover(); + this.element.style.backgroundColor = this.originalBackground; + Element.show(this.element); + if (this.options.externalControl) { + Element.show(this.options.externalControl); + } + this.editing = false; + this.saving = false; + this.oldInnerHTML = null; + this.onLeaveEditMode(); + }, + onComplete: function(transport) { + this.leaveEditMode(); + this.options.onComplete.bind(this)(transport, this.element); + }, + onEnterEditMode: function() {}, + onLeaveEditMode: function() {}, + dispose: function() { + if (this.oldInnerHTML) { + this.element.innerHTML = this.oldInnerHTML; + } + this.leaveEditMode(); + Event.stopObserving(this.element, 'click', this.onclickListener); + Event.stopObserving(this.element, 'mouseover', this.mouseoverListener); + Event.stopObserving(this.element, 'mouseout', this.mouseoutListener); + if (this.options.externalControl) { + Event.stopObserving(this.options.externalControl, 'click', this.onclickListener); + Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener); + Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener); + } + } +}; + +Ajax.InPlaceCollectionEditor = Class.create(); +Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype); +Object.extend(Ajax.InPlaceCollectionEditor.prototype, { + createEditField: function() { + if (!this.cached_selectTag) { + var selectTag = document.createElement("select"); + var collection = this.options.collection || []; + var optionTag; + collection.each(function(e,i) { + optionTag = document.createElement("option"); + optionTag.value = (e instanceof Array) ? e[0] : e; + if((typeof this.options.value == 'undefined') && + ((e instanceof Array) ? this.element.innerHTML == e[1] : e == optionTag.value)) optionTag.selected = true; + if(this.options.value==optionTag.value) optionTag.selected = true; + optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e)); + selectTag.appendChild(optionTag); + }.bind(this)); + this.cached_selectTag = selectTag; + } + + this.editField = this.cached_selectTag; + if(this.options.loadTextURL) this.loadExternalText(); + this.form.appendChild(this.editField); + this.options.callback = function(form, value) { + return "value=" + encodeURIComponent(value); + } + } +}); + +// Delayed observer, like Form.Element.Observer, +// but waits for delay after last key input +// Ideal for live-search fields + +Form.Element.DelayedObserver = Class.create(); +Form.Element.DelayedObserver.prototype = { + initialize: function(element, delay, callback) { + this.delay = delay || 0.5; + this.element = $(element); + this.callback = callback; + this.timer = null; + this.lastValue = $F(this.element); + Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); + }, + delayedListener: function(event) { + if(this.lastValue == $F(this.element)) return; + if(this.timer) clearTimeout(this.timer); + this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000); + this.lastValue = $F(this.element); + }, + onTimerEvent: function() { + this.timer = null; + this.callback(this.element, $F(this.element)); + } +}; diff --git a/Solar/App/Public/scripts/scriptaculous/dragdrop.js b/Solar/App/Public/scripts/scriptaculous/dragdrop.js index 9cd32e9f..211eb6b6 100755 --- a/Solar/App/Public/scripts/scriptaculous/dragdrop.js +++ b/Solar/App/Public/scripts/scriptaculous/dragdrop.js @@ -1,76 +1,970 @@ -// From script.aculo.us 1.6.2, compressed by jsmin (http://www.crockford.com/javascript/jsmin.html). - -if(typeof Effect=='undefined') -throw("dragdrop.js requires including script.aculo.us' effects.js library");var Droppables={drops:[],remove:function(element){this.drops=this.drops.reject(function(d){return d.element==$(element)});},add:function(element){element=$(element);var options=Object.extend({greedy:true,hoverclass:null,tree:false},arguments[1]||{});if(options.containment){options._containers=[];var containment=options.containment;if((typeof containment=='object')&&(containment.constructor==Array)){containment.each(function(c){options._containers.push($(c))});}else{options._containers.push($(containment));}} -if(options.accept)options.accept=[options.accept].flatten();Element.makePositioned(element);options.element=element;this.drops.push(options);},findDeepestChild:function(drops){deepest=drops[0];for(i=1;i0){drop=Droppables.findDeepestChild(affected);Position.within(drop.element,point[0],point[1]);if(drop.onHover) -drop.onHover(element,drop.element,Position.overlap(drop.overlap,drop.element));Droppables.activate(drop);}},fire:function(event,element){if(!this.last_active)return;Position.prepare();if(this.isAffected([Event.pointerX(event),Event.pointerY(event)],element,this.last_active)) -if(this.last_active.onDrop) -this.last_active.onDrop(element,this.last_active.element,event);},reset:function(){if(this.last_active) -this.deactivate(this.last_active);}} -var Draggables={drags:[],observers:[],register:function(draggable){if(this.drags.length==0){this.eventMouseUp=this.endDrag.bindAsEventListener(this);this.eventMouseMove=this.updateDrag.bindAsEventListener(this);this.eventKeypress=this.keyPress.bindAsEventListener(this);Event.observe(document,"mouseup",this.eventMouseUp);Event.observe(document,"mousemove",this.eventMouseMove);Event.observe(document,"keypress",this.eventKeypress);} -this.drags.push(draggable);},unregister:function(draggable){this.drags=this.drags.reject(function(d){return d==draggable});if(this.drags.length==0){Event.stopObserving(document,"mouseup",this.eventMouseUp);Event.stopObserving(document,"mousemove",this.eventMouseMove);Event.stopObserving(document,"keypress",this.eventKeypress);}},activate:function(draggable){window.focus();this.activeDraggable=draggable;},deactivate:function(){this.activeDraggable=null;},updateDrag:function(event){if(!this.activeDraggable)return;var pointer=[Event.pointerX(event),Event.pointerY(event)];if(this._lastPointer&&(this._lastPointer.inspect()==pointer.inspect()))return;this._lastPointer=pointer;this.activeDraggable.updateDrag(event,pointer);},endDrag:function(event){if(!this.activeDraggable)return;this._lastPointer=null;this.activeDraggable.endDrag(event);this.activeDraggable=null;},keyPress:function(event){if(this.activeDraggable) -this.activeDraggable.keyPress(event);},addObserver:function(observer){this.observers.push(observer);this._cacheObserverCallbacks();},removeObserver:function(element){this.observers=this.observers.reject(function(o){return o.element==element});this._cacheObserverCallbacks();},notify:function(eventName,draggable,event){if(this[eventName+'Count']>0) -this.observers.each(function(o){if(o[eventName])o[eventName](eventName,draggable,event);});},_cacheObserverCallbacks:function(){['onStart','onEnd','onDrag'].each(function(eventName){Draggables[eventName+'Count']=Draggables.observers.select(function(o){return o[eventName];}).length;});}} -var Draggable=Class.create();Draggable._revertCache={};Draggable._dragging={};Draggable.prototype={initialize:function(element){var options=Object.extend({handle:false,starteffect:function(element){element._opacity=Element.getOpacity(element);Draggable._dragging[element]=true;new Effect.Opacity(element,{duration:0.2,from:element._opacity,to:0.7});},reverteffect:function(element,top_offset,left_offset){var dur=Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;Draggable._revertCache[element]=new Effect.Move(element,{x:-left_offset,y:-top_offset,duration:dur,queue:{scope:'_draggable',position:'end'}});},endeffect:function(element){var toOpacity=typeof element._opacity=='number'?element._opacity:1.0;new Effect.Opacity(element,{duration:0.2,from:0.7,to:toOpacity,queue:{scope:'_draggable',position:'end'},afterFinish:function(){Draggable._dragging[element]=false}});},zindex:1000,revert:false,scroll:false,scrollSensitivity:20,scrollSpeed:15,snap:false},arguments[1]||{});this.element=$(element);if(options.handle&&(typeof options.handle=='string')){var h=Element.childrenWithClassName(this.element,options.handle,true);if(h.length>0)this.handle=h[0];} -if(!this.handle)this.handle=$(options.handle);if(!this.handle)this.handle=this.element;if(options.scroll&&!options.scroll.scrollTo&&!options.scroll.outerHTML) -options.scroll=$(options.scroll);Element.makePositioned(this.element);this.delta=this.currentDelta();this.options=options;this.dragging=false;this.eventMouseDown=this.initDrag.bindAsEventListener(this);Event.observe(this.handle,"mousedown",this.eventMouseDown);Draggables.register(this);},destroy:function(){Event.stopObserving(this.handle,"mousedown",this.eventMouseDown);Draggables.unregister(this);},currentDelta:function(){return([parseInt(Element.getStyle(this.element,'left')||'0'),parseInt(Element.getStyle(this.element,'top')||'0')]);},initDrag:function(event){if(typeof Draggable._dragging[this.element]!=undefined&&Draggable._dragging[this.element])return;if(Event.isLeftClick(event)){var src=Event.element(event);if(src.tagName&&(src.tagName=='INPUT'||src.tagName=='SELECT'||src.tagName=='OPTION'||src.tagName=='BUTTON'||src.tagName=='TEXTAREA'))return;if(Draggable._revertCache[this.element]){Draggable._revertCache[this.element].cancel();Draggable._revertCache[this.element]=null;} -var pointer=[Event.pointerX(event),Event.pointerY(event)];var pos=Position.cumulativeOffset(this.element);this.offset=[0,1].map(function(i){return(pointer[i]-pos[i])});Draggables.activate(this);Event.stop(event);}},startDrag:function(event){this.dragging=true;if(this.options.zindex){this.originalZ=parseInt(Element.getStyle(this.element,'z-index')||0);this.element.style.zIndex=this.options.zindex;} -if(this.options.ghosting){this._clone=this.element.cloneNode(true);Position.absolutize(this.element);this.element.parentNode.insertBefore(this._clone,this.element);} -if(this.options.scroll){if(this.options.scroll==window){var where=this._getWindowScroll(this.options.scroll);this.originalScrollLeft=where.left;this.originalScrollTop=where.top;}else{this.originalScrollLeft=this.options.scroll.scrollLeft;this.originalScrollTop=this.options.scroll.scrollTop;}} -Draggables.notify('onStart',this,event);if(this.options.starteffect)this.options.starteffect(this.element);},updateDrag:function(event,pointer){if(!this.dragging)this.startDrag(event);Position.prepare();Droppables.show(pointer,this.element);Draggables.notify('onDrag',this,event);this.draw(pointer);if(this.options.change)this.options.change(this);if(this.options.scroll){this.stopScrolling();var p;if(this.options.scroll==window){with(this._getWindowScroll(this.options.scroll)){p=[left,top,left+width,top+height];}}else{p=Position.page(this.options.scroll);p[0]+=this.options.scroll.scrollLeft;p[1]+=this.options.scroll.scrollTop;p.push(p[0]+this.options.scroll.offsetWidth);p.push(p[1]+this.options.scroll.offsetHeight);} -var speed=[0,0];if(pointer[0]<(p[0]+this.options.scrollSensitivity))speed[0]=pointer[0]-(p[0]+this.options.scrollSensitivity);if(pointer[1]<(p[1]+this.options.scrollSensitivity))speed[1]=pointer[1]-(p[1]+this.options.scrollSensitivity);if(pointer[0]>(p[2]-this.options.scrollSensitivity))speed[0]=pointer[0]-(p[2]-this.options.scrollSensitivity);if(pointer[1]>(p[3]-this.options.scrollSensitivity))speed[1]=pointer[1]-(p[3]-this.options.scrollSensitivity);this.startScrolling(speed);} -if(navigator.appVersion.indexOf('AppleWebKit')>0)window.scrollBy(0,0);Event.stop(event);},finishDrag:function(event,success){this.dragging=false;if(this.options.ghosting){Position.relativize(this.element);Element.remove(this._clone);this._clone=null;} -if(success)Droppables.fire(event,this.element);Draggables.notify('onEnd',this,event);var revert=this.options.revert;if(revert&&typeof revert=='function')revert=revert(this.element);var d=this.currentDelta();if(revert&&this.options.reverteffect){this.options.reverteffect(this.element,d[1]-this.delta[1],d[0]-this.delta[0]);}else{this.delta=d;} -if(this.options.zindex) -this.element.style.zIndex=this.originalZ;if(this.options.endeffect) -this.options.endeffect(this.element);Draggables.deactivate(this);Droppables.reset();},keyPress:function(event){if(event.keyCode!=Event.KEY_ESC)return;this.finishDrag(event,false);Event.stop(event);},endDrag:function(event){if(!this.dragging)return;this.stopScrolling();this.finishDrag(event,true);Event.stop(event);},draw:function(point){var pos=Position.cumulativeOffset(this.element);var d=this.currentDelta();pos[0]-=d[0];pos[1]-=d[1];if(this.options.scroll&&(this.options.scroll!=window)){pos[0]-=this.options.scroll.scrollLeft-this.originalScrollLeft;pos[1]-=this.options.scroll.scrollTop-this.originalScrollTop;} -var p=[0,1].map(function(i){return(point[i]-pos[i]-this.offset[i])}.bind(this));if(this.options.snap){if(typeof this.options.snap=='function'){p=this.options.snap(p[0],p[1],this);}else{if(this.options.snap instanceof Array){p=p.map(function(v,i){return Math.round(v/this.options.snap[i])*this.options.snap[i]}.bind(this))}else{p=p.map(function(v){return Math.round(v/this.options.snap)*this.options.snap}.bind(this))}}} -var style=this.element.style;if((!this.options.constraint)||(this.options.constraint=='horizontal')) -style.left=p[0]+"px";if((!this.options.constraint)||(this.options.constraint=='vertical')) -style.top=p[1]+"px";if(style.visibility=="hidden")style.visibility="";},stopScrolling:function(){if(this.scrollInterval){clearInterval(this.scrollInterval);this.scrollInterval=null;Draggables._lastScrollPointer=null;}},startScrolling:function(speed){if(!(speed[0]||speed[1]))return;this.scrollSpeed=[speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];this.lastScrolled=new Date();this.scrollInterval=setInterval(this.scroll.bind(this),10);},scroll:function(){var current=new Date();var delta=current-this.lastScrolled;this.lastScrolled=current;if(this.options.scroll==window){with(this._getWindowScroll(this.options.scroll)){if(this.scrollSpeed[0]||this.scrollSpeed[1]){var d=delta/1000;this.options.scroll.scrollTo(left+d*this.scrollSpeed[0],top+d*this.scrollSpeed[1]);}}}else{this.options.scroll.scrollLeft+=this.scrollSpeed[0]*delta/1000;this.options.scroll.scrollTop+=this.scrollSpeed[1]*delta/1000;} -Position.prepare();Droppables.show(Draggables._lastPointer,this.element);Draggables.notify('onDrag',this);Draggables._lastScrollPointer=Draggables._lastScrollPointer||$A(Draggables._lastPointer);Draggables._lastScrollPointer[0]+=this.scrollSpeed[0]*delta/1000;Draggables._lastScrollPointer[1]+=this.scrollSpeed[1]*delta/1000;if(Draggables._lastScrollPointer[0]<0) -Draggables._lastScrollPointer[0]=0;if(Draggables._lastScrollPointer[1]<0) -Draggables._lastScrollPointer[1]=0;this.draw(Draggables._lastScrollPointer);if(this.options.change)this.options.change(this);},_getWindowScroll:function(w){var T,L,W,H;with(w.document){if(w.document.documentElement&&documentElement.scrollTop){T=documentElement.scrollTop;L=documentElement.scrollLeft;}else if(w.document.body){T=body.scrollTop;L=body.scrollLeft;} -if(w.innerWidth){W=w.innerWidth;H=w.innerHeight;}else if(w.document.documentElement&&documentElement.clientWidth){W=documentElement.clientWidth;H=documentElement.clientHeight;}else{W=body.offsetWidth;H=body.offsetHeight}} -return{top:T,left:L,width:W,height:H};}} -var SortableObserver=Class.create();SortableObserver.prototype={initialize:function(element,observer){this.element=$(element);this.observer=observer;this.lastValue=Sortable.serialize(this.element);},onStart:function(){this.lastValue=Sortable.serialize(this.element);},onEnd:function(){Sortable.unmark();if(this.lastValue!=Sortable.serialize(this.element)) -this.observer(this.element)}} -var Sortable={sortables:{},_findRootElement:function(element){while(element.tagName!="BODY"){if(element.id&&Sortable.sortables[element.id])return element;element=element.parentNode;}},options:function(element){element=Sortable._findRootElement($(element));if(!element)return;return Sortable.sortables[element.id];},destroy:function(element){var s=Sortable.options(element);if(s){Draggables.removeObserver(s.element);s.droppables.each(function(d){Droppables.remove(d)});s.draggables.invoke('destroy');delete Sortable.sortables[s.element.id];}},create:function(element){element=$(element);var options=Object.extend({element:element,tag:'li',dropOnEmpty:false,tree:false,treeTag:'ul',overlap:'vertical',constraint:'vertical',containment:element,handle:false,only:false,hoverclass:null,ghosting:false,scroll:false,scrollSensitivity:20,scrollSpeed:15,format:/^[^_]*_(.*)$/,onChange:Prototype.emptyFunction,onUpdate:Prototype.emptyFunction},arguments[1]||{});this.destroy(element);var options_for_draggable={revert:true,scroll:options.scroll,scrollSpeed:options.scrollSpeed,scrollSensitivity:options.scrollSensitivity,ghosting:options.ghosting,constraint:options.constraint,handle:options.handle};if(options.starteffect) -options_for_draggable.starteffect=options.starteffect;if(options.reverteffect) -options_for_draggable.reverteffect=options.reverteffect;else -if(options.ghosting)options_for_draggable.reverteffect=function(element){element.style.top=0;element.style.left=0;};if(options.endeffect) -options_for_draggable.endeffect=options.endeffect;if(options.zindex) -options_for_draggable.zindex=options.zindex;var options_for_droppable={overlap:options.overlap,containment:options.containment,tree:options.tree,hoverclass:options.hoverclass,onHover:Sortable.onHover} -var options_for_tree={onHover:Sortable.onEmptyHover,overlap:options.overlap,containment:options.containment,hoverclass:options.hoverclass} -Element.cleanWhitespace(element);options.draggables=[];options.droppables=[];if(options.dropOnEmpty||options.tree){Droppables.add(element,options_for_tree);options.droppables.push(element);} -(this.findElements(element,options)||[]).each(function(e){var handle=options.handle?Element.childrenWithClassName(e,options.handle)[0]:e;options.draggables.push(new Draggable(e,Object.extend(options_for_draggable,{handle:handle})));Droppables.add(e,options_for_droppable);if(options.tree)e.treeNode=element;options.droppables.push(e);});if(options.tree){(Sortable.findTreeElements(element,options)||[]).each(function(e){Droppables.add(e,options_for_tree);e.treeNode=element;options.droppables.push(e);});} -this.sortables[element.id]=options;Draggables.addObserver(new SortableObserver(element,options.onUpdate));},findElements:function(element,options){return Element.findChildren(element,options.only,options.tree?true:false,options.tag);},findTreeElements:function(element,options){return Element.findChildren(element,options.only,options.tree?true:false,options.treeTag);},onHover:function(element,dropon,overlap){if(Element.isParent(dropon,element))return;if(overlap>.33&&overlap<.66&&Sortable.options(dropon).tree){return;}else if(overlap>0.5){Sortable.mark(dropon,'before');if(dropon.previousSibling!=element){var oldParentNode=element.parentNode;element.style.visibility="hidden";dropon.parentNode.insertBefore(element,dropon);if(dropon.parentNode!=oldParentNode) -Sortable.options(oldParentNode).onChange(element);Sortable.options(dropon.parentNode).onChange(element);}}else{Sortable.mark(dropon,'after');var nextElement=dropon.nextSibling||null;if(nextElement!=element){var oldParentNode=element.parentNode;element.style.visibility="hidden";dropon.parentNode.insertBefore(element,nextElement);if(dropon.parentNode!=oldParentNode) -Sortable.options(oldParentNode).onChange(element);Sortable.options(dropon.parentNode).onChange(element);}}},onEmptyHover:function(element,dropon,overlap){var oldParentNode=element.parentNode;var droponOptions=Sortable.options(dropon);if(!Element.isParent(dropon,element)){var index;var children=Sortable.findElements(dropon,{tag:droponOptions.tag,only:droponOptions.only});var child=null;if(children){var offset=Element.offsetSize(dropon,droponOptions.overlap)*(1.0-overlap);for(index=0;index=0){offset-=Element.offsetSize(children[index],droponOptions.overlap);}else if(offset-(Element.offsetSize(children[index],droponOptions.overlap)/2)>=0){child=index+10) { + drop = Droppables.findDeepestChild(affected); + Position.within(drop.element, point[0], point[1]); + if(drop.onHover) + drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); + + Droppables.activate(drop); + } + }, + + fire: function(event, element) { + if(!this.last_active) return; + Position.prepare(); + + if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active)) + if (this.last_active.onDrop) + this.last_active.onDrop(element, this.last_active.element, event); + }, + + reset: function() { + if(this.last_active) + this.deactivate(this.last_active); + } +} + +var Draggables = { + drags: [], + observers: [], + + register: function(draggable) { + if(this.drags.length == 0) { + this.eventMouseUp = this.endDrag.bindAsEventListener(this); + this.eventMouseMove = this.updateDrag.bindAsEventListener(this); + this.eventKeypress = this.keyPress.bindAsEventListener(this); + + Event.observe(document, "mouseup", this.eventMouseUp); + Event.observe(document, "mousemove", this.eventMouseMove); + Event.observe(document, "keypress", this.eventKeypress); + } + this.drags.push(draggable); + }, + + unregister: function(draggable) { + this.drags = this.drags.reject(function(d) { return d==draggable }); + if(this.drags.length == 0) { + Event.stopObserving(document, "mouseup", this.eventMouseUp); + Event.stopObserving(document, "mousemove", this.eventMouseMove); + Event.stopObserving(document, "keypress", this.eventKeypress); + } + }, + + activate: function(draggable) { + if(draggable.options.delay) { + this._timeout = setTimeout(function() { + Draggables._timeout = null; + window.focus(); + Draggables.activeDraggable = draggable; + }.bind(this), draggable.options.delay); + } else { + window.focus(); // allows keypress events if window isn't currently focused, fails for Safari + this.activeDraggable = draggable; + } + }, + + deactivate: function() { + this.activeDraggable = null; + }, + + updateDrag: function(event) { + if(!this.activeDraggable) return; + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + // Mozilla-based browsers fire successive mousemove events with + // the same coordinates, prevent needless redrawing (moz bug?) + if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; + this._lastPointer = pointer; + + this.activeDraggable.updateDrag(event, pointer); + }, + + endDrag: function(event) { + if(this._timeout) { + clearTimeout(this._timeout); + this._timeout = null; + } + if(!this.activeDraggable) return; + this._lastPointer = null; + this.activeDraggable.endDrag(event); + this.activeDraggable = null; + }, + + keyPress: function(event) { + if(this.activeDraggable) + this.activeDraggable.keyPress(event); + }, + + addObserver: function(observer) { + this.observers.push(observer); + this._cacheObserverCallbacks(); + }, + + removeObserver: function(element) { // element instead of observer fixes mem leaks + this.observers = this.observers.reject( function(o) { return o.element==element }); + this._cacheObserverCallbacks(); + }, + + notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag' + if(this[eventName+'Count'] > 0) + this.observers.each( function(o) { + if(o[eventName]) o[eventName](eventName, draggable, event); + }); + if(draggable.options[eventName]) draggable.options[eventName](draggable, event); + }, + + _cacheObserverCallbacks: function() { + ['onStart','onEnd','onDrag'].each( function(eventName) { + Draggables[eventName+'Count'] = Draggables.observers.select( + function(o) { return o[eventName]; } + ).length; + }); + } +} + +/*--------------------------------------------------------------------------*/ + +var Draggable = Class.create(); +Draggable._dragging = {}; + +Draggable.prototype = { + initialize: function(element) { + var defaults = { + handle: false, + reverteffect: function(element, top_offset, left_offset) { + var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; + new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur, + queue: {scope:'_draggable', position:'end'} + }); + }, + endeffect: function(element) { + var toOpacity = typeof element._opacity == 'number' ? element._opacity : 1.0; + new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, + queue: {scope:'_draggable', position:'end'}, + afterFinish: function(){ + Draggable._dragging[element] = false + } + }); + }, + zindex: 1000, + revert: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] } + delay: 0 + }; + + if(arguments[1] && typeof arguments[1].endeffect == 'undefined') + Object.extend(defaults, { + starteffect: function(element) { + element._opacity = Element.getOpacity(element); + Draggable._dragging[element] = true; + new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); + } + }); + + var options = Object.extend(defaults, arguments[1] || {}); + + this.element = $(element); + + if(options.handle && (typeof options.handle == 'string')) { + var h = Element.childrenWithClassName(this.element, options.handle, true); + if(h.length>0) this.handle = h[0]; + } + if(!this.handle) this.handle = $(options.handle); + if(!this.handle) this.handle = this.element; + + if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) { + options.scroll = $(options.scroll); + this._isScrollChild = Element.childOf(this.element, options.scroll); + } + + Element.makePositioned(this.element); // fix IE + + this.delta = this.currentDelta(); + this.options = options; + this.dragging = false; + + this.eventMouseDown = this.initDrag.bindAsEventListener(this); + Event.observe(this.handle, "mousedown", this.eventMouseDown); + + Draggables.register(this); + }, + + destroy: function() { + Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); + Draggables.unregister(this); + }, + + currentDelta: function() { + return([ + parseInt(Element.getStyle(this.element,'left') || '0'), + parseInt(Element.getStyle(this.element,'top') || '0')]); + }, + + initDrag: function(event) { + if(typeof Draggable._dragging[this.element] != 'undefined' && + Draggable._dragging[this.element]) return; + if(Event.isLeftClick(event)) { + // abort on form elements, fixes a Firefox issue + var src = Event.element(event); + if(src.tagName && ( + src.tagName=='INPUT' || + src.tagName=='SELECT' || + src.tagName=='OPTION' || + src.tagName=='BUTTON' || + src.tagName=='TEXTAREA')) return; + + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var pos = Position.cumulativeOffset(this.element); + this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); + + Draggables.activate(this); + Event.stop(event); + } + }, + + startDrag: function(event) { + this.dragging = true; + + if(this.options.zindex) { + this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); + this.element.style.zIndex = this.options.zindex; + } + + if(this.options.ghosting) { + this._clone = this.element.cloneNode(true); + Position.absolutize(this.element); + this.element.parentNode.insertBefore(this._clone, this.element); + } + + if(this.options.scroll) { + if (this.options.scroll == window) { + var where = this._getWindowScroll(this.options.scroll); + this.originalScrollLeft = where.left; + this.originalScrollTop = where.top; + } else { + this.originalScrollLeft = this.options.scroll.scrollLeft; + this.originalScrollTop = this.options.scroll.scrollTop; + } + } + + Draggables.notify('onStart', this, event); + + if(this.options.starteffect) this.options.starteffect(this.element); + }, + + updateDrag: function(event, pointer) { + if(!this.dragging) this.startDrag(event); + Position.prepare(); + Droppables.show(pointer, this.element); + Draggables.notify('onDrag', this, event); + + this.draw(pointer); + if(this.options.change) this.options.change(this); + + if(this.options.scroll) { + this.stopScrolling(); + + var p; + if (this.options.scroll == window) { + with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; } + } else { + p = Position.page(this.options.scroll); + p[0] += this.options.scroll.scrollLeft; + p[1] += this.options.scroll.scrollTop; + + p[0] += (window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0); + p[1] += (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0); + + p.push(p[0]+this.options.scroll.offsetWidth); + p.push(p[1]+this.options.scroll.offsetHeight); + } + var speed = [0,0]; + if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity); + if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity); + if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity); + if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); + this.startScrolling(speed); + } + + // fix AppleWebKit rendering + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); + + Event.stop(event); + }, + + finishDrag: function(event, success) { + this.dragging = false; + + if(this.options.ghosting) { + Position.relativize(this.element); + Element.remove(this._clone); + this._clone = null; + } + + if(success) Droppables.fire(event, this.element); + Draggables.notify('onEnd', this, event); + + var revert = this.options.revert; + if(revert && typeof revert == 'function') revert = revert(this.element); + + var d = this.currentDelta(); + if(revert && this.options.reverteffect) { + this.options.reverteffect(this.element, + d[1]-this.delta[1], d[0]-this.delta[0]); + } else { + this.delta = d; + } + + if(this.options.zindex) + this.element.style.zIndex = this.originalZ; + + if(this.options.endeffect) + this.options.endeffect(this.element); + + Draggables.deactivate(this); + Droppables.reset(); + }, + + keyPress: function(event) { + if(event.keyCode!=Event.KEY_ESC) return; + this.finishDrag(event, false); + Event.stop(event); + }, + + endDrag: function(event) { + if(!this.dragging) return; + this.stopScrolling(); + this.finishDrag(event, true); + Event.stop(event); + }, + + draw: function(point) { + var pos = Position.cumulativeOffset(this.element); + if(this.options.ghosting) { + var r = Position.realOffset(this.element); + window.status = r.inspect(); + pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY; + } + + var d = this.currentDelta(); + pos[0] -= d[0]; pos[1] -= d[1]; + + if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) { + pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft; + pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop; + } + + var p = [0,1].map(function(i){ + return (point[i]-pos[i]-this.offset[i]) + }.bind(this)); + + if(this.options.snap) { + if(typeof this.options.snap == 'function') { + p = this.options.snap(p[0],p[1],this); + } else { + if(this.options.snap instanceof Array) { + p = p.map( function(v, i) { + return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this)) + } else { + p = p.map( function(v) { + return Math.round(v/this.options.snap)*this.options.snap }.bind(this)) + } + }} + + var style = this.element.style; + if((!this.options.constraint) || (this.options.constraint=='horizontal')) + style.left = p[0] + "px"; + if((!this.options.constraint) || (this.options.constraint=='vertical')) + style.top = p[1] + "px"; + + if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering + }, + + stopScrolling: function() { + if(this.scrollInterval) { + clearInterval(this.scrollInterval); + this.scrollInterval = null; + Draggables._lastScrollPointer = null; + } + }, + + startScrolling: function(speed) { + if(!(speed[0] || speed[1])) return; + this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed]; + this.lastScrolled = new Date(); + this.scrollInterval = setInterval(this.scroll.bind(this), 10); + }, + + scroll: function() { + var current = new Date(); + var delta = current - this.lastScrolled; + this.lastScrolled = current; + if(this.options.scroll == window) { + with (this._getWindowScroll(this.options.scroll)) { + if (this.scrollSpeed[0] || this.scrollSpeed[1]) { + var d = delta / 1000; + this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] ); + } + } + } else { + this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; + this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; + } + + Position.prepare(); + Droppables.show(Draggables._lastPointer, this.element); + Draggables.notify('onDrag', this); + if (this._isScrollChild) { + Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer); + Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000; + Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000; + if (Draggables._lastScrollPointer[0] < 0) + Draggables._lastScrollPointer[0] = 0; + if (Draggables._lastScrollPointer[1] < 0) + Draggables._lastScrollPointer[1] = 0; + this.draw(Draggables._lastScrollPointer); + } + + if(this.options.change) this.options.change(this); + }, + + _getWindowScroll: function(w) { + var T, L, W, H; + with (w.document) { + if (w.document.documentElement && documentElement.scrollTop) { + T = documentElement.scrollTop; + L = documentElement.scrollLeft; + } else if (w.document.body) { + T = body.scrollTop; + L = body.scrollLeft; + } + if (w.innerWidth) { + W = w.innerWidth; + H = w.innerHeight; + } else if (w.document.documentElement && documentElement.clientWidth) { + W = documentElement.clientWidth; + H = documentElement.clientHeight; + } else { + W = body.offsetWidth; + H = body.offsetHeight + } + } + return { top: T, left: L, width: W, height: H }; + } +} + +/*--------------------------------------------------------------------------*/ + +var SortableObserver = Class.create(); +SortableObserver.prototype = { + initialize: function(element, observer) { + this.element = $(element); + this.observer = observer; + this.lastValue = Sortable.serialize(this.element); + }, + + onStart: function() { + this.lastValue = Sortable.serialize(this.element); + }, + + onEnd: function() { + Sortable.unmark(); + if(this.lastValue != Sortable.serialize(this.element)) + this.observer(this.element) + } +} + +var Sortable = { + SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/, + + sortables: {}, + + _findRootElement: function(element) { + while (element.tagName != "BODY") { + if(element.id && Sortable.sortables[element.id]) return element; + element = element.parentNode; + } + }, + + options: function(element) { + element = Sortable._findRootElement($(element)); + if(!element) return; + return Sortable.sortables[element.id]; + }, + + destroy: function(element){ + var s = Sortable.options(element); + + if(s) { + Draggables.removeObserver(s.element); + s.droppables.each(function(d){ Droppables.remove(d) }); + s.draggables.invoke('destroy'); + + delete Sortable.sortables[s.element.id]; + } + }, + + create: function(element) { + element = $(element); + var options = Object.extend({ + element: element, + tag: 'li', // assumes li children, override with tag: 'tagname' + dropOnEmpty: false, + tree: false, + treeTag: 'ul', + overlap: 'vertical', // one of 'vertical', 'horizontal' + constraint: 'vertical', // one of 'vertical', 'horizontal', false + containment: element, // also takes array of elements (or id's); or false + handle: false, // or a CSS class + only: false, + delay: 0, + hoverclass: null, + ghosting: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + format: this.SERIALIZE_RULE, + onChange: Prototype.emptyFunction, + onUpdate: Prototype.emptyFunction + }, arguments[1] || {}); + + // clear any old sortable with same element + this.destroy(element); + + // build options for the draggables + var options_for_draggable = { + revert: true, + scroll: options.scroll, + scrollSpeed: options.scrollSpeed, + scrollSensitivity: options.scrollSensitivity, + delay: options.delay, + ghosting: options.ghosting, + constraint: options.constraint, + handle: options.handle }; + + if(options.starteffect) + options_for_draggable.starteffect = options.starteffect; + + if(options.reverteffect) + options_for_draggable.reverteffect = options.reverteffect; + else + if(options.ghosting) options_for_draggable.reverteffect = function(element) { + element.style.top = 0; + element.style.left = 0; + }; + + if(options.endeffect) + options_for_draggable.endeffect = options.endeffect; + + if(options.zindex) + options_for_draggable.zindex = options.zindex; + + // build options for the droppables + var options_for_droppable = { + overlap: options.overlap, + containment: options.containment, + tree: options.tree, + hoverclass: options.hoverclass, + onHover: Sortable.onHover + //greedy: !options.dropOnEmpty + } + + var options_for_tree = { + onHover: Sortable.onEmptyHover, + overlap: options.overlap, + containment: options.containment, + hoverclass: options.hoverclass + } + + // fix for gecko engine + Element.cleanWhitespace(element); + + options.draggables = []; + options.droppables = []; + + // drop on empty handling + if(options.dropOnEmpty || options.tree) { + Droppables.add(element, options_for_tree); + options.droppables.push(element); + } + + (this.findElements(element, options) || []).each( function(e) { + // handles are per-draggable + var handle = options.handle ? + Element.childrenWithClassName(e, options.handle)[0] : e; + options.draggables.push( + new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); + Droppables.add(e, options_for_droppable); + if(options.tree) e.treeNode = element; + options.droppables.push(e); + }); + + if(options.tree) { + (Sortable.findTreeElements(element, options) || []).each( function(e) { + Droppables.add(e, options_for_tree); + e.treeNode = element; + options.droppables.push(e); + }); + } + + // keep reference + this.sortables[element.id] = options; + + // for onupdate + Draggables.addObserver(new SortableObserver(element, options.onUpdate)); + + }, + + // return all suitable-for-sortable elements in a guaranteed order + findElements: function(element, options) { + return Element.findChildren( + element, options.only, options.tree ? true : false, options.tag); + }, + + findTreeElements: function(element, options) { + return Element.findChildren( + element, options.only, options.tree ? true : false, options.treeTag); + }, + + onHover: function(element, dropon, overlap) { + if(Element.isParent(dropon, element)) return; + + if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) { + return; + } else if(overlap>0.5) { + Sortable.mark(dropon, 'before'); + if(dropon.previousSibling != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, dropon); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } else { + Sortable.mark(dropon, 'after'); + var nextElement = dropon.nextSibling || null; + if(nextElement != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, nextElement); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } + }, + + onEmptyHover: function(element, dropon, overlap) { + var oldParentNode = element.parentNode; + var droponOptions = Sortable.options(dropon); + + if(!Element.isParent(dropon, element)) { + var index; + + var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only}); + var child = null; + + if(children) { + var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap); + + for (index = 0; index < children.length; index += 1) { + if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) { + offset -= Element.offsetSize (children[index], droponOptions.overlap); + } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { + child = index + 1 < children.length ? children[index + 1] : null; + break; + } else { + child = children[index]; + break; + } + } + } + + dropon.insertBefore(element, child); + + Sortable.options(oldParentNode).onChange(element); + droponOptions.onChange(element); + } + }, + + unmark: function() { + if(Sortable._marker) Element.hide(Sortable._marker); + }, + + mark: function(dropon, position) { + // mark on ghosting only + var sortable = Sortable.options(dropon.parentNode); + if(sortable && !sortable.ghosting) return; + + if(!Sortable._marker) { + Sortable._marker = $('dropmarker') || document.createElement('DIV'); + Element.hide(Sortable._marker); + Element.addClassName(Sortable._marker, 'dropmarker'); + Sortable._marker.style.position = 'absolute'; + document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); + } + var offsets = Position.cumulativeOffset(dropon); + Sortable._marker.style.left = offsets[0] + 'px'; + Sortable._marker.style.top = offsets[1] + 'px'; + + if(position=='after') + if(sortable.overlap == 'horizontal') + Sortable._marker.style.left = (offsets[0]+dropon.clientWidth) + 'px'; + else + Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px'; + + Element.show(Sortable._marker); + }, + + _tree: function(element, options, parent) { + var children = Sortable.findElements(element, options) || []; + + for (var i = 0; i < children.length; ++i) { + var match = children[i].id.match(options.format); + + if (!match) continue; + + var child = { + id: encodeURIComponent(match ? match[1] : null), + element: element, + parent: parent, + children: new Array, + position: parent.children.length, + container: Sortable._findChildrenElement(children[i], options.treeTag.toUpperCase()) + } + + /* Get the element containing the children and recurse over it */ + if (child.container) + this._tree(child.container, options, child) + + parent.children.push (child); + } + + return parent; + }, + + /* Finds the first element of the given tag type within a parent element. + Used for finding the first LI[ST] within a L[IST]I[TEM].*/ + _findChildrenElement: function (element, containerTag) { + if (element && element.hasChildNodes) + for (var i = 0; i < element.childNodes.length; ++i) + if (element.childNodes[i].tagName == containerTag) + return element.childNodes[i]; + + return null; + }, + + tree: function(element) { + element = $(element); + var sortableOptions = this.options(element); + var options = Object.extend({ + tag: sortableOptions.tag, + treeTag: sortableOptions.treeTag, + only: sortableOptions.only, + name: element.id, + format: sortableOptions.format + }, arguments[1] || {}); + + var root = { + id: null, + parent: null, + children: new Array, + container: element, + position: 0 + } + + return Sortable._tree (element, options, root); + }, + + /* Construct a [i] index for a particular node */ + _constructIndex: function(node) { + var index = ''; + do { + if (node.id) index = '[' + node.position + ']' + index; + } while ((node = node.parent) != null); + return index; + }, + + sequence: function(element) { + element = $(element); + var options = Object.extend(this.options(element), arguments[1] || {}); + + return $(this.findElements(element, options) || []).map( function(item) { + return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; + }); + }, + + setSequence: function(element, new_sequence) { + element = $(element); + var options = Object.extend(this.options(element), arguments[2] || {}); + + var nodeMap = {}; + this.findElements(element, options).each( function(n) { + if (n.id.match(options.format)) + nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; + n.parentNode.removeChild(n); + }); + + new_sequence.each(function(ident) { + var n = nodeMap[ident]; + if (n) { + n[1].appendChild(n[0]); + delete nodeMap[ident]; + } + }); + }, + + serialize: function(element) { + element = $(element); + var options = Object.extend(Sortable.options(element), arguments[1] || {}); + var name = encodeURIComponent( + (arguments[1] && arguments[1].name) ? arguments[1].name : element.id); + + if (options.tree) { + return Sortable.tree(element, arguments[1]).children.map( function (item) { + return [name + Sortable._constructIndex(item) + "[id]=" + + encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); + }).flatten().join('&'); + } else { + return Sortable.sequence(element, arguments[1]).map( function(item) { + return name + "[]=" + encodeURIComponent(item); + }).join('&'); + } + } +} + +/* Returns true if child is contained within element */ +Element.isParent = function(child, element) { + if (!child.parentNode || child == element) return false; + + if (child.parentNode == element) return true; + + return Element.isParent(child.parentNode, element); +} + +Element.findChildren = function(element, only, recursive, tagName) { + if(!element.hasChildNodes()) return null; + tagName = tagName.toUpperCase(); + if(only) only = [only].flatten(); + var elements = []; + $A(element.childNodes).each( function(e) { + if(e.tagName && e.tagName.toUpperCase()==tagName && + (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) + elements.push(e); + if(recursive) { + var grandchildren = Element.findChildren(e, only, recursive, tagName); + if(grandchildren) elements.push(grandchildren); + } + }); + + return (elements.length>0 ? elements.flatten() : []); +} + +Element.offsetSize = function (element, type) { + if (type == 'vertical' || type == 'height') + return element.offsetHeight; + else + return element.offsetWidth; +} \ No newline at end of file diff --git a/Solar/App/Public/scripts/scriptaculous/effects.js b/Solar/App/Public/scripts/scriptaculous/effects.js index 4f5930f1..8aa6d13c 100755 --- a/Solar/App/Public/scripts/scriptaculous/effects.js +++ b/Solar/App/Public/scripts/scriptaculous/effects.js @@ -1,67 +1,977 @@ -// From script.aculo.us 1.6.2, compressed by jsmin (http://www.crockford.com/javascript/jsmin.html). - -String.prototype.parseColor=function(){var color='#';if(this.slice(0,4)=='rgb('){var cols=this.slice(4,this.length-1).split(',');var i=0;do{color+=parseInt(cols[i]).toColorPart()}while(++i<3);}else{if(this.slice(0,1)=='#'){if(this.length==4)for(var i=1;i<4;i++)color+=(this.charAt(i)+this.charAt(i)).toLowerCase();if(this.length==7)color=this.toLowerCase();}} -return(color.length==7?color:(arguments[0]||this));} -Element.collectTextNodes=function(element){return $A($(element).childNodes).collect(function(node){return(node.nodeType==3?node.nodeValue:(node.hasChildNodes()?Element.collectTextNodes(node):''));}).flatten().join('');} -Element.collectTextNodesIgnoreClass=function(element,className){return $A($(element).childNodes).collect(function(node){return(node.nodeType==3?node.nodeValue:((node.hasChildNodes()&&!Element.hasClassName(node,className))?Element.collectTextNodesIgnoreClass(node,className):''));}).flatten().join('');} -Element.setContentZoom=function(element,percent){element=$(element);Element.setStyle(element,{fontSize:(percent/100)+'em'});if(navigator.appVersion.indexOf('AppleWebKit')>0)window.scrollBy(0,0);} -Element.getOpacity=function(element){var opacity;if(opacity=Element.getStyle(element,'opacity')) -return parseFloat(opacity);if(opacity=(Element.getStyle(element,'filter')||'').match(/alpha\(opacity=(.*)\)/)) -if(opacity[1])return parseFloat(opacity[1])/100;return 1.0;} -Element.setOpacity=function(element,value){element=$(element);if(value==1){Element.setStyle(element,{opacity:(/Gecko/.test(navigator.userAgent)&&!/Konqueror|Safari|KHTML/.test(navigator.userAgent))?0.999999:null});if(/MSIE/.test(navigator.userAgent)) -Element.setStyle(element,{filter:Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')});}else{if(value<0.00001)value=0;Element.setStyle(element,{opacity:value});if(/MSIE/.test(navigator.userAgent)) -Element.setStyle(element,{filter:Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')+'alpha(opacity='+value*100+')'});}} -Element.getInlineOpacity=function(element){return $(element).style.opacity||'';} -Element.childrenWithClassName=function(element,className,findFirst){var classNameRegExp=new RegExp("(^|\\s)"+className+"(\\s|$)");var results=$A($(element).getElementsByTagName('*'))[findFirst?'detect':'select'](function(c){return(c.className&&c.className.match(classNameRegExp));});if(!results)results=[];return results;} -Element.forceRerendering=function(element){try{element=$(element);var n=document.createTextNode(' ');element.appendChild(n);element.removeChild(n);}catch(e){}};Array.prototype.call=function(){var args=arguments;this.each(function(f){f.apply(this,args)});} -var Effect={tagifyText:function(element){if(typeof Builder=='undefined') -throw("Effect.tagifyText requires including script.aculo.us' builder.js library");var tagifyStyle='position:relative';if(/MSIE/.test(navigator.userAgent))tagifyStyle+=';zoom:1';element=$(element);$A(element.childNodes).each(function(child){if(child.nodeType==3){child.nodeValue.toArray().each(function(character){element.insertBefore(Builder.node('span',{style:tagifyStyle},character==' '?String.fromCharCode(160):character),child);});Element.remove(child);}});},multiple:function(element,effect){var elements;if(((typeof element=='object')||(typeof element=='function'))&&(element.length)) -elements=element;else -elements=$(element).childNodes;var options=Object.extend({speed:0.1,delay:0.0},arguments[2]||{});var masterDelay=options.delay;$A(elements).each(function(element,index){new effect(element,Object.extend(options,{delay:index*options.speed+masterDelay}));});},PAIRS:{'slide':['SlideDown','SlideUp'],'blind':['BlindDown','BlindUp'],'appear':['Appear','Fade']},toggle:function(element,effect){element=$(element);effect=(effect||'appear').toLowerCase();var options=Object.extend({queue:{position:'end',scope:(element.id||'global'),limit:1}},arguments[2]||{});Effect[element.visible()?Effect.PAIRS[effect][1]:Effect.PAIRS[effect][0]](element,options);}};var Effect2=Effect;Effect.Transitions={} -Effect.Transitions.linear=Prototype.K;Effect.Transitions.sinoidal=function(pos){return(-Math.cos(pos*Math.PI)/2)+0.5;} -Effect.Transitions.reverse=function(pos){return 1-pos;} -Effect.Transitions.flicker=function(pos){return((-Math.cos(pos*Math.PI)/4)+0.75)+Math.random()/4;} -Effect.Transitions.wobble=function(pos){return(-Math.cos(pos*Math.PI*(9*pos))/2)+0.5;} -Effect.Transitions.pulse=function(pos){return(Math.floor(pos*10)%2==0?(pos*10-Math.floor(pos*10)):1-(pos*10-Math.floor(pos*10)));} -Effect.Transitions.none=function(pos){return 0;} -Effect.Transitions.full=function(pos){return 1;} -Effect.ScopedQueue=Class.create();Object.extend(Object.extend(Effect.ScopedQueue.prototype,Enumerable),{initialize:function(){this.effects=[];this.interval=null;},_each:function(iterator){this.effects._each(iterator);},add:function(effect){var timestamp=new Date().getTime();var position=(typeof effect.options.queue=='string')?effect.options.queue:effect.options.queue.position;switch(position){case'front':this.effects.findAll(function(e){return e.state=='idle'}).each(function(e){e.startOn+=effect.finishOn;e.finishOn+=effect.finishOn;});break;case'end':timestamp=this.effects.pluck('finishOn').max()||timestamp;break;} -effect.startOn+=timestamp;effect.finishOn+=timestamp;if(!effect.options.queue.limit||(this.effects.length=this.startOn){if(timePos>=this.finishOn){this.render(1.0);this.cancel();this.event('beforeFinish');if(this.finish)this.finish();this.event('afterFinish');return;} -var pos=(timePos-this.startOn)/(this.finishOn-this.startOn);var frame=Math.round(pos*this.options.fps*this.options.duration);if(frame>this.currentFrame){this.render(pos);this.currentFrame=frame;}}},render:function(pos){if(this.state=='idle'){this.state='running';this.event('beforeSetup');if(this.setup)this.setup();this.event('afterSetup');} -if(this.state=='running'){if(this.options.transition)pos=this.options.transition(pos);pos*=(this.options.to-this.options.from);pos+=this.options.from;this.position=pos;this.event('beforeUpdate');if(this.update)this.update(pos);this.event('afterUpdate');}},cancel:function(){if(!this.options.sync) -Effect.Queues.get(typeof this.options.queue=='string'?'global':this.options.queue.scope).remove(this);this.state='finished';},event:function(eventName){if(this.options[eventName+'Internal'])this.options[eventName+'Internal'](this);if(this.options[eventName])this.options[eventName](this);},inspect:function(){return'#';}} -Effect.Parallel=Class.create();Object.extend(Object.extend(Effect.Parallel.prototype,Effect.Base.prototype),{initialize:function(effects){this.effects=effects||[];this.start(arguments[1]);},update:function(position){this.effects.invoke('render',position);},finish:function(position){this.effects.each(function(effect){effect.render(1.0);effect.cancel();effect.event('beforeFinish');if(effect.finish)effect.finish(position);effect.event('afterFinish');});}});Effect.Opacity=Class.create();Object.extend(Object.extend(Effect.Opacity.prototype,Effect.Base.prototype),{initialize:function(element){this.element=$(element);if(/MSIE/.test(navigator.userAgent)&&(!this.element.currentStyle.hasLayout)) -this.element.setStyle({zoom:1});var options=Object.extend({from:this.element.getOpacity()||0.0,to:1.0},arguments[1]||{});this.start(options);},update:function(position){this.element.setOpacity(position);}});Effect.Move=Class.create();Object.extend(Object.extend(Effect.Move.prototype,Effect.Base.prototype),{initialize:function(element){this.element=$(element);var options=Object.extend({x:0,y:0,mode:'relative'},arguments[1]||{});this.start(options);},setup:function(){this.element.makePositioned();this.originalLeft=parseFloat(this.element.getStyle('left')||'0');this.originalTop=parseFloat(this.element.getStyle('top')||'0');if(this.options.mode=='absolute'){this.options.x=this.options.x-this.originalLeft;this.options.y=this.options.y-this.originalTop;}},update:function(position){this.element.setStyle({left:Math.round(this.options.x*position+this.originalLeft)+'px',top:Math.round(this.options.y*position+this.originalTop)+'px'});}});Effect.MoveBy=function(element,toTop,toLeft){return new Effect.Move(element,Object.extend({x:toLeft,y:toTop},arguments[3]||{}));};Effect.Scale=Class.create();Object.extend(Object.extend(Effect.Scale.prototype,Effect.Base.prototype),{initialize:function(element,percent){this.element=$(element) -var options=Object.extend({scaleX:true,scaleY:true,scaleContent:true,scaleFromCenter:false,scaleMode:'box',scaleFrom:100.0,scaleTo:percent},arguments[2]||{});this.start(options);},setup:function(){this.restoreAfterFinish=this.options.restoreAfterFinish||false;this.elementPositioning=this.element.getStyle('position');this.originalStyle={};['top','left','width','height','fontSize'].each(function(k){this.originalStyle[k]=this.element.style[k];}.bind(this));this.originalTop=this.element.offsetTop;this.originalLeft=this.element.offsetLeft;var fontSize=this.element.getStyle('font-size')||'100%';['em','px','%','pt'].each(function(fontSizeType){if(fontSize.indexOf(fontSizeType)>0){this.fontSize=parseFloat(fontSize);this.fontSizeType=fontSizeType;}}.bind(this));this.factor=(this.options.scaleTo-this.options.scaleFrom)/100;this.dims=null;if(this.options.scaleMode=='box') -this.dims=[this.element.offsetHeight,this.element.offsetWidth];if(/^content/.test(this.options.scaleMode)) -this.dims=[this.element.scrollHeight,this.element.scrollWidth];if(!this.dims) -this.dims=[this.options.scaleMode.originalHeight,this.options.scaleMode.originalWidth];},update:function(position){var currentScale=(this.options.scaleFrom/100.0)+(this.factor*position);if(this.options.scaleContent&&this.fontSize) -this.element.setStyle({fontSize:this.fontSize*currentScale+this.fontSizeType});this.setDimensions(this.dims[0]*currentScale,this.dims[1]*currentScale);},finish:function(position){if(this.restoreAfterFinish)this.element.setStyle(this.originalStyle);},setDimensions:function(height,width){var d={};if(this.options.scaleX)d.width=Math.round(width)+'px';if(this.options.scaleY)d.height=Math.round(height)+'px';if(this.options.scaleFromCenter){var topd=(height-this.dims[0])/2;var leftd=(width-this.dims[1])/2;if(this.elementPositioning=='absolute'){if(this.options.scaleY)d.top=this.originalTop-topd+'px';if(this.options.scaleX)d.left=this.originalLeft-leftd+'px';}else{if(this.options.scaleY)d.top=-topd+'px';if(this.options.scaleX)d.left=-leftd+'px';}} -this.element.setStyle(d);}});Effect.Highlight=Class.create();Object.extend(Object.extend(Effect.Highlight.prototype,Effect.Base.prototype),{initialize:function(element){this.element=$(element);var options=Object.extend({startcolor:'#ffff99'},arguments[1]||{});this.start(options);},setup:function(){if(this.element.getStyle('display')=='none'){this.cancel();return;} -this.oldStyle={backgroundImage:this.element.getStyle('background-image')};this.element.setStyle({backgroundImage:'none'});if(!this.options.endcolor) -this.options.endcolor=this.element.getStyle('background-color').parseColor('#ffffff');if(!this.options.restorecolor) -this.options.restorecolor=this.element.getStyle('background-color');this._base=$R(0,2).map(function(i){return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16)}.bind(this));this._delta=$R(0,2).map(function(i){return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i]}.bind(this));},update:function(position){this.element.setStyle({backgroundColor:$R(0,2).inject('#',function(m,v,i){return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart());}.bind(this))});},finish:function(){this.element.setStyle(Object.extend(this.oldStyle,{backgroundColor:this.options.restorecolor}));}});Effect.ScrollTo=Class.create();Object.extend(Object.extend(Effect.ScrollTo.prototype,Effect.Base.prototype),{initialize:function(element){this.element=$(element);this.start(arguments[1]||{});},setup:function(){Position.prepare();var offsets=Position.cumulativeOffset(this.element);if(this.options.offset)offsets[1]+=this.options.offset;var max=window.innerHeight?window.height-window.innerHeight:document.body.scrollHeight- -(document.documentElement.clientHeight?document.documentElement.clientHeight:document.body.clientHeight);this.scrollStart=Position.deltaY;this.delta=(offsets[1]>max?max:offsets[1])-this.scrollStart;},update:function(position){Position.prepare();window.scrollTo(Position.deltaX,this.scrollStart+(position*this.delta));}});Effect.Fade=function(element){element=$(element);var oldOpacity=element.getInlineOpacity();var options=Object.extend({from:element.getOpacity()||1.0,to:0.0,afterFinishInternal:function(effect){if(effect.options.to!=0)return;effect.element.hide();effect.element.setStyle({opacity:oldOpacity});}},arguments[1]||{});return new Effect.Opacity(element,options);} -Effect.Appear=function(element){element=$(element);var options=Object.extend({from:(element.getStyle('display')=='none'?0.0:element.getOpacity()||0.0),to:1.0,afterFinishInternal:function(effect){effect.element.forceRerendering();},beforeSetup:function(effect){effect.element.setOpacity(effect.options.from);effect.element.show();}},arguments[1]||{});return new Effect.Opacity(element,options);} -Effect.Puff=function(element){element=$(element);var oldStyle={opacity:element.getInlineOpacity(),position:element.getStyle('position')};return new Effect.Parallel([new Effect.Scale(element,200,{sync:true,scaleFromCenter:true,scaleContent:true,restoreAfterFinish:true}),new Effect.Opacity(element,{sync:true,to:0.0})],Object.extend({duration:1.0,beforeSetupInternal:function(effect){effect.effects[0].element.setStyle({position:'absolute'});},afterFinishInternal:function(effect){effect.effects[0].element.hide();effect.effects[0].element.setStyle(oldStyle);}},arguments[1]||{}));} -Effect.BlindUp=function(element){element=$(element);element.makeClipping();return new Effect.Scale(element,0,Object.extend({scaleContent:false,scaleX:false,restoreAfterFinish:true,afterFinishInternal:function(effect){effect.element.hide();effect.element.undoClipping();}},arguments[1]||{}));} -Effect.BlindDown=function(element){element=$(element);var elementDimensions=element.getDimensions();return new Effect.Scale(element,100,Object.extend({scaleContent:false,scaleX:false,scaleFrom:0,scaleMode:{originalHeight:elementDimensions.height,originalWidth:elementDimensions.width},restoreAfterFinish:true,afterSetup:function(effect){effect.element.makeClipping();effect.element.setStyle({height:'0px'});effect.element.show();},afterFinishInternal:function(effect){effect.element.undoClipping();}},arguments[1]||{}));} -Effect.SwitchOff=function(element){element=$(element);var oldOpacity=element.getInlineOpacity();return new Effect.Appear(element,Object.extend({duration:0.4,from:0,transition:Effect.Transitions.flicker,afterFinishInternal:function(effect){new Effect.Scale(effect.element,1,{duration:0.3,scaleFromCenter:true,scaleX:false,scaleContent:false,restoreAfterFinish:true,beforeSetup:function(effect){effect.element.makePositioned();effect.element.makeClipping();},afterFinishInternal:function(effect){effect.element.hide();effect.element.undoClipping();effect.element.undoPositioned();effect.element.setStyle({opacity:oldOpacity});}})}},arguments[1]||{}));} -Effect.DropOut=function(element){element=$(element);var oldStyle={top:element.getStyle('top'),left:element.getStyle('left'),opacity:element.getInlineOpacity()};return new Effect.Parallel([new Effect.Move(element,{x:0,y:100,sync:true}),new Effect.Opacity(element,{sync:true,to:0.0})],Object.extend({duration:0.5,beforeSetup:function(effect){effect.effects[0].element.makePositioned();},afterFinishInternal:function(effect){effect.effects[0].element.hide();effect.effects[0].element.undoPositioned();effect.effects[0].element.setStyle(oldStyle);}},arguments[1]||{}));} -Effect.Shake=function(element){element=$(element);var oldStyle={top:element.getStyle('top'),left:element.getStyle('left')};return new Effect.Move(element,{x:20,y:0,duration:0.05,afterFinishInternal:function(effect){new Effect.Move(effect.element,{x:-40,y:0,duration:0.1,afterFinishInternal:function(effect){new Effect.Move(effect.element,{x:40,y:0,duration:0.1,afterFinishInternal:function(effect){new Effect.Move(effect.element,{x:-40,y:0,duration:0.1,afterFinishInternal:function(effect){new Effect.Move(effect.element,{x:40,y:0,duration:0.1,afterFinishInternal:function(effect){new Effect.Move(effect.element,{x:-20,y:0,duration:0.05,afterFinishInternal:function(effect){effect.element.undoPositioned();effect.element.setStyle(oldStyle);}})}})}})}})}})}});} -Effect.SlideDown=function(element){element=$(element);element.cleanWhitespace();var oldInnerBottom=$(element.firstChild).getStyle('bottom');var elementDimensions=element.getDimensions();return new Effect.Scale(element,100,Object.extend({scaleContent:false,scaleX:false,scaleFrom:window.opera?0:1,scaleMode:{originalHeight:elementDimensions.height,originalWidth:elementDimensions.width},restoreAfterFinish:true,afterSetup:function(effect){effect.element.makePositioned();effect.element.firstChild.makePositioned();if(window.opera)effect.element.setStyle({top:''});effect.element.makeClipping();effect.element.setStyle({height:'0px'});effect.element.show();},afterUpdateInternal:function(effect){effect.element.firstChild.setStyle({bottom:(effect.dims[0]-effect.element.clientHeight)+'px'});},afterFinishInternal:function(effect){effect.element.undoClipping();if(/MSIE/.test(navigator.userAgent)){effect.element.undoPositioned();effect.element.firstChild.undoPositioned();}else{effect.element.firstChild.undoPositioned();effect.element.undoPositioned();} -effect.element.firstChild.setStyle({bottom:oldInnerBottom});}},arguments[1]||{}));} -Effect.SlideUp=function(element){element=$(element);element.cleanWhitespace();var oldInnerBottom=$(element.firstChild).getStyle('bottom');return new Effect.Scale(element,window.opera?0:1,Object.extend({scaleContent:false,scaleX:false,scaleMode:'box',scaleFrom:100,restoreAfterFinish:true,beforeStartInternal:function(effect){effect.element.makePositioned();effect.element.firstChild.makePositioned();if(window.opera)effect.element.setStyle({top:''});effect.element.makeClipping();effect.element.show();},afterUpdateInternal:function(effect){effect.element.firstChild.setStyle({bottom:(effect.dims[0]-effect.element.clientHeight)+'px'});},afterFinishInternal:function(effect){effect.element.hide();effect.element.undoClipping();effect.element.firstChild.undoPositioned();effect.element.undoPositioned();effect.element.setStyle({bottom:oldInnerBottom});}},arguments[1]||{}));} -Effect.Squish=function(element){return new Effect.Scale(element,window.opera?1:0,{restoreAfterFinish:true,beforeSetup:function(effect){effect.element.makeClipping(effect.element);},afterFinishInternal:function(effect){effect.element.hide(effect.element);effect.element.undoClipping(effect.element);}});} -Effect.Grow=function(element){element=$(element);var options=Object.extend({direction:'center',moveTransition:Effect.Transitions.sinoidal,scaleTransition:Effect.Transitions.sinoidal,opacityTransition:Effect.Transitions.full},arguments[1]||{});var oldStyle={top:element.style.top,left:element.style.left,height:element.style.height,width:element.style.width,opacity:element.getInlineOpacity()};var dims=element.getDimensions();var initialMoveX,initialMoveY;var moveX,moveY;switch(options.direction){case'top-left':initialMoveX=initialMoveY=moveX=moveY=0;break;case'top-right':initialMoveX=dims.width;initialMoveY=moveY=0;moveX=-dims.width;break;case'bottom-left':initialMoveX=moveX=0;initialMoveY=dims.height;moveY=-dims.height;break;case'bottom-right':initialMoveX=dims.width;initialMoveY=dims.height;moveX=-dims.width;moveY=-dims.height;break;case'center':initialMoveX=dims.width/2;initialMoveY=dims.height/2;moveX=-dims.width/2;moveY=-dims.height/2;break;} -return new Effect.Move(element,{x:initialMoveX,y:initialMoveY,duration:0.01,beforeSetup:function(effect){effect.element.hide();effect.element.makeClipping();effect.element.makePositioned();},afterFinishInternal:function(effect){new Effect.Parallel([new Effect.Opacity(effect.element,{sync:true,to:1.0,from:0.0,transition:options.opacityTransition}),new Effect.Move(effect.element,{x:moveX,y:moveY,sync:true,transition:options.moveTransition}),new Effect.Scale(effect.element,100,{scaleMode:{originalHeight:dims.height,originalWidth:dims.width},sync:true,scaleFrom:window.opera?1:0,transition:options.scaleTransition,restoreAfterFinish:true})],Object.extend({beforeSetup:function(effect){effect.effects[0].element.setStyle({height:'0px'});effect.effects[0].element.show();},afterFinishInternal:function(effect){effect.effects[0].element.undoClipping();effect.effects[0].element.undoPositioned();effect.effects[0].element.setStyle(oldStyle);}},options))}});} -Effect.Shrink=function(element){element=$(element);var options=Object.extend({direction:'center',moveTransition:Effect.Transitions.sinoidal,scaleTransition:Effect.Transitions.sinoidal,opacityTransition:Effect.Transitions.none},arguments[1]||{});var oldStyle={top:element.style.top,left:element.style.left,height:element.style.height,width:element.style.width,opacity:element.getInlineOpacity()};var dims=element.getDimensions();var moveX,moveY;switch(options.direction){case'top-left':moveX=moveY=0;break;case'top-right':moveX=dims.width;moveY=0;break;case'bottom-left':moveX=0;moveY=dims.height;break;case'bottom-right':moveX=dims.width;moveY=dims.height;break;case'center':moveX=dims.width/2;moveY=dims.height/2;break;} -return new Effect.Parallel([new Effect.Opacity(element,{sync:true,to:0.0,from:1.0,transition:options.opacityTransition}),new Effect.Scale(element,window.opera?1:0,{sync:true,transition:options.scaleTransition,restoreAfterFinish:true}),new Effect.Move(element,{x:moveX,y:moveY,sync:true,transition:options.moveTransition})],Object.extend({beforeStartInternal:function(effect){effect.effects[0].element.makePositioned();effect.effects[0].element.makeClipping();},afterFinishInternal:function(effect){effect.effects[0].element.hide();effect.effects[0].element.undoClipping();effect.effects[0].element.undoPositioned();effect.effects[0].element.setStyle(oldStyle);}},options));} -Effect.Pulsate=function(element){element=$(element);var options=arguments[1]||{};var oldOpacity=element.getInlineOpacity();var transition=options.transition||Effect.Transitions.sinoidal;var reverser=function(pos){return transition(1-Effect.Transitions.pulse(pos))};reverser.bind(transition);return new Effect.Opacity(element,Object.extend(Object.extend({duration:3.0,from:0,afterFinishInternal:function(effect){effect.element.setStyle({opacity:oldOpacity});}},options),{transition:reverser}));} -Effect.Fold=function(element){element=$(element);var oldStyle={top:element.style.top,left:element.style.left,width:element.style.width,height:element.style.height};Element.makeClipping(element);return new Effect.Scale(element,5,Object.extend({scaleContent:false,scaleX:false,afterFinishInternal:function(effect){new Effect.Scale(element,1,{scaleContent:false,scaleY:false,afterFinishInternal:function(effect){effect.element.hide();effect.element.undoClipping();effect.element.setStyle(oldStyle);}});}},arguments[1]||{}));};['setOpacity','getOpacity','getInlineOpacity','forceRerendering','setContentZoom','collectTextNodes','collectTextNodesIgnoreClass','childrenWithClassName'].each(function(f){Element.Methods[f]=Element[f];});Element.Methods.visualEffect=function(element,effect,options){s=effect.gsub(/_/,'-').camelize();effect_class=s.charAt(0).toUpperCase()+s.substring(1);new Effect[effect_class](element,options);return $(element);};Element.addMethods(); \ No newline at end of file +// script.aculo.us effects.js v1.6.4, Wed Sep 06 11:30:58 CEST 2006 + +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// Contributors: +// Justin Palmer (http://encytemedia.com/) +// Mark Pilgrim (http://diveintomark.org/) +// Martin Bialasinki +// +// See scriptaculous.js for full license. + +// converts rgb() and #xxx to #xxxxxx format, +// returns self (or first argument) if not convertable +String.prototype.parseColor = function() { + var color = '#'; + if(this.slice(0,4) == 'rgb(') { + var cols = this.slice(4,this.length-1).split(','); + var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3); + } else { + if(this.slice(0,1) == '#') { + if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); + if(this.length==7) color = this.toLowerCase(); + } + } + return(color.length==7 ? color : (arguments[0] || this)); +} + +/*--------------------------------------------------------------------------*/ + +Element.collectTextNodes = function(element) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + (node.hasChildNodes() ? Element.collectTextNodes(node) : '')); + }).flatten().join(''); +} + +Element.collectTextNodesIgnoreClass = function(element, className) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? + Element.collectTextNodesIgnoreClass(node, className) : '')); + }).flatten().join(''); +} + +Element.setContentZoom = function(element, percent) { + element = $(element); + Element.setStyle(element, {fontSize: (percent/100) + 'em'}); + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); +} + +Element.getOpacity = function(element){ + var opacity; + if (opacity = Element.getStyle(element, 'opacity')) + return parseFloat(opacity); + if (opacity = (Element.getStyle(element, 'filter') || '').match(/alpha\(opacity=(.*)\)/)) + if(opacity[1]) return parseFloat(opacity[1]) / 100; + return 1.0; +} + +Element.setOpacity = function(element, value){ + element= $(element); + if (value == 1){ + Element.setStyle(element, { opacity: + (/Gecko/.test(navigator.userAgent) && !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? + 0.999999 : 1.0 }); + if(/MSIE/.test(navigator.userAgent) && !window.opera) + Element.setStyle(element, {filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')}); + } else { + if(value < 0.00001) value = 0; + Element.setStyle(element, {opacity: value}); + if(/MSIE/.test(navigator.userAgent) && !window.opera) + Element.setStyle(element, + { filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') + + 'alpha(opacity='+value*100+')' }); + } +} + +Element.getInlineOpacity = function(element){ + return $(element).style.opacity || ''; +} + +Element.childrenWithClassName = function(element, className, findFirst) { + var classNameRegExp = new RegExp("(^|\\s)" + className + "(\\s|$)"); + var results = $A($(element).getElementsByTagName('*'))[findFirst ? 'detect' : 'select']( function(c) { + return (c.className && c.className.match(classNameRegExp)); + }); + if(!results) results = []; + return results; +} + +Element.forceRerendering = function(element) { + try { + element = $(element); + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch(e) { } +}; + +/*--------------------------------------------------------------------------*/ + +Array.prototype.call = function() { + var args = arguments; + this.each(function(f){ f.apply(this, args) }); +} + +/*--------------------------------------------------------------------------*/ + +var Effect = { + _elementDoesNotExistError: { + name: 'ElementDoesNotExistError', + message: 'The specified DOM element does not exist, but is required for this effect to operate' + }, + tagifyText: function(element) { + if(typeof Builder == 'undefined') + throw("Effect.tagifyText requires including script.aculo.us' builder.js library"); + + var tagifyStyle = 'position:relative'; + if(/MSIE/.test(navigator.userAgent) && !window.opera) tagifyStyle += ';zoom:1'; + element = $(element); + $A(element.childNodes).each( function(child) { + if(child.nodeType==3) { + child.nodeValue.toArray().each( function(character) { + element.insertBefore( + Builder.node('span',{style: tagifyStyle}, + character == ' ' ? String.fromCharCode(160) : character), + child); + }); + Element.remove(child); + } + }); + }, + multiple: function(element, effect) { + var elements; + if(((typeof element == 'object') || + (typeof element == 'function')) && + (element.length)) + elements = element; + else + elements = $(element).childNodes; + + var options = Object.extend({ + speed: 0.1, + delay: 0.0 + }, arguments[2] || {}); + var masterDelay = options.delay; + + $A(elements).each( function(element, index) { + new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay })); + }); + }, + PAIRS: { + 'slide': ['SlideDown','SlideUp'], + 'blind': ['BlindDown','BlindUp'], + 'appear': ['Appear','Fade'] + }, + toggle: function(element, effect) { + element = $(element); + effect = (effect || 'appear').toLowerCase(); + var options = Object.extend({ + queue: { position:'end', scope:(element.id || 'global'), limit: 1 } + }, arguments[2] || {}); + Effect[element.visible() ? + Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options); + } +}; + +var Effect2 = Effect; // deprecated + +/* ------------- transitions ------------- */ + +Effect.Transitions = {} + +Effect.Transitions.linear = Prototype.K; + +Effect.Transitions.sinoidal = function(pos) { + return (-Math.cos(pos*Math.PI)/2) + 0.5; +} +Effect.Transitions.reverse = function(pos) { + return 1-pos; +} +Effect.Transitions.flicker = function(pos) { + return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4; +} +Effect.Transitions.wobble = function(pos) { + return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5; +} +Effect.Transitions.pulse = function(pos) { + return (Math.floor(pos*10) % 2 == 0 ? + (pos*10-Math.floor(pos*10)) : 1-(pos*10-Math.floor(pos*10))); +} +Effect.Transitions.none = function(pos) { + return 0; +} +Effect.Transitions.full = function(pos) { + return 1; +} + +/* ------------- core effects ------------- */ + +Effect.ScopedQueue = Class.create(); +Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), { + initialize: function() { + this.effects = []; + this.interval = null; + }, + _each: function(iterator) { + this.effects._each(iterator); + }, + add: function(effect) { + var timestamp = new Date().getTime(); + + var position = (typeof effect.options.queue == 'string') ? + effect.options.queue : effect.options.queue.position; + + switch(position) { + case 'front': + // move unstarted effects after this effect + this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { + e.startOn += effect.finishOn; + e.finishOn += effect.finishOn; + }); + break; + case 'end': + // start effect after last queued effect has finished + timestamp = this.effects.pluck('finishOn').max() || timestamp; + break; + } + + effect.startOn += timestamp; + effect.finishOn += timestamp; + + if(!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit)) + this.effects.push(effect); + + if(!this.interval) + this.interval = setInterval(this.loop.bind(this), 40); + }, + remove: function(effect) { + this.effects = this.effects.reject(function(e) { return e==effect }); + if(this.effects.length == 0) { + clearInterval(this.interval); + this.interval = null; + } + }, + loop: function() { + var timePos = new Date().getTime(); + this.effects.invoke('loop', timePos); + } +}); + +Effect.Queues = { + instances: $H(), + get: function(queueName) { + if(typeof queueName != 'string') return queueName; + + if(!this.instances[queueName]) + this.instances[queueName] = new Effect.ScopedQueue(); + + return this.instances[queueName]; + } +} +Effect.Queue = Effect.Queues.get('global'); + +Effect.DefaultOptions = { + transition: Effect.Transitions.sinoidal, + duration: 1.0, // seconds + fps: 25.0, // max. 25fps due to Effect.Queue implementation + sync: false, // true for combining + from: 0.0, + to: 1.0, + delay: 0.0, + queue: 'parallel' +} + +Effect.Base = function() {}; +Effect.Base.prototype = { + position: null, + start: function(options) { + this.options = Object.extend(Object.extend({},Effect.DefaultOptions), options || {}); + this.currentFrame = 0; + this.state = 'idle'; + this.startOn = this.options.delay*1000; + this.finishOn = this.startOn + (this.options.duration*1000); + this.event('beforeStart'); + if(!this.options.sync) + Effect.Queues.get(typeof this.options.queue == 'string' ? + 'global' : this.options.queue.scope).add(this); + }, + loop: function(timePos) { + if(timePos >= this.startOn) { + if(timePos >= this.finishOn) { + this.render(1.0); + this.cancel(); + this.event('beforeFinish'); + if(this.finish) this.finish(); + this.event('afterFinish'); + return; + } + var pos = (timePos - this.startOn) / (this.finishOn - this.startOn); + var frame = Math.round(pos * this.options.fps * this.options.duration); + if(frame > this.currentFrame) { + this.render(pos); + this.currentFrame = frame; + } + } + }, + render: function(pos) { + if(this.state == 'idle') { + this.state = 'running'; + this.event('beforeSetup'); + if(this.setup) this.setup(); + this.event('afterSetup'); + } + if(this.state == 'running') { + if(this.options.transition) pos = this.options.transition(pos); + pos *= (this.options.to-this.options.from); + pos += this.options.from; + this.position = pos; + this.event('beforeUpdate'); + if(this.update) this.update(pos); + this.event('afterUpdate'); + } + }, + cancel: function() { + if(!this.options.sync) + Effect.Queues.get(typeof this.options.queue == 'string' ? + 'global' : this.options.queue.scope).remove(this); + this.state = 'finished'; + }, + event: function(eventName) { + if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); + if(this.options[eventName]) this.options[eventName](this); + }, + inspect: function() { + return '#'; + } +} + +Effect.Parallel = Class.create(); +Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), { + initialize: function(effects) { + this.effects = effects || []; + this.start(arguments[1]); + }, + update: function(position) { + this.effects.invoke('render', position); + }, + finish: function(position) { + this.effects.each( function(effect) { + effect.render(1.0); + effect.cancel(); + effect.event('beforeFinish'); + if(effect.finish) effect.finish(position); + effect.event('afterFinish'); + }); + } +}); + +Effect.Opacity = Class.create(); +Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + // make this work on IE on elements without 'layout' + if(/MSIE/.test(navigator.userAgent) && !window.opera && (!this.element.currentStyle.hasLayout)) + this.element.setStyle({zoom: 1}); + var options = Object.extend({ + from: this.element.getOpacity() || 0.0, + to: 1.0 + }, arguments[1] || {}); + this.start(options); + }, + update: function(position) { + this.element.setOpacity(position); + } +}); + +Effect.Move = Class.create(); +Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + x: 0, + y: 0, + mode: 'relative' + }, arguments[1] || {}); + this.start(options); + }, + setup: function() { + // Bug in Opera: Opera returns the "real" position of a static element or + // relative element that does not have top/left explicitly set. + // ==> Always set top and left for position relative elements in your stylesheets + // (to 0 if you do not need them) + this.element.makePositioned(); + this.originalLeft = parseFloat(this.element.getStyle('left') || '0'); + this.originalTop = parseFloat(this.element.getStyle('top') || '0'); + if(this.options.mode == 'absolute') { + // absolute movement, so we need to calc deltaX and deltaY + this.options.x = this.options.x - this.originalLeft; + this.options.y = this.options.y - this.originalTop; + } + }, + update: function(position) { + this.element.setStyle({ + left: Math.round(this.options.x * position + this.originalLeft) + 'px', + top: Math.round(this.options.y * position + this.originalTop) + 'px' + }); + } +}); + +// for backwards compatibility +Effect.MoveBy = function(element, toTop, toLeft) { + return new Effect.Move(element, + Object.extend({ x: toLeft, y: toTop }, arguments[3] || {})); +}; + +Effect.Scale = Class.create(); +Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), { + initialize: function(element, percent) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + scaleX: true, + scaleY: true, + scaleContent: true, + scaleFromCenter: false, + scaleMode: 'box', // 'box' or 'contents' or {} with provided values + scaleFrom: 100.0, + scaleTo: percent + }, arguments[2] || {}); + this.start(options); + }, + setup: function() { + this.restoreAfterFinish = this.options.restoreAfterFinish || false; + this.elementPositioning = this.element.getStyle('position'); + + this.originalStyle = {}; + ['top','left','width','height','fontSize'].each( function(k) { + this.originalStyle[k] = this.element.style[k]; + }.bind(this)); + + this.originalTop = this.element.offsetTop; + this.originalLeft = this.element.offsetLeft; + + var fontSize = this.element.getStyle('font-size') || '100%'; + ['em','px','%','pt'].each( function(fontSizeType) { + if(fontSize.indexOf(fontSizeType)>0) { + this.fontSize = parseFloat(fontSize); + this.fontSizeType = fontSizeType; + } + }.bind(this)); + + this.factor = (this.options.scaleTo - this.options.scaleFrom)/100; + + this.dims = null; + if(this.options.scaleMode=='box') + this.dims = [this.element.offsetHeight, this.element.offsetWidth]; + if(/^content/.test(this.options.scaleMode)) + this.dims = [this.element.scrollHeight, this.element.scrollWidth]; + if(!this.dims) + this.dims = [this.options.scaleMode.originalHeight, + this.options.scaleMode.originalWidth]; + }, + update: function(position) { + var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); + if(this.options.scaleContent && this.fontSize) + this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType }); + this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); + }, + finish: function(position) { + if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle); + }, + setDimensions: function(height, width) { + var d = {}; + if(this.options.scaleX) d.width = Math.round(width) + 'px'; + if(this.options.scaleY) d.height = Math.round(height) + 'px'; + if(this.options.scaleFromCenter) { + var topd = (height - this.dims[0])/2; + var leftd = (width - this.dims[1])/2; + if(this.elementPositioning == 'absolute') { + if(this.options.scaleY) d.top = this.originalTop-topd + 'px'; + if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px'; + } else { + if(this.options.scaleY) d.top = -topd + 'px'; + if(this.options.scaleX) d.left = -leftd + 'px'; + } + } + this.element.setStyle(d); + } +}); + +Effect.Highlight = Class.create(); +Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {}); + this.start(options); + }, + setup: function() { + // Prevent executing on elements not in the layout flow + if(this.element.getStyle('display')=='none') { this.cancel(); return; } + // Disable background image during the effect + this.oldStyle = { + backgroundImage: this.element.getStyle('background-image') }; + this.element.setStyle({backgroundImage: 'none'}); + if(!this.options.endcolor) + this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff'); + if(!this.options.restorecolor) + this.options.restorecolor = this.element.getStyle('background-color'); + // init color calculations + this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this)); + this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this)); + }, + update: function(position) { + this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){ + return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) }); + }, + finish: function() { + this.element.setStyle(Object.extend(this.oldStyle, { + backgroundColor: this.options.restorecolor + })); + } +}); + +Effect.ScrollTo = Class.create(); +Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + this.start(arguments[1] || {}); + }, + setup: function() { + Position.prepare(); + var offsets = Position.cumulativeOffset(this.element); + if(this.options.offset) offsets[1] += this.options.offset; + var max = window.innerHeight ? + window.height - window.innerHeight : + document.body.scrollHeight - + (document.documentElement.clientHeight ? + document.documentElement.clientHeight : document.body.clientHeight); + this.scrollStart = Position.deltaY; + this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart; + }, + update: function(position) { + Position.prepare(); + window.scrollTo(Position.deltaX, + this.scrollStart + (position*this.delta)); + } +}); + +/* ------------- combination effects ------------- */ + +Effect.Fade = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + var options = Object.extend({ + from: element.getOpacity() || 1.0, + to: 0.0, + afterFinishInternal: function(effect) { + if(effect.options.to!=0) return; + effect.element.hide(); + effect.element.setStyle({opacity: oldOpacity}); + }}, arguments[1] || {}); + return new Effect.Opacity(element,options); +} + +Effect.Appear = function(element) { + element = $(element); + var options = Object.extend({ + from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0), + to: 1.0, + // force Safari to render floated elements properly + afterFinishInternal: function(effect) { + effect.element.forceRerendering(); + }, + beforeSetup: function(effect) { + effect.element.setOpacity(effect.options.from); + effect.element.show(); + }}, arguments[1] || {}); + return new Effect.Opacity(element,options); +} + +Effect.Puff = function(element) { + element = $(element); + var oldStyle = { + opacity: element.getInlineOpacity(), + position: element.getStyle('position'), + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height + }; + return new Effect.Parallel( + [ new Effect.Scale(element, 200, + { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], + Object.extend({ duration: 1.0, + beforeSetupInternal: function(effect) { + Position.absolutize(effect.effects[0].element) + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide(); + effect.effects[0].element.setStyle(oldStyle); } + }, arguments[1] || {}) + ); +} + +Effect.BlindUp = function(element) { + element = $(element); + element.makeClipping(); + return new Effect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + restoreAfterFinish: true, + afterFinishInternal: function(effect) { + effect.element.hide(); + effect.element.undoClipping(); + } + }, arguments[1] || {}) + ); +} + +Effect.BlindDown = function(element) { + element = $(element); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, + scaleFrom: 0, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makeClipping(); + effect.element.setStyle({height: '0px'}); + effect.element.show(); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping(); + } + }, arguments[1] || {})); +} + +Effect.SwitchOff = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + return new Effect.Appear(element, Object.extend({ + duration: 0.4, + from: 0, + transition: Effect.Transitions.flicker, + afterFinishInternal: function(effect) { + new Effect.Scale(effect.element, 1, { + duration: 0.3, scaleFromCenter: true, + scaleX: false, scaleContent: false, restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makePositioned(); + effect.element.makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide(); + effect.element.undoClipping(); + effect.element.undoPositioned(); + effect.element.setStyle({opacity: oldOpacity}); + } + }) + } + }, arguments[1] || {})); +} + +Effect.DropOut = function(element) { + element = $(element); + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left'), + opacity: element.getInlineOpacity() }; + return new Effect.Parallel( + [ new Effect.Move(element, {x: 0, y: 100, sync: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 }) ], + Object.extend( + { duration: 0.5, + beforeSetup: function(effect) { + effect.effects[0].element.makePositioned(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide(); + effect.effects[0].element.undoPositioned(); + effect.effects[0].element.setStyle(oldStyle); + } + }, arguments[1] || {})); +} + +Effect.Shake = function(element) { + element = $(element); + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left') }; + return new Effect.Move(element, + { x: 20, y: 0, duration: 0.05, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) { + effect.element.undoPositioned(); + effect.element.setStyle(oldStyle); + }}) }}) }}) }}) }}) }}); +} + +Effect.SlideDown = function(element) { + element = $(element); + element.cleanWhitespace(); + // SlideDown need to have the content of the element wrapped in a container element with fixed height! + var oldInnerBottom = $(element.firstChild).getStyle('bottom'); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, + scaleFrom: window.opera ? 0 : 1, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makePositioned(); + effect.element.firstChild.makePositioned(); + if(window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping(); + effect.element.setStyle({height: '0px'}); + effect.element.show(); }, + afterUpdateInternal: function(effect) { + effect.element.firstChild.setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping(); + // IE will crash if child is undoPositioned first + if(/MSIE/.test(navigator.userAgent) && !window.opera){ + effect.element.undoPositioned(); + effect.element.firstChild.undoPositioned(); + }else{ + effect.element.firstChild.undoPositioned(); + effect.element.undoPositioned(); + } + effect.element.firstChild.setStyle({bottom: oldInnerBottom}); } + }, arguments[1] || {}) + ); +} + +Effect.SlideUp = function(element) { + element = $(element); + element.cleanWhitespace(); + var oldInnerBottom = $(element.firstChild).getStyle('bottom'); + return new Effect.Scale(element, window.opera ? 0 : 1, + Object.extend({ scaleContent: false, + scaleX: false, + scaleMode: 'box', + scaleFrom: 100, + restoreAfterFinish: true, + beforeStartInternal: function(effect) { + effect.element.makePositioned(); + effect.element.firstChild.makePositioned(); + if(window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping(); + effect.element.show(); }, + afterUpdateInternal: function(effect) { + effect.element.firstChild.setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); }, + afterFinishInternal: function(effect) { + effect.element.hide(); + effect.element.undoClipping(); + effect.element.firstChild.undoPositioned(); + effect.element.undoPositioned(); + effect.element.setStyle({bottom: oldInnerBottom}); } + }, arguments[1] || {}) + ); +} + +// Bug in opera makes the TD containing this element expand for a instance after finish +Effect.Squish = function(element) { + return new Effect.Scale(element, window.opera ? 1 : 0, + { restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makeClipping(effect.element); }, + afterFinishInternal: function(effect) { + effect.element.hide(effect.element); + effect.element.undoClipping(effect.element); } + }); +} + +Effect.Grow = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.full + }, arguments[1] || {}); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var initialMoveX, initialMoveY; + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + initialMoveX = initialMoveY = moveX = moveY = 0; + break; + case 'top-right': + initialMoveX = dims.width; + initialMoveY = moveY = 0; + moveX = -dims.width; + break; + case 'bottom-left': + initialMoveX = moveX = 0; + initialMoveY = dims.height; + moveY = -dims.height; + break; + case 'bottom-right': + initialMoveX = dims.width; + initialMoveY = dims.height; + moveX = -dims.width; + moveY = -dims.height; + break; + case 'center': + initialMoveX = dims.width / 2; + initialMoveY = dims.height / 2; + moveX = -dims.width / 2; + moveY = -dims.height / 2; + break; + } + + return new Effect.Move(element, { + x: initialMoveX, + y: initialMoveY, + duration: 0.01, + beforeSetup: function(effect) { + effect.element.hide(); + effect.element.makeClipping(); + effect.element.makePositioned(); + }, + afterFinishInternal: function(effect) { + new Effect.Parallel( + [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }), + new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }), + new Effect.Scale(effect.element, 100, { + scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, + sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) + ], Object.extend({ + beforeSetup: function(effect) { + effect.effects[0].element.setStyle({height: '0px'}); + effect.effects[0].element.show(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.undoClipping(); + effect.effects[0].element.undoPositioned(); + effect.effects[0].element.setStyle(oldStyle); + } + }, options) + ) + } + }); +} + +Effect.Shrink = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.none + }, arguments[1] || {}); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + moveX = moveY = 0; + break; + case 'top-right': + moveX = dims.width; + moveY = 0; + break; + case 'bottom-left': + moveX = 0; + moveY = dims.height; + break; + case 'bottom-right': + moveX = dims.width; + moveY = dims.height; + break; + case 'center': + moveX = dims.width / 2; + moveY = dims.height / 2; + break; + } + + return new Effect.Parallel( + [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }), + new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}), + new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }) + ], Object.extend({ + beforeStartInternal: function(effect) { + effect.effects[0].element.makePositioned(); + effect.effects[0].element.makeClipping(); }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide(); + effect.effects[0].element.undoClipping(); + effect.effects[0].element.undoPositioned(); + effect.effects[0].element.setStyle(oldStyle); } + }, options) + ); +} + +Effect.Pulsate = function(element) { + element = $(element); + var options = arguments[1] || {}; + var oldOpacity = element.getInlineOpacity(); + var transition = options.transition || Effect.Transitions.sinoidal; + var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) }; + reverser.bind(transition); + return new Effect.Opacity(element, + Object.extend(Object.extend({ duration: 3.0, from: 0, + afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); } + }, options), {transition: reverser})); +} + +Effect.Fold = function(element) { + element = $(element); + var oldStyle = { + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height }; + Element.makeClipping(element); + return new Effect.Scale(element, 5, Object.extend({ + scaleContent: false, + scaleX: false, + afterFinishInternal: function(effect) { + new Effect.Scale(element, 1, { + scaleContent: false, + scaleY: false, + afterFinishInternal: function(effect) { + effect.element.hide(); + effect.element.undoClipping(); + effect.element.setStyle(oldStyle); + } }); + }}, arguments[1] || {})); +}; + +['setOpacity','getOpacity','getInlineOpacity','forceRerendering','setContentZoom', + 'collectTextNodes','collectTextNodesIgnoreClass','childrenWithClassName'].each( + function(f) { Element.Methods[f] = Element[f]; } +); + +Element.Methods.visualEffect = function(element, effect, options) { + s = effect.gsub(/_/, '-').camelize(); + effect_class = s.charAt(0).toUpperCase() + s.substring(1); + new Effect[effect_class](element, options); + return $(element); +}; + +Element.addMethods(); \ No newline at end of file diff --git a/Solar/App/Public/scripts/scriptaculous/scriptaculous.js b/Solar/App/Public/scripts/scriptaculous/scriptaculous.js index c8e4e55d..42d5dcea 100755 --- a/Solar/App/Public/scripts/scriptaculous/scriptaculous.js +++ b/Solar/App/Public/scripts/scriptaculous/scriptaculous.js @@ -1,6 +1,49 @@ -// From script.aculo.us 1.6.2, compressed by jsmin (http://www.crockford.com/javascript/jsmin.html). +// script.aculo.us scriptaculous.js v1.6.4, Wed Sep 06 11:30:58 CEST 2006 + +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// +// 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 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. + +var Scriptaculous = { + Version: '1.6.4', + require: function(libraryName) { + // inserting via DOM fails in Safari 2.0, so brute force approach + document.write(''); + }, + load: function() { + if((typeof Prototype=='undefined') || + (typeof Element == 'undefined') || + (typeof Element.Methods=='undefined') || + parseFloat(Prototype.Version.split(".")[0] + "." + + Prototype.Version.split(".")[1]) < 1.5) + throw("script.aculo.us requires the Prototype JavaScript framework >= 1.5.0"); + + $A(document.getElementsByTagName("script")).findAll( function(s) { + return (s.src && s.src.match(/scriptaculous\.js(\?.*)?$/)) + }).each( function(s) { + var path = s.src.replace(/scriptaculous\.js(\?.*)?$/,''); + var includes = s.src.match(/\?.*load=([a-z,]*)/); + (includes ? includes[1] : 'builder,effects,dragdrop,controls,slider').split(',').each( + function(include) { Scriptaculous.require(path+include+'.js') }); + }); + } +} -var Scriptaculous={Version:'1.6.2',require:function(libraryName){document.write('');},load:function(){if((typeof Prototype=='undefined')||(typeof Element=='undefined')||(typeof Element.Methods=='undefined')||parseFloat(Prototype.Version.split(".")[0]+"."+ -Prototype.Version.split(".")[1])<1.5) -throw("script.aculo.us requires the Prototype JavaScript framework >= 1.5.0");$A(document.getElementsByTagName("script")).findAll(function(s){return(s.src&&s.src.match(/scriptaculous\.js(\?.*)?$/))}).each(function(s){var path=s.src.replace(/scriptaculous\.js(\?.*)?$/,'');var includes=s.src.match(/\?.*load=([a-z,]*)/);(includes?includes[1]:'builder,effects,dragdrop,controls,slider').split(',').each(function(include){Scriptaculous.require(path+include+'.js')});});}} Scriptaculous.load(); \ No newline at end of file diff --git a/Solar/App/Public/scripts/scriptaculous/slider.js b/Solar/App/Public/scripts/scriptaculous/slider.js index 61856bf3..859ce1d9 100755 --- a/Solar/App/Public/scripts/scriptaculous/slider.js +++ b/Solar/App/Public/scripts/scriptaculous/slider.js @@ -1,18 +1,294 @@ -// From script.aculo.us 1.6.2, compressed by jsmin (http://www.crockford.com/javascript/jsmin.html). - -if(!Control)var Control={};Control.Slider=Class.create();Control.Slider.prototype={initialize:function(handle,track,options){var slider=this;if(handle instanceof Array){this.handles=handle.collect(function(e){return $(e)});}else{this.handles=[$(handle)];} -this.track=$(track);this.options=options||{};this.axis=this.options.axis||'horizontal';this.increment=this.options.increment||1;this.step=parseInt(this.options.step||'1');this.range=this.options.range||$R(0,1);this.value=0;this.values=this.handles.map(function(){return 0});this.spans=this.options.spans?this.options.spans.map(function(s){return $(s)}):false;this.options.startSpan=$(this.options.startSpan||null);this.options.endSpan=$(this.options.endSpan||null);this.restricted=this.options.restricted||false;this.maximum=this.options.maximum||this.range.end;this.minimum=this.options.minimum||this.range.start;this.alignX=parseInt(this.options.alignX||'0');this.alignY=parseInt(this.options.alignY||'0');this.trackLength=this.maximumOffset()-this.minimumOffset();this.handleLength=this.isVertical()?(this.handles[0].offsetHeight!=0?this.handles[0].offsetHeight:this.handles[0].style.height.replace(/px$/,"")):(this.handles[0].offsetWidth!=0?this.handles[0].offsetWidth:this.handles[0].style.width.replace(/px$/,""));this.active=false;this.dragging=false;this.disabled=false;if(this.options.disabled)this.setDisabled();this.allowedValues=this.options.values?this.options.values.sortBy(Prototype.K):false;if(this.allowedValues){this.minimum=this.allowedValues.min();this.maximum=this.allowedValues.max();} -this.eventMouseDown=this.startDrag.bindAsEventListener(this);this.eventMouseUp=this.endDrag.bindAsEventListener(this);this.eventMouseMove=this.update.bindAsEventListener(this);this.handles.each(function(h,i){i=slider.handles.length-1-i;slider.setValue(parseFloat((slider.options.sliderValue instanceof Array?slider.options.sliderValue[i]:slider.options.sliderValue)||slider.range.start),i);Element.makePositioned(h);Event.observe(h,"mousedown",slider.eventMouseDown);});Event.observe(this.track,"mousedown",this.eventMouseDown);Event.observe(document,"mouseup",this.eventMouseUp);Event.observe(document,"mousemove",this.eventMouseMove);this.initialized=true;},dispose:function(){var slider=this;Event.stopObserving(this.track,"mousedown",this.eventMouseDown);Event.stopObserving(document,"mouseup",this.eventMouseUp);Event.stopObserving(document,"mousemove",this.eventMouseMove);this.handles.each(function(h){Event.stopObserving(h,"mousedown",slider.eventMouseDown);});},setDisabled:function(){this.disabled=true;},setEnabled:function(){this.disabled=false;},getNearestValue:function(value){if(this.allowedValues){if(value>=this.allowedValues.max())return(this.allowedValues.max());if(value<=this.allowedValues.min())return(this.allowedValues.min());var offset=Math.abs(this.allowedValues[0]-value);var newValue=this.allowedValues[0];this.allowedValues.each(function(v){var currentOffset=Math.abs(v-value);if(currentOffset<=offset){newValue=v;offset=currentOffset;}});return newValue;} -if(value>this.range.end)return this.range.end;if(value0)&&(sliderValuethis.values[handleIdx+1])) -sliderValue=this.values[handleIdx+1];} -sliderValue=this.getNearestValue(sliderValue);this.values[handleIdx]=sliderValue;this.value=this.values[0];this.handles[handleIdx].style[this.isVertical()?'top':'left']=this.translateToPx(sliderValue);this.drawSpans();if(!this.dragging||!this.event)this.updateFinished();},setValueBy:function(delta,handleIdx){this.setValue(this.values[handleIdx||this.activeHandleIdx||0]+delta,handleIdx||this.activeHandleIdx||0);},translateToPx:function(value){return Math.round(((this.trackLength-this.handleLength)/(this.range.end-this.range.start))*(value-this.range.start))+"px";},translateToValue:function(offset){return((offset/(this.trackLength-this.handleLength)*(this.range.end-this.range.start))+this.range.start);},getRange:function(range){var v=this.values.sortBy(Prototype.K);range=range||0;return $R(v[range],v[range+1]);},minimumOffset:function(){return(this.isVertical()?this.alignY:this.alignX);},maximumOffset:function(){return(this.isVertical()?(this.track.offsetHeight!=0?this.track.offsetHeight:this.track.style.height.replace(/px$/,""))-this.alignY:(this.track.offsetWidth!=0?this.track.offsetWidth:this.track.style.width.replace(/px$/,""))-this.alignY);},isVertical:function(){return(this.axis=='vertical');},drawSpans:function(){var slider=this;if(this.spans) -$R(0,this.spans.length-1).each(function(r){slider.setSpan(slider.spans[r],slider.getRange(r))});if(this.options.startSpan) -this.setSpan(this.options.startSpan,$R(0,this.values.length>1?this.getRange(0).min():this.value));if(this.options.endSpan) -this.setSpan(this.options.endSpan,$R(this.values.length>1?this.getRange(this.spans.length-1).max():this.value,this.maximum));},setSpan:function(span,range){if(this.isVertical()){span.style.top=this.translateToPx(range.start);span.style.height=this.translateToPx(range.end-range.start+this.range.start);}else{span.style.left=this.translateToPx(range.start);span.style.width=this.translateToPx(range.end-range.start+this.range.start);}},updateStyles:function(){this.handles.each(function(h){Element.removeClassName(h,'selected')});Element.addClassName(this.activeHandle,'selected');},startDrag:function(event){if(Event.isLeftClick(event)){if(!this.disabled){this.active=true;var handle=Event.element(event);var pointer=[Event.pointerX(event),Event.pointerY(event)];var track=handle;if(track==this.track){var offsets=Position.cumulativeOffset(this.track);this.event=event;this.setValue(this.translateToValue((this.isVertical()?pointer[1]-offsets[1]:pointer[0]-offsets[0])-(this.handleLength/2)));var offsets=Position.cumulativeOffset(this.activeHandle);this.offsetX=(pointer[0]-offsets[0]);this.offsetY=(pointer[1]-offsets[1]);}else{while((this.handles.indexOf(handle)==-1)&&handle.parentNode) -handle=handle.parentNode;this.activeHandle=handle;this.activeHandleIdx=this.handles.indexOf(this.activeHandle);this.updateStyles();var offsets=Position.cumulativeOffset(this.activeHandle);this.offsetX=(pointer[0]-offsets[0]);this.offsetY=(pointer[1]-offsets[1]);}} -Event.stop(event);}},update:function(event){if(this.active){if(!this.dragging)this.dragging=true;this.draw(event);if(navigator.appVersion.indexOf('AppleWebKit')>0)window.scrollBy(0,0);Event.stop(event);}},draw:function(event){var pointer=[Event.pointerX(event),Event.pointerY(event)];var offsets=Position.cumulativeOffset(this.track);pointer[0]-=this.offsetX+offsets[0];pointer[1]-=this.offsetY+offsets[1];this.event=event;this.setValue(this.translateToValue(this.isVertical()?pointer[1]:pointer[0]));if(this.initialized&&this.options.onSlide) -this.options.onSlide(this.values.length>1?this.values:this.value,this);},endDrag:function(event){if(this.active&&this.dragging){this.finishDrag(event,true);Event.stop(event);} -this.active=false;this.dragging=false;},finishDrag:function(event,success){this.active=false;this.dragging=false;this.updateFinished();},updateFinished:function(){if(this.initialized&&this.options.onChange) -this.options.onChange(this.values.length>1?this.values:this.value,this);this.event=null;}} \ No newline at end of file +// script.aculo.us slider.js v1.6.4, Wed Sep 06 11:30:58 CEST 2006 + +// Copyright (c) 2005 Marty Haught, Thomas Fuchs +// +// See http://script.aculo.us for more info +// +// 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 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. + +if(!Control) var Control = {}; +Control.Slider = Class.create(); + +// options: +// axis: 'vertical', or 'horizontal' (default) +// +// callbacks: +// onChange(value) +// onSlide(value) +Control.Slider.prototype = { + initialize: function(handle, track, options) { + var slider = this; + + if(handle instanceof Array) { + this.handles = handle.collect( function(e) { return $(e) }); + } else { + this.handles = [$(handle)]; + } + + this.track = $(track); + this.options = options || {}; + + this.axis = this.options.axis || 'horizontal'; + this.increment = this.options.increment || 1; + this.step = parseInt(this.options.step || '1'); + this.range = this.options.range || $R(0,1); + + this.value = 0; // assure backwards compat + this.values = this.handles.map( function() { return 0 }); + this.spans = this.options.spans ? this.options.spans.map(function(s){ return $(s) }) : false; + this.options.startSpan = $(this.options.startSpan || null); + this.options.endSpan = $(this.options.endSpan || null); + + this.restricted = this.options.restricted || false; + + this.maximum = this.options.maximum || this.range.end; + this.minimum = this.options.minimum || this.range.start; + + // Will be used to align the handle onto the track, if necessary + this.alignX = parseInt(this.options.alignX || '0'); + this.alignY = parseInt(this.options.alignY || '0'); + + this.trackLength = this.maximumOffset() - this.minimumOffset(); + + this.handleLength = this.isVertical() ? + (this.handles[0].offsetHeight != 0 ? + this.handles[0].offsetHeight : this.handles[0].style.height.replace(/px$/,"")) : + (this.handles[0].offsetWidth != 0 ? this.handles[0].offsetWidth : + this.handles[0].style.width.replace(/px$/,"")); + + this.active = false; + this.dragging = false; + this.disabled = false; + + if(this.options.disabled) this.setDisabled(); + + // Allowed values array + this.allowedValues = this.options.values ? this.options.values.sortBy(Prototype.K) : false; + if(this.allowedValues) { + this.minimum = this.allowedValues.min(); + this.maximum = this.allowedValues.max(); + } + + this.eventMouseDown = this.startDrag.bindAsEventListener(this); + this.eventMouseUp = this.endDrag.bindAsEventListener(this); + this.eventMouseMove = this.update.bindAsEventListener(this); + + // Initialize handles in reverse (make sure first handle is active) + this.handles.each( function(h,i) { + i = slider.handles.length-1-i; + slider.setValue(parseFloat( + (slider.options.sliderValue instanceof Array ? + slider.options.sliderValue[i] : slider.options.sliderValue) || + slider.range.start), i); + Element.makePositioned(h); // fix IE + Event.observe(h, "mousedown", slider.eventMouseDown); + }); + + Event.observe(this.track, "mousedown", this.eventMouseDown); + Event.observe(document, "mouseup", this.eventMouseUp); + Event.observe(document, "mousemove", this.eventMouseMove); + + this.initialized = true; + }, + dispose: function() { + var slider = this; + Event.stopObserving(this.track, "mousedown", this.eventMouseDown); + Event.stopObserving(document, "mouseup", this.eventMouseUp); + Event.stopObserving(document, "mousemove", this.eventMouseMove); + this.handles.each( function(h) { + Event.stopObserving(h, "mousedown", slider.eventMouseDown); + }); + }, + setDisabled: function(){ + this.disabled = true; + }, + setEnabled: function(){ + this.disabled = false; + }, + getNearestValue: function(value){ + if(this.allowedValues){ + if(value >= this.allowedValues.max()) return(this.allowedValues.max()); + if(value <= this.allowedValues.min()) return(this.allowedValues.min()); + + var offset = Math.abs(this.allowedValues[0] - value); + var newValue = this.allowedValues[0]; + this.allowedValues.each( function(v) { + var currentOffset = Math.abs(v - value); + if(currentOffset <= offset){ + newValue = v; + offset = currentOffset; + } + }); + return newValue; + } + if(value > this.range.end) return this.range.end; + if(value < this.range.start) return this.range.start; + return value; + }, + setValue: function(sliderValue, handleIdx){ + if(!this.active) { + this.activeHandleIdx = handleIdx || 0; + this.activeHandle = this.handles[this.activeHandleIdx]; + this.updateStyles(); + } + handleIdx = handleIdx || this.activeHandleIdx || 0; + if(this.initialized && this.restricted) { + if((handleIdx>0) && (sliderValuethis.values[handleIdx+1])) + sliderValue = this.values[handleIdx+1]; + } + sliderValue = this.getNearestValue(sliderValue); + this.values[handleIdx] = sliderValue; + this.value = this.values[0]; // assure backwards compat + + this.handles[handleIdx].style[this.isVertical() ? 'top' : 'left'] = + this.translateToPx(sliderValue); + + this.drawSpans(); + if(!this.dragging || !this.event) this.updateFinished(); + }, + setValueBy: function(delta, handleIdx) { + this.setValue(this.values[handleIdx || this.activeHandleIdx || 0] + delta, + handleIdx || this.activeHandleIdx || 0); + }, + translateToPx: function(value) { + return Math.round( + ((this.trackLength-this.handleLength)/(this.range.end-this.range.start)) * + (value - this.range.start)) + "px"; + }, + translateToValue: function(offset) { + return ((offset/(this.trackLength-this.handleLength) * + (this.range.end-this.range.start)) + this.range.start); + }, + getRange: function(range) { + var v = this.values.sortBy(Prototype.K); + range = range || 0; + return $R(v[range],v[range+1]); + }, + minimumOffset: function(){ + return(this.isVertical() ? this.alignY : this.alignX); + }, + maximumOffset: function(){ + return(this.isVertical() ? + (this.track.offsetHeight != 0 ? this.track.offsetHeight : + this.track.style.height.replace(/px$/,"")) - this.alignY : + (this.track.offsetWidth != 0 ? this.track.offsetWidth : + this.track.style.width.replace(/px$/,"")) - this.alignY); + }, + isVertical: function(){ + return (this.axis == 'vertical'); + }, + drawSpans: function() { + var slider = this; + if(this.spans) + $R(0, this.spans.length-1).each(function(r) { slider.setSpan(slider.spans[r], slider.getRange(r)) }); + if(this.options.startSpan) + this.setSpan(this.options.startSpan, + $R(0, this.values.length>1 ? this.getRange(0).min() : this.value )); + if(this.options.endSpan) + this.setSpan(this.options.endSpan, + $R(this.values.length>1 ? this.getRange(this.spans.length-1).max() : this.value, this.maximum)); + }, + setSpan: function(span, range) { + if(this.isVertical()) { + span.style.top = this.translateToPx(range.start); + span.style.height = this.translateToPx(range.end - range.start + this.range.start); + } else { + span.style.left = this.translateToPx(range.start); + span.style.width = this.translateToPx(range.end - range.start + this.range.start); + } + }, + updateStyles: function() { + this.handles.each( function(h){ Element.removeClassName(h, 'selected') }); + Element.addClassName(this.activeHandle, 'selected'); + }, + startDrag: function(event) { + if(Event.isLeftClick(event)) { + if(!this.disabled){ + this.active = true; + + var handle = Event.element(event); + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var track = handle; + if(track==this.track) { + var offsets = Position.cumulativeOffset(this.track); + this.event = event; + this.setValue(this.translateToValue( + (this.isVertical() ? pointer[1]-offsets[1] : pointer[0]-offsets[0])-(this.handleLength/2) + )); + var offsets = Position.cumulativeOffset(this.activeHandle); + this.offsetX = (pointer[0] - offsets[0]); + this.offsetY = (pointer[1] - offsets[1]); + } else { + // find the handle (prevents issues with Safari) + while((this.handles.indexOf(handle) == -1) && handle.parentNode) + handle = handle.parentNode; + + this.activeHandle = handle; + this.activeHandleIdx = this.handles.indexOf(this.activeHandle); + this.updateStyles(); + + var offsets = Position.cumulativeOffset(this.activeHandle); + this.offsetX = (pointer[0] - offsets[0]); + this.offsetY = (pointer[1] - offsets[1]); + } + } + Event.stop(event); + } + }, + update: function(event) { + if(this.active) { + if(!this.dragging) this.dragging = true; + this.draw(event); + // fix AppleWebKit rendering + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); + Event.stop(event); + } + }, + draw: function(event) { + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var offsets = Position.cumulativeOffset(this.track); + pointer[0] -= this.offsetX + offsets[0]; + pointer[1] -= this.offsetY + offsets[1]; + this.event = event; + this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] )); + if(this.initialized && this.options.onSlide) + this.options.onSlide(this.values.length>1 ? this.values : this.value, this); + }, + endDrag: function(event) { + if(this.active && this.dragging) { + this.finishDrag(event, true); + Event.stop(event); + } + this.active = false; + this.dragging = false; + }, + finishDrag: function(event, success) { + this.active = false; + this.dragging = false; + this.updateFinished(); + }, + updateFinished: function() { + if(this.initialized && this.options.onChange) + this.options.onChange(this.values.length>1 ? this.values : this.value, this); + this.event = null; + } +} \ No newline at end of file diff --git a/Solar/App/Public/scripts/scriptaculous/unittest.js b/Solar/App/Public/scripts/scriptaculous/unittest.js index c56bdf09..215563ac 100644 --- a/Solar/App/Public/scripts/scriptaculous/unittest.js +++ b/Solar/App/Public/scripts/scriptaculous/unittest.js @@ -1,36 +1,552 @@ -// From script.aculo.us 1.6.2, compressed by jsmin (http://www.crockford.com/javascript/jsmin.html). - -Event.simulateMouse=function(element,eventName){var options=Object.extend({pointerX:0,pointerY:0,buttons:0},arguments[2]||{});var oEvent=document.createEvent("MouseEvents");oEvent.initMouseEvent(eventName,true,true,document.defaultView,options.buttons,options.pointerX,options.pointerY,options.pointerX,options.pointerY,false,false,false,false,0,$(element));if(this.mark)Element.remove(this.mark);this.mark=document.createElement('div');this.mark.appendChild(document.createTextNode(" "));document.body.appendChild(this.mark);this.mark.style.position='absolute';this.mark.style.top=options.pointerY+"px";this.mark.style.left=options.pointerX+"px";this.mark.style.width="5px";this.mark.style.height="5px;";this.mark.style.borderTop="1px solid red;" -this.mark.style.borderLeft="1px solid red;" -if(this.step) -alert('['+new Date().getTime().toString()+'] '+eventName+'/'+Test.Unit.inspect(options));$(element).dispatchEvent(oEvent);};Event.simulateKey=function(element,eventName){var options=Object.extend({ctrlKey:false,altKey:false,shiftKey:false,metaKey:false,keyCode:0,charCode:0},arguments[2]||{});var oEvent=document.createEvent("KeyEvents");oEvent.initKeyEvent(eventName,true,true,window,options.ctrlKey,options.altKey,options.shiftKey,options.metaKey,options.keyCode,options.charCode);$(element).dispatchEvent(oEvent);};Event.simulateKeys=function(element,command){for(var i=0;i'+''+''+''+'
    StatusTestMessage
    ';this.logsummary=$('logsummary') -this.loglines=$('loglines');},_toHTML:function(txt){return txt.escapeHTML().replace(/\n/g,"
    ");}} -Test.Unit.Runner=Class.create();Test.Unit.Runner.prototype={initialize:function(testcases){this.options=Object.extend({testLog:'testlog'},arguments[1]||{});this.options.resultsURL=this.parseResultsURLQueryParameter();if(this.options.testLog){this.options.testLog=$(this.options.testLog)||null;} -if(this.options.tests){this.tests=[];for(var i=0;i0){return"ERROR";} -if(this.tests[i].failures>0){hasFailure=true;}} -if(hasFailure){return"FAILURE";}else{return"SUCCESS";}},postResults:function(){if(this.options.resultsURL){new Ajax.Request(this.options.resultsURL,{method:'get',parameters:'result='+this.getResult(),asynchronous:false});}},runTests:function(){var test=this.tests[this.currentTest];if(!test){this.postResults();this.logger.summary(this.summary());return;} -if(!test.isWaiting){this.logger.start(test.name);} -test.run();if(test.isWaiting){this.logger.message("Waiting for "+test.timeToWait+"ms");setTimeout(this.runTests.bind(this),test.timeToWait||1000);}else{this.logger.finish(test.status(),test.summary());this.currentTest++;this.runTests();}},summary:function(){var assertions=0;var failures=0;var errors=0;var messages=[];for(var i=0;i0)return'failed';if(this.errors>0)return'error';return'passed';},assert:function(expression){var message=arguments[1]||'assert: got "'+Test.Unit.inspect(expression)+'"';try{expression?this.pass():this.fail(message);} -catch(e){this.error(e);}},assertEqual:function(expected,actual){var message=arguments[2]||"assertEqual";try{(expected==actual)?this.pass():this.fail(message+': expected "'+Test.Unit.inspect(expected)+'", actual "'+Test.Unit.inspect(actual)+'"');} -catch(e){this.error(e);}},assertEnumEqual:function(expected,actual){var message=arguments[2]||"assertEnumEqual";try{$A(expected).length==$A(actual).length&&expected.zip(actual).all(function(pair){return pair[0]==pair[1]})?this.pass():this.fail(message+': expected '+Test.Unit.inspect(expected)+', actual '+Test.Unit.inspect(actual));} -catch(e){this.error(e);}},assertNotEqual:function(expected,actual){var message=arguments[2]||"assertNotEqual";try{(expected!=actual)?this.pass():this.fail(message+': got "'+Test.Unit.inspect(actual)+'"');} -catch(e){this.error(e);}},assertNull:function(obj){var message=arguments[1]||'assertNull' -try{(obj==null)?this.pass():this.fail(message+': got "'+Test.Unit.inspect(obj)+'"');} -catch(e){this.error(e);}},assertMatch:function(expected,actual){var message=arguments[2]||'assertMatch';var regex=new RegExp(expected);try{(regex.exec(actual))?this.pass():this.fail(message+' : regex: "'+Test.Unit.inspect(expected)+' did not match: '+Test.Unit.inspect(actual)+'"');} -catch(e){this.error(e);}},assertHidden:function(element){var message=arguments[1]||'assertHidden';this.assertEqual("none",element.style.display,message);},assertNotNull:function(object){var message=arguments[1]||'assertNotNull';this.assert(object!=null,message);},assertInstanceOf:function(expected,actual){var message=arguments[2]||'assertInstanceOf';try{(actual instanceof expected)?this.pass():this.fail(message+": object was not an instance of the expected type");} -catch(e){this.error(e);}},assertNotInstanceOf:function(expected,actual){var message=arguments[2]||'assertNotInstanceOf';try{!(actual instanceof expected)?this.pass():this.fail(message+": object was an instance of the not expected type");} -catch(e){this.error(e);}},_isVisible:function(element){element=$(element);if(!element.parentNode)return true;this.assertNotNull(element);if(element.style&&Element.getStyle(element,'display')=='none') -return false;return this._isVisible(element.parentNode);},assertNotVisible:function(element){this.assert(!this._isVisible(element),Test.Unit.inspect(element)+" was not hidden and didn't have a hidden parent either. "+(""||arguments[1]));},assertVisible:function(element){this.assert(this._isVisible(element),Test.Unit.inspect(element)+" was not visible. "+(""||arguments[1]));},benchmark:function(operation,iterations){var startAt=new Date();(iterations||1).times(operation);var timeTaken=((new Date())-startAt);this.info((arguments[2]||'Operation')+' finished '+ -iterations+' iterations in '+(timeTaken/1000)+'s');return timeTaken;}} -Test.Unit.Testcase=Class.create();Object.extend(Object.extend(Test.Unit.Testcase.prototype,Test.Unit.Assertions.prototype),{initialize:function(name,test,setup,teardown){Test.Unit.Assertions.prototype.initialize.bind(this)();this.name=name;this.test=test||function(){};this.setup=setup||function(){};this.teardown=teardown||function(){};this.isWaiting=false;this.timeToWait=1000;},wait:function(time,nextPart){this.isWaiting=true;this.test=nextPart;this.timeToWait=time;},run:function(){try{try{if(!this.isWaiting)this.setup.bind(this)();this.isWaiting=false;this.test.bind(this)();}finally{if(!this.isWaiting){this.teardown.bind(this)();}}} -catch(e){this.error(e);}}}); \ No newline at end of file +// script.aculo.us unittest.js v1.6.4, Wed Sep 06 11:30:58 CEST 2006 + +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005 Jon Tirsen (http://www.tirsen.com) +// (c) 2005 Michael Schuerig (http://www.schuerig.de/michael/) +// +// 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 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. + + +// experimental, Firefox-only +Event.simulateMouse = function(element, eventName) { + var options = Object.extend({ + pointerX: 0, + pointerY: 0, + buttons: 0 + }, arguments[2] || {}); + var oEvent = document.createEvent("MouseEvents"); + oEvent.initMouseEvent(eventName, true, true, document.defaultView, + options.buttons, options.pointerX, options.pointerY, options.pointerX, options.pointerY, + false, false, false, false, 0, $(element)); + + if(this.mark) Element.remove(this.mark); + this.mark = document.createElement('div'); + this.mark.appendChild(document.createTextNode(" ")); + document.body.appendChild(this.mark); + this.mark.style.position = 'absolute'; + this.mark.style.top = options.pointerY + "px"; + this.mark.style.left = options.pointerX + "px"; + this.mark.style.width = "5px"; + this.mark.style.height = "5px;"; + this.mark.style.borderTop = "1px solid red;" + this.mark.style.borderLeft = "1px solid red;" + + if(this.step) + alert('['+new Date().getTime().toString()+'] '+eventName+'/'+Test.Unit.inspect(options)); + + $(element).dispatchEvent(oEvent); +}; + +// Note: Due to a fix in Firefox 1.0.5/6 that probably fixed "too much", this doesn't work in 1.0.6 or DP2. +// You need to downgrade to 1.0.4 for now to get this working +// See https://bugzilla.mozilla.org/show_bug.cgi?id=289940 for the fix that fixed too much +Event.simulateKey = function(element, eventName) { + var options = Object.extend({ + ctrlKey: false, + altKey: false, + shiftKey: false, + metaKey: false, + keyCode: 0, + charCode: 0 + }, arguments[2] || {}); + + var oEvent = document.createEvent("KeyEvents"); + oEvent.initKeyEvent(eventName, true, true, window, + options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, + options.keyCode, options.charCode ); + $(element).dispatchEvent(oEvent); +}; + +Event.simulateKeys = function(element, command) { + for(var i=0; i' + + '' + + '' + + '' + + '
    StatusTestMessage
    '; + this.logsummary = $('logsummary') + this.loglines = $('loglines'); + }, + _toHTML: function(txt) { + return txt.escapeHTML().replace(/\n/g,"
    "); + } +} + +Test.Unit.Runner = Class.create(); +Test.Unit.Runner.prototype = { + initialize: function(testcases) { + this.options = Object.extend({ + testLog: 'testlog' + }, arguments[1] || {}); + this.options.resultsURL = this.parseResultsURLQueryParameter(); + if (this.options.testLog) { + this.options.testLog = $(this.options.testLog) || null; + } + if(this.options.tests) { + this.tests = []; + for(var i = 0; i < this.options.tests.length; i++) { + if(/^test/.test(this.options.tests[i])) { + this.tests.push(new Test.Unit.Testcase(this.options.tests[i], testcases[this.options.tests[i]], testcases["setup"], testcases["teardown"])); + } + } + } else { + if (this.options.test) { + this.tests = [new Test.Unit.Testcase(this.options.test, testcases[this.options.test], testcases["setup"], testcases["teardown"])]; + } else { + this.tests = []; + for(var testcase in testcases) { + if(/^test/.test(testcase)) { + this.tests.push( + new Test.Unit.Testcase( + this.options.context ? ' -> ' + this.options.titles[testcase] : testcase, + testcases[testcase], testcases["setup"], testcases["teardown"] + )); + } + } + } + } + this.currentTest = 0; + this.logger = new Test.Unit.Logger(this.options.testLog); + setTimeout(this.runTests.bind(this), 1000); + }, + parseResultsURLQueryParameter: function() { + return window.location.search.parseQuery()["resultsURL"]; + }, + // Returns: + // "ERROR" if there was an error, + // "FAILURE" if there was a failure, or + // "SUCCESS" if there was neither + getResult: function() { + var hasFailure = false; + for(var i=0;i 0) { + return "ERROR"; + } + if (this.tests[i].failures > 0) { + hasFailure = true; + } + } + if (hasFailure) { + return "FAILURE"; + } else { + return "SUCCESS"; + } + }, + postResults: function() { + if (this.options.resultsURL) { + new Ajax.Request(this.options.resultsURL, + { method: 'get', parameters: 'result=' + this.getResult(), asynchronous: false }); + } + }, + runTests: function() { + var test = this.tests[this.currentTest]; + if (!test) { + // finished! + this.postResults(); + this.logger.summary(this.summary()); + return; + } + if(!test.isWaiting) { + this.logger.start(test.name); + } + test.run(); + if(test.isWaiting) { + this.logger.message("Waiting for " + test.timeToWait + "ms"); + setTimeout(this.runTests.bind(this), test.timeToWait || 1000); + } else { + this.logger.finish(test.status(), test.summary()); + this.currentTest++; + // tail recursive, hopefully the browser will skip the stackframe + this.runTests(); + } + }, + summary: function() { + var assertions = 0; + var failures = 0; + var errors = 0; + var messages = []; + for(var i=0;i 0) return 'failed'; + if (this.errors > 0) return 'error'; + return 'passed'; + }, + assert: function(expression) { + var message = arguments[1] || 'assert: got "' + Test.Unit.inspect(expression) + '"'; + try { expression ? this.pass() : + this.fail(message); } + catch(e) { this.error(e); } + }, + assertEqual: function(expected, actual) { + var message = arguments[2] || "assertEqual"; + try { (expected == actual) ? this.pass() : + this.fail(message + ': expected "' + Test.Unit.inspect(expected) + + '", actual "' + Test.Unit.inspect(actual) + '"'); } + catch(e) { this.error(e); } + }, + assertEnumEqual: function(expected, actual) { + var message = arguments[2] || "assertEnumEqual"; + try { $A(expected).length == $A(actual).length && + expected.zip(actual).all(function(pair) { return pair[0] == pair[1] }) ? + this.pass() : this.fail(message + ': expected ' + Test.Unit.inspect(expected) + + ', actual ' + Test.Unit.inspect(actual)); } + catch(e) { this.error(e); } + }, + assertNotEqual: function(expected, actual) { + var message = arguments[2] || "assertNotEqual"; + try { (expected != actual) ? this.pass() : + this.fail(message + ': got "' + Test.Unit.inspect(actual) + '"'); } + catch(e) { this.error(e); } + }, + assertIdentical: function(expected, actual) { + var message = arguments[2] || "assertIdentical"; + try { (expected === actual) ? this.pass() : + this.fail(message + ': expected "' + Test.Unit.inspect(expected) + + '", actual "' + Test.Unit.inspect(actual) + '"'); } + catch(e) { this.error(e); } + }, + assertNotIdentical: function(expected, actual) { + var message = arguments[2] || "assertNotIdentical"; + try { !(expected === actual) ? this.pass() : + this.fail(message + ': expected "' + Test.Unit.inspect(expected) + + '", actual "' + Test.Unit.inspect(actual) + '"'); } + catch(e) { this.error(e); } + }, + assertNull: function(obj) { + var message = arguments[1] || 'assertNull' + try { (obj==null) ? this.pass() : + this.fail(message + ': got "' + Test.Unit.inspect(obj) + '"'); } + catch(e) { this.error(e); } + }, + assertMatch: function(expected, actual) { + var message = arguments[2] || 'assertMatch'; + var regex = new RegExp(expected); + try { (regex.exec(actual)) ? this.pass() : + this.fail(message + ' : regex: "' + Test.Unit.inspect(expected) + ' did not match: ' + Test.Unit.inspect(actual) + '"'); } + catch(e) { this.error(e); } + }, + assertHidden: function(element) { + var message = arguments[1] || 'assertHidden'; + this.assertEqual("none", element.style.display, message); + }, + assertNotNull: function(object) { + var message = arguments[1] || 'assertNotNull'; + this.assert(object != null, message); + }, + assertType: function(expected, actual) { + var message = arguments[2] || 'assertType'; + try { + (actual.constructor == expected) ? this.pass() : + this.fail(message + ': expected "' + Test.Unit.inspect(expected) + + '", actual "' + (actual.constructor) + '"'); } + catch(e) { this.error(e); } + }, + assertNotOfType: function(expected, actual) { + var message = arguments[2] || 'assertNotOfType'; + try { + (actual.constructor != expected) ? this.pass() : + this.fail(message + ': expected "' + Test.Unit.inspect(expected) + + '", actual "' + (actual.constructor) + '"'); } + catch(e) { this.error(e); } + }, + assertInstanceOf: function(expected, actual) { + var message = arguments[2] || 'assertInstanceOf'; + try { + (actual instanceof expected) ? this.pass() : + this.fail(message + ": object was not an instance of the expected type"); } + catch(e) { this.error(e); } + }, + assertNotInstanceOf: function(expected, actual) { + var message = arguments[2] || 'assertNotInstanceOf'; + try { + !(actual instanceof expected) ? this.pass() : + this.fail(message + ": object was an instance of the not expected type"); } + catch(e) { this.error(e); } + }, + assertRespondsTo: function(method, obj) { + var message = arguments[2] || 'assertRespondsTo'; + try { + (obj[method] && typeof obj[method] == 'function') ? this.pass() : + this.fail(message + ": object doesn't respond to [" + method + "]"); } + catch(e) { this.error(e); } + }, + assertReturnsTrue: function(method, obj) { + var message = arguments[2] || 'assertReturnsTrue'; + try { + var m = obj[method]; + if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)]; + m() ? this.pass() : + this.fail(message + ": method returned false"); } + catch(e) { this.error(e); } + }, + assertReturnsFalse: function(method, obj) { + var message = arguments[2] || 'assertReturnsFalse'; + try { + var m = obj[method]; + if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)]; + !m() ? this.pass() : + this.fail(message + ": method returned true"); } + catch(e) { this.error(e); } + }, + assertRaise: function(exceptionName, method) { + var message = arguments[2] || 'assertRaise'; + try { + method(); + this.fail(message + ": exception expected but none was raised"); } + catch(e) { + (e.name==exceptionName) ? this.pass() : this.error(e); + } + }, + assertElementsMatch: function() { + var expressions = $A(arguments), elements = $A(expressions.shift()); + if (elements.length != expressions.length) { + this.fail('assertElementsMatch: size mismatch: ' + elements.length + ' elements, ' + expressions.length + ' expressions'); + return false; + } + elements.zip(expressions).all(function(pair, index) { + var element = $(pair.first()), expression = pair.last(); + if (element.match(expression)) return true; + this.fail('assertElementsMatch: (in index ' + index + ') expected ' + expression.inspect() + ' but got ' + element.inspect()); + }.bind(this)) && this.pass(); + }, + assertElementMatches: function(element, expression) { + this.assertElementsMatch([element], expression); + }, + benchmark: function(operation, iterations) { + var startAt = new Date(); + (iterations || 1).times(operation); + var timeTaken = ((new Date())-startAt); + this.info((arguments[2] || 'Operation') + ' finished ' + + iterations + ' iterations in ' + (timeTaken/1000)+'s' ); + return timeTaken; + }, + _isVisible: function(element) { + element = $(element); + if(!element.parentNode) return true; + this.assertNotNull(element); + if(element.style && Element.getStyle(element, 'display') == 'none') + return false; + + return this._isVisible(element.parentNode); + }, + assertNotVisible: function(element) { + this.assert(!this._isVisible(element), Test.Unit.inspect(element) + " was not hidden and didn't have a hidden parent either. " + ("" || arguments[1])); + }, + assertVisible: function(element) { + this.assert(this._isVisible(element), Test.Unit.inspect(element) + " was not visible. " + ("" || arguments[1])); + }, + benchmark: function(operation, iterations) { + var startAt = new Date(); + (iterations || 1).times(operation); + var timeTaken = ((new Date())-startAt); + this.info((arguments[2] || 'Operation') + ' finished ' + + iterations + ' iterations in ' + (timeTaken/1000)+'s' ); + return timeTaken; + } +} + +Test.Unit.Testcase = Class.create(); +Object.extend(Object.extend(Test.Unit.Testcase.prototype, Test.Unit.Assertions.prototype), { + initialize: function(name, test, setup, teardown) { + Test.Unit.Assertions.prototype.initialize.bind(this)(); + this.name = name; + + if(typeof test == 'string') { + test = test.gsub(/(\.should[^\(]+\()/,'#{0}this,'); + test = test.gsub(/(\.should[^\(]+)\(this,\)/,'#{1}(this)'); + this.test = function() { + eval('with(this){'+test+'}'); + } + } else { + this.test = test || function() {}; + } + + this.setup = setup || function() {}; + this.teardown = teardown || function() {}; + this.isWaiting = false; + this.timeToWait = 1000; + }, + wait: function(time, nextPart) { + this.isWaiting = true; + this.test = nextPart; + this.timeToWait = time; + }, + run: function() { + try { + try { + if (!this.isWaiting) this.setup.bind(this)(); + this.isWaiting = false; + this.test.bind(this)(); + } finally { + if(!this.isWaiting) { + this.teardown.bind(this)(); + } + } + } + catch(e) { this.error(e); } + } +}); + +// *EXPERIMENTAL* BDD-style testing to please non-technical folk +// This draws many ideas from RSpec http://rspec.rubyforge.org/ + +Test.setupBDDExtensionMethods = function(){ + var METHODMAP = { + shouldEqual: 'assertEqual', + shouldNotEqual: 'assertNotEqual', + shouldEqualEnum: 'assertEnumEqual', + shouldBeA: 'assertType', + shouldNotBeA: 'assertNotOfType', + shouldBeAn: 'assertType', + shouldNotBeAn: 'assertNotOfType', + shouldBeNull: 'assertNull', + shouldNotBeNull: 'assertNotNull', + + shouldBe: 'assertReturnsTrue', + shouldNotBe: 'assertReturnsFalse', + shouldRespondTo: 'assertRespondsTo' + }; + Test.BDDMethods = {}; + for(m in METHODMAP) { + Test.BDDMethods[m] = eval( + 'function(){'+ + 'var args = $A(arguments);'+ + 'var scope = args.shift();'+ + 'scope.'+METHODMAP[m]+'.apply(scope,(args || []).concat([this])); }'); + } + [Array.prototype, String.prototype, Number.prototype].each( + function(p){ Object.extend(p, Test.BDDMethods) } + ); +} + +Test.context = function(name, spec, log){ + Test.setupBDDExtensionMethods(); + + var compiledSpec = {}; + var titles = {}; + for(specName in spec) { + switch(specName){ + case "setup": + case "teardown": + compiledSpec[specName] = spec[specName]; + break; + default: + var testName = 'test'+specName.gsub(/\s+/,'-').camelize(); + var body = spec[specName].toString().split('\n').slice(1); + if(/^\{/.test(body[0])) body = body.slice(1); + body.pop(); + body = body.map(function(statement){ + return statement.strip() + }); + compiledSpec[testName] = body.join('\n'); + titles[testName] = specName; + } + } + new Test.Unit.Runner(compiledSpec, { titles: titles, testLog: log || 'testlog', context: name }); +}; \ No newline at end of file diff --git a/Solar/App/Public/styles/default.css b/Solar/App/Public/styles/default.css index a76750d4..608cb437 100644 --- a/Solar/App/Public/styles/default.css +++ b/Solar/App/Public/styles/default.css @@ -112,16 +112,6 @@ option { list-style: none; } -#content form ul.success { - background-color: #aaf; - border: 1px solid #33f; -} - -#content form ul.failure { - background-color: #faa; - border: 1px solid #f33; -} - #content form dl ul { color: red; font-size: 90%; diff --git a/Solar/Auth.php b/Solar/Auth.php index 6ddaa59d..dbc66b8d 100755 --- a/Solar/Auth.php +++ b/Solar/Auth.php @@ -51,8 +51,8 @@ class Solar_Auth extends Solar_Base { * : (bool) Whether or not to allow login/logout attempts. * * `source` - * : (string) The source for auth credentials, 'get' - * (for [[Solar::get()]] method) or 'post' (for [[Solar::post()]] method). + * : (string) The source for auth credentials, 'get' (via the + * for GET request vars) or 'post' (via the POST request vars). * Default is 'post'. * * `source_handle` @@ -103,12 +103,12 @@ class Solar_Auth extends Solar_Base { /** * - * Flash-messaging object. + * Class-specific session object. * - * @var Solar_Flash + * @var Solar_Session * */ - protected $_flash; + protected $_session; /** * @@ -130,10 +130,10 @@ class Solar_Auth extends Solar_Base { /** * - * Convenience reference to $_SESSION['Solar_Auth']['active']. + * The Unix time at which the authenticated handle was last + * valid. * - * This is the Unix time at which the authenticated handle was last - * valid(). + * Convenience reference to $this->_session->store['active']. * * @var int * @@ -144,11 +144,11 @@ class Solar_Auth extends Solar_Base { /** * - * Convenience reference to $_SESSION['Solar_Auth']['initial']. - * - * This is the Unix time at which the handle was initially + * The Unix time at which the handle was initially * authenticated. * + * Convenience reference to $this->_session->store['initial']. + * * @var int * */ @@ -156,9 +156,7 @@ class Solar_Auth extends Solar_Base { /** * - * Convenience reference to $_SESSION['Solar_Auth']['status']. - * - * This is the status code of the current user authentication; the string + * The status code of the current user authentication. The string * codes are ... * * `ANON` @@ -177,6 +175,8 @@ class Solar_Auth extends Solar_Base { * `WRONG` * : The user attempted authentication but failed * + * Convenience reference to $this->_session->store['status']. + * * @var string * */ @@ -184,9 +184,9 @@ class Solar_Auth extends Solar_Base { /** * - * Convenience reference to $_SESSION['Solar_Auth']['handle']. + * The currently authenticated user handle. * - * This is the currently authenticated user handle. + * Convenience reference to $this->_session->store['handle']. * * @var string * @@ -195,11 +195,11 @@ class Solar_Auth extends Solar_Base { /** * - * Convenience reference to $_SESSION['Solar_Auth']['email']. - * - * This is the email address of the currently authenticated user. + * The email address of the currently authenticated user. * May or may not be populated by the adapter. * + * Convenience reference to $this->_session->store['email']. + * * @var string * */ @@ -207,11 +207,11 @@ class Solar_Auth extends Solar_Base { /** * - * Convenience reference to $_SESSION['Solar_Auth']['moniker']. - * - * This is the "display name" or "full name" of the currently + * The "display name" or "full name" of the currently * authenticated user. May or may not be populated by the adapter. * + * Convenience reference to $this->_session->store['moniker']. + * * @var string * */ @@ -219,11 +219,11 @@ class Solar_Auth extends Solar_Base { /** * - * Convenience reference to $_SESSION['Solar_Auth']['uri']. - * - * This is the URI for the currently authenticated user. May or + * The URI for the currently authenticated user. May or * may not be populated by the adapter. * + * Convenience reference to $this->_session->store['uri']. + * * @var string * */ @@ -262,12 +262,6 @@ public function __construct($config = null) 'submit_logout' => $this->_config['submit_logout'], ); $this->_adapter->setCommon($common); - - // create the flash object - $this->_flash = Solar::factory( - 'Solar_Flash', - array('class' => get_class($this)) - ); } /** @@ -279,16 +273,16 @@ public function __construct($config = null) */ public function start() { - // start the session if one hasn't been started already - if (session_id() === '') { - session_start(); - } + // create the session-access object. + // starts the session if it has not been started already. + $this->_session = Solar::factory( + 'Solar_Session', + array('class' => get_class($this)) + ); - // initialize the session array if it does not exist - if (! isset($_SESSION['Solar_Auth']) || - ! is_array($_SESSION['Solar_Auth'])) { - - $_SESSION['Solar_Auth'] = array( + // initialize the session array as needed + if (empty($this->_session->store)) { + $this->_session->store = array( 'status' => 'ANON', 'initial' => null, 'active' => null, @@ -299,14 +293,14 @@ public function start() ); } - // add convenience references to the session array keys - $this->status =& $_SESSION['Solar_Auth']['status']; - $this->initial =& $_SESSION['Solar_Auth']['initial']; - $this->active =& $_SESSION['Solar_Auth']['active']; - $this->handle =& $_SESSION['Solar_Auth']['handle']; - $this->email =& $_SESSION['Solar_Auth']['email']; - $this->moniker =& $_SESSION['Solar_Auth']['moniker']; - $this->uri =& $_SESSION['Solar_Auth']['uri']; + // add convenience references to the session store keys + $this->status =& $this->_session->store['status']; + $this->initial =& $this->_session->store['initial']; + $this->active =& $this->_session->store['active']; + $this->handle =& $this->_session->store['handle']; + $this->email =& $this->_session->store['email']; + $this->moniker =& $this->_session->store['moniker']; + $this->uri =& $this->_session->store['uri']; // update idle and expire times no matter what $this->updateIdleExpire(); @@ -438,11 +432,11 @@ public function reset($status = 'ANON') $this->_adapter->reset(); } - // reset the session id and delete previous session - session_regenerate_id(true); + // reset the session id and delete previous session file + $this->_session->regenerateId(); // flash forward any messages - $this->_flash->set('status_text', $this->locale($this->status)); + $this->_session->setFlash('status_text', $this->locale($this->status)); } /** @@ -459,7 +453,7 @@ public function reset($status = 'ANON') */ public function getFlash($key, $val = null) { - return $this->_flash->get($key, $val); + return $this->_session->getFlash($key, $val); } } ?> \ No newline at end of file diff --git a/Solar/Auth/Adapter.php b/Solar/Auth/Adapter.php index 73667e8e..74fb7d7d 100644 --- a/Solar/Auth/Adapter.php +++ b/Solar/Auth/Adapter.php @@ -32,30 +32,30 @@ abstract class Solar_Auth_Adapter extends Solar_Base { * * Keys are ... * - * `source`: - * (string) The source for auth credentials, 'get' - * (for [[Solar::get()]] method) or 'post' (for [[Solar::post()]] method). - * Default is 'post'. + * `source` + * : (string) The source for auth credentials, 'get' (via the + * for GET request vars) or 'post' (via the POST request vars). + * Default is 'post'. * - * `source_handle`: - * (string) Username key in the credential array source, - * default 'handle'. + * `source_handle` + * : (string) Username key in the credential array source, + * default 'handle'. * - * `source_passwd`: - * (string) Password key in the credential array source, - * default 'passwd'. + * `source_passwd` + * : (string) Password key in the credential array source, + * default 'passwd'. * - * `source_submit`: - * (string) Submission key in the credential array source, - * default 'submit'. + * `source_submit` + * : (string) Submission key in the credential array source, + * default 'submit'. * - * `submit_login`: - * (string) The submission-key value to indicate a - * login attempt; default is the 'SUBMIT_LOGIN' locale key value. + * `submit_login` + * : (string) The submission-key value to indicate a + * login attempt; default is the 'SUBMIT_LOGIN' locale key value. * - * `submit_logout`: - * (string) The submission-key value to indicate a - * login attempt; default is the 'SUBMIT_LOGOUT' locale key value. + * `submit_logout` + * : (string) The submission-key value to indicate a + * login attempt; default is the 'SUBMIT_LOGOUT' locale key value. * * @var array * @@ -63,6 +63,7 @@ abstract class Solar_Auth_Adapter extends Solar_Base { * */ protected $_common = array( + 'request' => null, 'source' => 'post', 'source_handle' => 'handle', 'source_passwd' => 'passwd', @@ -71,6 +72,15 @@ abstract class Solar_Auth_Adapter extends Solar_Base { 'submit_logout' => null, ); + /** + * + * Details on the current request. + * + * @var Solar_Request + * + */ + protected $_request; + /** * * The unique user handle as derived from the authentication source. @@ -138,6 +148,7 @@ public function __construct($config = null) $this->_common['submit_login'] = $this->locale('SUBMIT_LOGIN'); $this->_common['submit_logout'] = $this->locale('SUBMIT_LOGOUT'); parent::__construct($config); + $this->_request = Solar::factory('Solar_Request'); } /** @@ -200,7 +211,7 @@ public function setCommon($common) public function isLoginRequest() { $method = strtolower($this->_common['source']); - $submit = Solar::$method($this->_common['source_submit']); + $submit = $this->_request->$method($this->_common['source_submit']); return $submit == $this->_common['submit_login']; } @@ -215,7 +226,7 @@ public function isLoginRequest() public function isLogoutRequest() { $method = strtolower($this->_common['source']); - $submit = Solar::$method($this->_common['source_submit']); + $submit = $this->_request->$method($this->_common['source_submit']); return $submit == $this->_common['submit_logout']; } @@ -228,15 +239,19 @@ public function isLogoutRequest() */ public function isLoginValid() { + // clear out current error and user data. $this->_err = null; - $method = strtolower($this->_common['source']); - $submit = Solar::$method($this->_common['source_submit']); $this->reset(); - $this->_handle = Solar::$method($this->_common['source_handle']); - $this->_passwd = Solar::$method($this->_common['source_passwd']); + + // load the handle and password from the request source + $method = strtolower($this->_common['source']); + $this->_handle = $this->_request->$method($this->_common['source_handle']); + $this->_passwd = $this->_request->$method($this->_common['source_passwd']); + + // verify the credentials, which may set some user data. $result = (bool) $this->_verify(); if ($result !== true) { - // not verified, clear out all user data + // not verified, clear out all user data. $this->reset(); } return $result; diff --git a/Solar/Auth/Adapter/Htpasswd.php b/Solar/Auth/Adapter/Htpasswd.php index 1e6cb520..86e21e2d 100755 --- a/Solar/Auth/Adapter/Htpasswd.php +++ b/Solar/Auth/Adapter/Htpasswd.php @@ -48,8 +48,8 @@ class Solar_Auth_Adapter_Htpasswd extends Solar_Auth_Adapter { * * Keys are ... * - * `file`: - * (string) Path to password file. + * `file` + * : (string) Path to password file. * * @var array * diff --git a/Solar/Auth/Adapter/Ini.php b/Solar/Auth/Adapter/Ini.php index 49959943..701bbebd 100755 --- a/Solar/Auth/Adapter/Ini.php +++ b/Solar/Auth/Adapter/Ini.php @@ -25,15 +25,13 @@ * Authenticate against .ini style files. * * Each group is a user handle, with keys for 'passwd', 'moniker', 'email', - * and 'uri'. For example: + * and 'uri'. For example ... * - * - * [pmjones] - * passwd = plaintextpass - * email = pmjones@solarphp.com - * moniker = Paul M. Jones - * uri = http://paul-m-jones.com/ - * }} + * [pmjones] + * passwd = plaintextpass + * email = pmjones@solarphp.com + * moniker = Paul M. Jones + * uri = http://paul-m-jones.com/ * * @category Solar * @@ -48,8 +46,8 @@ class Solar_Auth_Adapter_Ini extends Solar_Auth_Adapter { * * Keys are ... * - * `file`: - * (string) Path to .ini file. + * `file` + * : (string) Path to .ini file. * * @var array * diff --git a/Solar/Auth/Adapter/Ldap.php b/Solar/Auth/Adapter/Ldap.php index a76eeb45..7c68d744 100755 --- a/Solar/Auth/Adapter/Ldap.php +++ b/Solar/Auth/Adapter/Ldap.php @@ -37,12 +37,12 @@ class Solar_Auth_Adapter_Ldap extends Solar_Auth_Adapter { * * Keys are ... * - * `uri`: - * (string) URL to the LDAP server, e.g. "ldaps://example.com:389". + * `uri` + * : (string) URL to the LDAP server, e.g. "ldaps://example.com:389". * - * `format`: - * (string) Sprintf() format string for the LDAP query; %s - * represents the username. Example: "uid=%s,dc=example,dc=com". + * `format` + * : (string) Sprintf() format string for the LDAP query; %s + * represents the username. Example: "uid=%s,dc=example,dc=com". * * @var array * diff --git a/Solar/Auth/Adapter/Mail.php b/Solar/Auth/Adapter/Mail.php index fedfa788..f05fe022 100755 --- a/Solar/Auth/Adapter/Mail.php +++ b/Solar/Auth/Adapter/Mail.php @@ -37,9 +37,9 @@ class Solar_Auth_Adapter_Mail extends Solar_Auth_Adapter { * * Keys are ... * - * `mailbox`: - * (string) An imap_open() mailbox string, e.g. - * "mail.example.com:143/imap" or "mail.example.com:110/pop3". + * `mailbox` + * : (string) An imap_open() mailbox string, e.g. + * "mail.example.com:143/imap" or "mail.example.com:110/pop3". * * @var array * diff --git a/Solar/Auth/Adapter/Post.php b/Solar/Auth/Adapter/Post.php index 0aac3ed6..db6e7749 100644 --- a/Solar/Auth/Adapter/Post.php +++ b/Solar/Auth/Adapter/Post.php @@ -24,9 +24,11 @@ * * Authenticate via simple HTTP POST request-and-reply. * - * Based in part on php.net user comments: - * http://us3.php.net/manual/en/function.fsockopen.php#57275 - * http://us3.php.net/manual/en/function.fopen.php#58099 + * Based in part on php.net user comments ... + * + * - + * + * - * * @category Solar * @@ -41,22 +43,22 @@ class Solar_Auth_Adapter_Post extends Solar_Auth_Adapter { * * Keys are ... * - * `url`: - * (string) URL to the HTTP service, e.g. "https://example.com/login.php". + * `url` + * : (string) URL to the HTTP service, e.g. "https://example.com/login.php". * - * `handle`: - * (string) The handle element name. + * `handle` + * : (string) The handle element name. * - * `passwd`: - * (string) The passwd element name. + * `passwd` + * : (string) The passwd element name. * - * `headers`: - * (array) Additional headers to use in the POST request. + * `headers` + * : (array) Additional headers to use in the POST request. * - * `replies`: - * (array) Key-value pairs where the key is the server reply - * string, and the value is a boolean indicating if it indicates success - * or failure to authenticate. + * `replies` + * : (array) Key-value pairs where the key is the server reply + * string, and the value is a boolean indicating if it indicates success + * or failure to authenticate. * * @var array * diff --git a/Solar/Auth/Adapter/Sql.php b/Solar/Auth/Adapter/Sql.php index ed321db1..ebcead9b 100755 --- a/Solar/Auth/Adapter/Sql.php +++ b/Solar/Auth/Adapter/Sql.php @@ -39,35 +39,35 @@ class Solar_Auth_Adapter_Sql extends Solar_Auth_Adapter { * * Keys are ... * - * `sql`: - * (string|array) How to get the SQL object. If a string, is - * treated as a [[Solar::registry()]] object name. If array, treated as - * config for a standalone Solar_Sql object. + * `sql` + * : (string|array) How to get the SQL object. If a string, is + * treated as a [[Solar::registry()]] object name. If array, treated as + * config for a standalone Solar_Sql object. * - * `table`: - * (string) Name of the table holding authentication data. + * `table` + * : (string) Name of the table holding authentication data. * - * `handle_col`: - * (string) Name of the column with the handle. + * `handle_col` + * : (string) Name of the column with the handle. * - * `passwd_col`: - * (string) Name of the column with the MD5-hashed passwd. + * `passwd_col` + * : (string) Name of the column with the MD5-hashed passwd. * - * `email_col`: - * (string) Name of the column with the email address. + * `email_col` + * : (string) Name of the column with the email address. * - * `moniker_col`: - * (string) Name of the column with the display name (moniker). + * `moniker_col` + * : (string) Name of the column with the display name (moniker). * - * `uri_col`: - * (string) Name of the column with the website URI. + * `uri_col` + * : (string) Name of the column with the website URI. * - * `salt`: - * (string) A salt prefix to make cracking passwords harder. + * `salt` + * : (string) A salt prefix to make cracking passwords harder. * - * `where`: - * (string|array) Additional _multiWhere() conditions to use - * when selecting rows for authentication. + * `where` + * : (string|array) Additional _multiWhere() conditions to use + * when selecting rows for authentication. * * @var array * diff --git a/Solar/Auth/Adapter/Typekey.php b/Solar/Auth/Adapter/Typekey.php index cfc4c3b3..42a37ef8 100644 --- a/Solar/Auth/Adapter/Typekey.php +++ b/Solar/Auth/Adapter/Typekey.php @@ -35,11 +35,11 @@ * * Developed for, and then donated by, Mashery.com . * - * For more info on TypeKey, see: + * For more info on TypeKey, see ... * - * * http://www.sixapart.com/typekey/api + * * * - * * http://www.sixapart.com/movabletype/docs/tk-apps + * * * * @category Solar * @@ -58,21 +58,21 @@ class Solar_Auth_Adapter_Typekey extends Solar_Auth_Adapter { * * Keys are ... * - * `token`: - * (string) The TypeKey "site token" id against which + * `token` + * : (string) The TypeKey "site token" id against which * authentication requests will be made. * - * `window`: - * (int) The signature should have been generated + * `window` + * : (int) The signature should have been generated * within this many seconds of "now". Default is 10 seconds, to * allow for long network latency periods. * - * `cache`: - * (dependency) A Solar_Cache dependency for storing + * `cache` + * : (dependency) A Solar_Cache dependency for storing * the TypeKey public key data. * - * `cache_key`: - * (string) When using a cache, the entry key for + * `cache_key` + * : (string) When using a cache, the entry key for * the TypeKey public key data. Default 'typekey_pubkey'. * */ @@ -109,7 +109,7 @@ class Solar_Auth_Adapter_Typekey extends Solar_Auth_Adapter { /** * - * DSA signature extracted from login attempt $_GET vars. + * DSA signature extracted from login attempt GET request vars. * * @var string * @@ -172,26 +172,24 @@ public function __construct($config) * * Is the current page-load a login request? * - * We can tell because there will be certain GET params in place: + * We can tell because there will be certain GET params in place ... * - * - * &ts=1149633028 - * &email=clay%40mashery.com - * &name=mashery - * &nick=Solar - * &sig=PBG7mN48V9f83hOX5Ao+X9GbmUU=:maoKWgIZpcF1qVFUHf8GbFooAFc= - * }} + * &ts=1149633028 + * &email=user%40example.com + * &name=handle + * &nick=Moni%20Kerr + * &sig=PBG7mN48V9f83hOX5Ao+X9GbmUU=:maoKWgIZpcF1qVFUHf8GbFooAFc= * * @return bool * */ public function isLoginRequest() { - return ! empty($_GET['email']) && - ! empty($_GET['name']) && - ! empty($_GET['nick']) && - ! empty($_GET['ts']) && - ! empty($_GET['sig']); + return ! empty($this->_request->get['email']) && + ! empty($this->_request->get['name']) && + ! empty($this->_request->get['nick']) && + ! empty($this->_request->get['ts']) && + ! empty($this->_request->get['sig']); } /** @@ -255,17 +253,19 @@ public function isLoginValid() // no errors yet ;-) $this->_err = null; - // get data from the login. - $email = $_GET['email']; - $name = $_GET['name']; - $nick = $_GET['nick']; - $ts = $_GET['ts']; + // get data from the request. + $email = $this->_request->get('email'); + $name = $this->_request->get('name'); + $nick = $this->_request->get('nick'); + $ts = $this->_request->get('ts'); // get the signature values from the login. note that the sig // values need to have pluses converted to spaces because // urldecode() doesn't do that for us. thus, we have to re- // encode, the raw-decode it. - $this->_sig = rawurldecode(urlencode($_GET['sig'])); + $this->_sig = rawurldecode( + urlencode($this->_request->get('sig')) + ); // re-create the message for signature comparison. // :::::: diff --git a/Solar/Base.php b/Solar/Base.php index 44702c16..1add0415 100755 --- a/Solar/Base.php +++ b/Solar/Base.php @@ -20,7 +20,7 @@ * Abstract base class for all Solar objects. * * This is the class from which almost all other Solar classes are - * extended. Solar_Base is relatively light, and provides: + * extended. Solar_Base is relatively light, and provides ... * * * Construction-time reading of [Main:ConfigFile config file] options * for itself, and merging of those options with any options passed diff --git a/Solar/Cache.php b/Solar/Cache.php index e46e7355..69b49e69 100755 --- a/Solar/Cache.php +++ b/Solar/Cache.php @@ -32,15 +32,15 @@ class Solar_Cache extends Solar_Base { * * Keys are ... * - * `active`: - * (bool) Whether the cache is active or not when instantiated. + * `active` + * : (bool) Whether the cache is active or not when instantiated. * - * `adapter`: - * (string) The adapter class, default 'Solar_Cache_Adapter_File'. + * `adapter` + * : (string) The adapter class, default 'Solar_Cache_Adapter_File'. * - * `config`: - * (array) Construction-time config keys to pass to the adapter - * to override Solar.config.php values. Default is null. + * `config` + * : (array) Construction-time config keys to pass to the adapter + * to override Solar.config.php values. Default is null. * * @var array * @@ -95,8 +95,6 @@ public function __construct($config = null) * * Makes the cache active (true) or inactive (false). * - * Example: - * * {{code: php * $cache = Solar::factory('Solar_Cache'); * @@ -121,8 +119,6 @@ public function setActive($flag) * * Gets the current activity state of the cache (on or off). * - * Example: - * * {{code: php * $cache = Solar::factory('Solar_Cache'); * @@ -165,6 +161,7 @@ public function getLife() * For example, to store an array in the cache ... * * {{code: php + * // create a cache object * $cache = Solar::factory('Solar_Cache'); * * // create a unique ID @@ -213,14 +210,17 @@ public function save($key, $data) * the contents of the cache entry. * * For example, to get a cache entry identified by a web page - * name, you could do this: + * name, you could do this ... * * {{code: php - * // create a cache object - * $cache = Solar::factory('Solar_Cache'); + * // create a request object + * $request = Solar::factory('Solar_Request'); * * // get the request URI as an identifier - * $id = Solar::server('REQUEST_URI'); + * $id = $request->server('REQUEST_URI'); + * + * // create a cache object + * $cache = Solar::factory('Solar_Cache'); * * // fetch the result and dump it to screen * $result = $cache->fetch($id); @@ -245,13 +245,15 @@ public function fetch($key) * * Deletes a cache entry. * - * Example: - * * {{code: php - * $cache = Solar::factory('Solar_Cache'); + * // create a request object + * $request = Solar::factory('Solar_Request'); * * // create an entry ID named for the current URI - * $id = Solar::server('REQUEST_URI'); + * $id = $request->server('REQUEST_URI'); + * + * // create a cache object + * $cache = Solar::factory('Solar_Cache'); * * // delete any cache entry with that ID * $cache->delete($id); @@ -273,12 +275,8 @@ public function delete($key) * * Deletes all entries from the cache. * - * Example: - * * {{code: php * $cache = Solar::factory('Solar_Cache'); - * - * // delete all entries * $cache->deleteAll(); * }} * @@ -303,10 +301,14 @@ public function deleteAll() * the cache entry. * * {{code: php - * $cache = Solar::factory('Solar_Cache'); + * // create a request object + * $request = Solar::factory('Solar_Request'); * * // create an entry ID named for the current URI - * $id = Solar::server('REQUEST_URI'); + * $id = $request->server('REQUEST_URI'); + * + * // create a cache object + * $cache = Solar::factory('Solar_Cache'); * * // find out what the underlying cache adapter uses as the entry name * $real_name = $cache->entry($id); diff --git a/Solar/Cache/Adapter.php b/Solar/Cache/Adapter.php index 6557934b..44269838 100644 --- a/Solar/Cache/Adapter.php +++ b/Solar/Cache/Adapter.php @@ -32,8 +32,8 @@ abstract class Solar_Cache_Adapter extends Solar_Base { * * Config keys are ... * - * `life`: - * (int) The lifetime of each cache entry in seconds. + * `life` + * : (int) The lifetime of each cache entry in seconds. * * @var array * diff --git a/Solar/Cache/Adapter/Apc.php b/Solar/Cache/Adapter/Apc.php index 4f9f2d7c..90a81080 100755 --- a/Solar/Cache/Adapter/Apc.php +++ b/Solar/Cache/Adapter/Apc.php @@ -45,9 +45,9 @@ class Solar_Cache_Adapter_Apc extends Solar_Cache_Adapter { * * Keys are ... * - * `life`: - * (int) The cache entry lifetime in seconds, default 0 - * (never expires). + * `life` + * : (int) The cache entry lifetime in seconds, default 0 + * (never expires). * * @var array * diff --git a/Solar/Cache/Adapter/Eaccelerator.php b/Solar/Cache/Adapter/Eaccelerator.php index e35d6e4f..d0298c16 100755 --- a/Solar/Cache/Adapter/Eaccelerator.php +++ b/Solar/Cache/Adapter/Eaccelerator.php @@ -46,9 +46,9 @@ class Solar_Cache_Adapter_Eaccelerator extends Solar_Cache_Adapter { * * Keys are ... * - * `life`: - * (int) The cache entry lifetime in seconds, default 0 - * (never expires). + * `life` + * : (int) The cache entry lifetime in seconds, default 0 + * (never expires). * * @var array * diff --git a/Solar/Cache/Adapter/File.php b/Solar/Cache/Adapter/File.php index b998d103..d7559794 100755 --- a/Solar/Cache/Adapter/File.php +++ b/Solar/Cache/Adapter/File.php @@ -58,14 +58,14 @@ class Solar_Cache_Adapter_File extends Solar_Cache_Adapter { * * Config keys are ... * - * `path`: - * (string) The directory where cache files are located; - * should be readable and writable by the script process, usually - * the web server process. Default is '/tmp/Solar_Cache_File/'. - * - * `life`: - * (int) The lifetime of each cache entry in seconds; - * default is 3600 seconds (i.e., 1 hour). + * `path` + * : (string) The directory where cache files are located; + * should be readable and writable by the script process, usually + * the web server process. Default is '/tmp/Solar_Cache_File/'. + * + * `life` + * : (int) The lifetime of each cache entry in seconds; + * default is 3600 seconds (i.e., 1 hour). * * @var array * diff --git a/Solar/Cache/Adapter/Memcache.php b/Solar/Cache/Adapter/Memcache.php index 2b229dfc..ac7ff936 100755 --- a/Solar/Cache/Adapter/Memcache.php +++ b/Solar/Cache/Adapter/Memcache.php @@ -51,19 +51,19 @@ class Solar_Cache_Adapter_Memcache extends Solar_Cache_Adapter { * * Keys are ... * - * `host`: - * (string) The memcached host name, default 'localhost'. + * `host` + * : (string) The memcached host name, default 'localhost'. * - * `port`: - * (int) The memcached port number, default 11211. + * `port` + * : (int) The memcached port number, default 11211. * - * `life`: - * (int) The cache entry lifetime in seconds, default 60. + * `life` + * : (int) The cache entry lifetime in seconds, default 60. * - * `timeout`: - * (int) The timeout before the server connection is - * considered a miss, in seconds. Default is 1 second, and should - * not really be changed for reasons other than testing purposes. + * `timeout` + * : (int) The timeout before the server connection is + * considered a miss, in seconds. Default is 1 second, and should + * not really be changed for reasons other than testing purposes. * * @var array * @@ -88,17 +88,6 @@ class Solar_Cache_Adapter_Memcache extends Solar_Cache_Adapter { * * Constructor. * - * Config keys are ... - * - * `host`: - * (string) The hostname of the memcached server, default 'localhost' - * - * `port`: - * (int) The port number for the memcached server, default 11211 - * - * `life`: - * (int) The lifetime of each cache entry in seconds, default 60 (1 minute) - * * @param array $config User-provided configuration values. * */ diff --git a/Solar/Cache/Adapter/Xcache.php b/Solar/Cache/Adapter/Xcache.php index 23172407..efe0e7e9 100755 --- a/Solar/Cache/Adapter/Xcache.php +++ b/Solar/Cache/Adapter/Xcache.php @@ -44,19 +44,19 @@ class Solar_Cache_Adapter_Xcache extends Solar_Cache_Adapter { * * Keys are ... * - * `life`: - * _(int)_ The cache entry lifetime in seconds, default `0` + * `life` + * : (int) The cache entry lifetime in seconds, default `0` * (never expires). * - * `user`: - * _(string)_ Admin user name for Xcache, as set in php.ini. This login - * and the corresponding password are required _only_ for the deleteAll() - * method. Defaults to `null`. + * `user` + * : (string) Admin user name for Xcache, as set in php.ini. This login + * and the corresponding password are required _only_ for the deleteAll() + * method. Defaults to `null`. * - * `pass`: - * _(string)_ Plaintext password that matches the md5() encrypted password - * in php.ini. This password and the corresponding login are required - * _only_ for the deleteAll() method. Defaults to `null`. + * `pass` + * : (string) Plaintext password that matches the md5() encrypted password + * in php.ini. This password and the corresponding login are required + * _only_ for the deleteAll() method. Defaults to `null`. * * @var array * @@ -148,6 +148,9 @@ public function deleteAll() $olduser = null; $oldpass = null; + // we need to work with actual PHP superglobal $_SERVER here, + // instead of a Solar_Request::$server value, because the APC + // extension doesn't know about Solar_Request. if (isset($_SERVER['PHP_AUTH_USER'])) { $olduser = $_SERVER['PHP_AUTH_USER']; } @@ -163,7 +166,7 @@ public function deleteAll() // clear user cache $vcnt = xcache_count(XC_TYPE_VAR); for ($i = 0; $i < $vcnt; $i++) { - if (!xcache_clear_cache(XC_TYPE_VAR, $i)) { + if (! xcache_clear_cache(XC_TYPE_VAR, $i)) { return false; } } diff --git a/Solar/Class/Stack.php b/Solar/Class/Stack.php index 344366f8..289e2f73 100644 --- a/Solar/Class/Stack.php +++ b/Solar/Class/Stack.php @@ -54,8 +54,6 @@ public function get() * * Adds one or more classes to the stack. * - * For example: - * * {{code: php * $stack = Solar::factory('Solar_Class_Stack'); * $stack->add(array('Base1', 'Base2', 'Base3')); @@ -108,8 +106,6 @@ public function add($list) * * Clears the stack and adds one or more classes. * - * For example: - * * {{code: php * $stack = Solar::factory('Solar_Class_Stack'); * $stack->add('Base1'); @@ -140,8 +136,6 @@ public function set($list) * * Loads a class using the class stack prefixes. * - * For example: - * * {{code: php * $stack = Solar::factory('Solar_Class_Stack'); * $stack->add('Base1'); diff --git a/Solar/Content.php b/Solar/Content.php index 73e638ba..6f277cc8 100755 --- a/Solar/Content.php +++ b/Solar/Content.php @@ -34,9 +34,9 @@ class Solar_Content extends Solar_Base { * * Keys are ... * - * `sql`: - * (dependency) A Solar_Sql dependency injection, passed - * into the table objects at creation time. + * `sql` + * : (dependency) A Solar_Sql dependency injection, passed + * into the table objects at creation time. * * @param array * diff --git a/Solar/Content/Abstract.php b/Solar/Content/Abstract.php index 294c1bfc..36381887 100644 --- a/Solar/Content/Abstract.php +++ b/Solar/Content/Abstract.php @@ -30,14 +30,14 @@ abstract class Solar_Content_Abstract extends Solar_Base { * * User-defined configuaration values. * - * `content`: - * (dependency) A Solar_Content dependency object. + * `content` + * : (dependency) A Solar_Content dependency object. * - * `area_id`: - * (int) Only work with this area_id (if any). + * `area_id` + * : (int) Only work with this area_id (if any). * - * `paging`: - * (int) The number of rows per page when fetching pages. + * `paging` + * : (int) The number of rows per page when fetching pages. * * @var array * @@ -186,188 +186,6 @@ public function getPaging() return $this->_paging; } - /** - * - * Fetch a list of nodes of the node type. - * - * @param string|array $tags Fetch nodes with all these tags; if - * empty, ignores tags. - * - * @param string|array $where A set of multiWhere() conditions to - * determine which nodes are fetched. - * - * @param string|array $order Order the returned rows in this - * fashion. - * - * @param int $page Which page-number of results to fetch. - * - * @return Solar_Sql_Rowset - * - */ - public function fetchAll($tags = null, $where = null, $order = null, - $page = null) - { - // set the default order if needed - if (! $order) { - $order = $this->_order; - } - - if (! empty($tags)) { - // force the tags to an array (for the IN(...) clause) - $tags = $this->_content->tags->asArray($tags); - } - - // getting just tags, or just part-counts, is fine as a normal select. - // but getting tagged part-counts requires a sub-select. - if ($tags && $this->_parts) { - - // create the tags inner select - $subselect = Solar::factory('Solar_Sql_Select'); - - $subselect->from($this->_content->nodes, '*') - ->multiWhere($this->_where()) - ->multiWhere($where); - - // join for area name - $subselect->join( - $this->_content->areas, - 'nodes.area_id = areas.id', - 'name AS area_name' - ); - - $this->_selectTags($subselect, $tags); - - // wrap in a part-count outer select - $select = Solar::factory('Solar_Sql_Select'); - $select->fromSelect($subselect, 'nodes'); - $this->_selectPartCounts($select, $this->_parts); - - } else { - - $select = Solar::factory('Solar_Sql_Select'); - - $select->from($this->_content->nodes, '*') - ->multiWhere($this->_where()) - ->multiWhere($where); - - // join for area name - $select->join( - $this->_content->areas, - 'nodes.area_id = areas.id', - 'name AS area_name' - ); - - if ($tags) { - $this->_selectTags($select, $tags); - } elseif ($this->_parts) { - $this->_selectPartCounts($select, $this->_parts); - } - - } - - $select->setPaging($this->_paging); - $select->order($order); - $select->limitPage($page); - - $all = $select->fetch('all'); - $all->setSave($this); - return $all; - } - - /** - * - * Given an existing select object, add part-count selection to it. - * - * Note that this acts on the object reference directly. - * - * @param Solar_Sql_Select $select The select object. - * - * @param array $parts The parts to get counts for. - * - * @return void - * - */ - protected function _selectPartCounts($select, $parts) - { - // join each table and get a count - foreach ($parts as $part) { - // we left-join so that an absences of a part-type does - // not return 0 rows for the main type - // - // LEFT JOIN nodes AS comment_parts ON comment_parts.parent_id = nodes.id - $join = $part . '_parts'; - $type = $select->quote($part); - $count = $part . '_count'; - $select->leftJoin( - // this table - "nodes AS $join", - // on these conditions - "$join.parent_id = nodes.id AND $join.type = $type", - // with these columns - "COUNT($join.id) AS $count" - ); - } - $select->group('nodes.id'); - } - - /** - * - * Given an existing select object, add tag-based selection to it. - * - * Note that this acts on the object reference directly. - * - * @param Solar_Sql_Select $select The select object. - * - * @param array $tags Select nodes with these tags. - * - * @return void - * - */ - protected function _selectTags($select, $tags) - { - $select->join($this->_content->tags, 'tags.node_id = nodes.id') - ->where('tags.name IN (?)', $tags) - ->having("COUNT(nodes.id) = ?", count($tags)) - ->group("nodes.id"); - } - - /** - * - * Fetch a total count and pages of nodes in the content store. - * - * @param string|array $tags Count nodes with all these - * tags; if empty, counts for all tags. - * - * @param string|array $where A set of multiWhere() conditions to - * determine which nodes are fetched. - * - * @return array A array with keys 'count' (total number of - * bookmarks) and 'pages' (number of pages). - * - */ - public function countPages($tags = null, $where = null) - { - $select = Solar::factory('Solar_Sql_Select'); - $select->from($this->_content->nodes, 'id'); - $select->multiWhere($this->_where()); - $select->multiWhere($where); - - // using tags? - $tags = $this->_content->tags->asArray($tags); - if ($tags) { - // add tags to the query - $this->_selectTags($select, $tags); - // wrap as a sub-select - $wrap = Solar::factory('Solar_Sql_Select'); - $wrap->fromSelect($select, 'nodes'); - $wrap->setPaging($this->_paging); - return $wrap->countPages('nodes.id'); - } else { - // no need for subselect - return $select->countPages('nodes.id'); - } - } - /** * * Fetch one node by ID. @@ -406,11 +224,6 @@ public function fetchWhere($where, $order = null) 'name AS area_name' ); - // get part counts? - if ($this->_parts) { - $this->_selectPartCounts($select, $this->_parts); - } - // add master and user conditions $select->multiWhere($this->_where()); $select->multiWhere($where); @@ -423,9 +236,23 @@ public function fetchWhere($where, $order = null) // get the row $row = $select->fetch('row'); $row->setSave($this); + + // fetch and append the part counts. + // check that the row ID exists so that we don't get SQL + // errors looking for empty/nonexistent nodes. + if ($this->_parts && $row->id) { + $part_count = $this->_fetchPartCounts($row->id); + foreach ($part_count as $val) { + $col = $val['type'] . '_count'; + $row->$col = $val['part_count']; + } + } + + // done! return $row; } + /** * * Fetch the parts of a parent node ID. @@ -530,6 +357,116 @@ public function fetchTags($where = null) return $select->fetch('pairs'); } + /** + * + * Fetch a list of nodes of the node type. + * + * @param string|array $tags Fetch nodes with all these tags; if + * empty, ignores tags. + * + * @param string|array $where A set of multiWhere() conditions to + * determine which nodes are fetched. + * + * @param string|array $order Order the returned rows in this + * fashion. + * + * @param int $page Which page-number of results to fetch. + * + * @return Solar_Sql_Rowset + * + */ + public function fetchAll($tags = null, $where = null, $order = null, + $page = null) + { + // set the default order if needed + if (! $order) { + $order = $this->_order; + } + + // basic selection + $select = Solar::factory('Solar_Sql_Select'); + $select->from($this->_content->nodes, '*') + ->multiWhere($this->_where()) + ->multiWhere($where); + + // join for area name + $select->join( + $this->_content->areas, + 'nodes.area_id = areas.id', + 'name AS area_name' + ); + + // add tags? + if (! empty($tags)) { + // force the tags to an array (for the IN(...) clause) + $tags = $this->_content->tags->asArray($tags); + $this->_selectTags($select, $tags); + } + + // complete the select + $select->setPaging($this->_paging); + $select->order($order); + $select->limitPage($page); + + // fetch data as assoc array keyed on node ID + $data = $select->fetch('assoc'); + + // part counts? also check to make sure $data is not empty, + // thus avoiding SQL errors when finding parts for an empty + // rowset. + if ($this->_parts && $data) { + // fetch and retain part counts + $part_count = $this->_fetchPartCounts(array_keys($data)); + foreach ($part_count as $val) { + $id = $val['id']; + $col = $val['type'] . '_count'; + $data[$id][$col] = $val['part_count']; + } + } + + // convert to a Solar_Sql_Rowset and return + $rowset = Solar::factory('Solar_Sql_Rowset', array('data' => $data)); + $rowset->setSave($this); + return $rowset; + } + + /** + * + * Fetch a total count and pages of nodes in the content store. + * + * @param string|array $tags Count nodes with all these + * tags; if empty, counts for all tags. + * + * @param string|array $where A set of multiWhere() conditions to + * determine which nodes are fetched. + * + * @return array A array with keys 'count' (total number of + * bookmarks) and 'pages' (number of pages). + * + */ + public function countPages($tags = null, $where = null) + { + $select = Solar::factory('Solar_Sql_Select'); + $select->from($this->_content->nodes, 'id'); + $select->multiWhere($this->_where()); + $select->multiWhere($where); + + // using tags? + $tags = $this->_content->tags->asArray($tags); + if ($tags) { + // add tags to the query + $this->_selectTags($select, $tags); + // wrap as a sub-select + $wrap = Solar::factory('Solar_Sql_Select'); + $wrap->fromSelect($select, 'nodes'); + $wrap->setPaging($this->_paging); + return $wrap->countPages('nodes.id'); + } else { + // no need for subselect + return $select->countPages('nodes.id'); + } + } + /** * * Inserts or updates a node. @@ -563,7 +500,8 @@ public function insert($data) $data['type'] = $this->_type; // force the IP address - $data['editor_ipaddr'] = Solar::server('REMOTE_ADDR'); + $request = Solar::factory('Solar_Request'); + $data['editor_ipaddr'] = $request->server('REMOTE_ADDR'); // force the created timestamp $data['created'] = date('Y-m-d\TH:i:s'); @@ -598,7 +536,8 @@ public function update($data) $data['type'] = $this->_type; // force the IP address - $data['editor_ipaddr'] = Solar::server('REMOTE_ADDR'); + $request = Solar::factory('Solar_Request'); + $data['editor_ipaddr'] = $request->server('REMOTE_ADDR'); // force the updated timestamp $data['updated'] = date('Y-m-d\TH:i:s'); @@ -722,5 +661,79 @@ protected function _where() // done return $where; } + + /** + * + * Given an existing select object, add tag-based selection to it. + * + * Note that this acts on the object reference directly. + * + * @param Solar_Sql_Select $select The select object. + * + * @param array $tags Select nodes with these tags. + * + * @return void + * + */ + protected function _selectTags($select, $tags) + { + $select->join($this->_content->tags, 'tags.node_id = nodes.id') + ->where('tags.name IN (?)', $tags) + ->having("COUNT(nodes.id) = ?", count($tags)) + ->group("nodes.id"); + } + + /** + * + * Fetch an array of part-counts for specific node IDs. + * + * The returned array is sequential; each element is an array with + * keys for the node ID, the part-type being counted, and the count + * of nodes with that part type belonging to the parent node. + * + * There are either one or two entries for each node: one with a + * zero count (to force the existence of each part-type for that + * node ID), then a second entry with the actual count (if the node + * ID has parts of that type). + * + * @param string|array $id_list The IDs to get part counts for. + * + * @return array The part-counts as a sequential array. + * + * + */ + protected function _fetchPartCounts($id_list) + { + // force to array + settype($id_list, 'array'); + + // prepend with zero-counts so that all parts are represented + $zero = array(); + foreach ($id_list as $id) { + foreach ($this->_parts as $type) { + $zero[] = array( + 'id' => $id, + 'type' => $type, + 'part_count' => '0', + ); + } + } + + // get a list of parent_id, part type, and part count + $select = Solar::factory('Solar_Sql_Select'); + $select->from('nodes', array( + 'parent_id AS id', + 'type', + 'COUNT(id) AS part_count' + )); + $select->where('parent_id IN(?)', $id_list); + $select->where('type IN(?)', $this->_parts); + $select->group(array('nodes.parent_id', 'nodes.type')); + $result = $select->fetch('array'); + + // append the results to the zero base, and we're done + $result = array_merge($zero, $result); + return $result; + } } ?> diff --git a/Solar/Controller/Exception.php b/Solar/Controller/Exception.php new file mode 100644 index 00000000..afc013ec --- /dev/null +++ b/Solar/Controller/Exception.php @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/Solar/Controller/Front.php b/Solar/Controller/Front.php index 574f70f2..34cac26a 100644 --- a/Solar/Controller/Front.php +++ b/Solar/Controller/Front.php @@ -19,7 +19,8 @@ * * Front-controller class for Solar. * - * An example front-controller "index.php" for your web root: + * An example bootstrap "index.php" for your web root using the front + * controller ... * * {{code: php * require 'Solar.php'; diff --git a/Solar/Controller/Page.php b/Solar/Controller/Page.php index 4c8b737c..2196168f 100644 --- a/Solar/Controller/Page.php +++ b/Solar/Controller/Page.php @@ -24,59 +24,56 @@ * * Abstract page controller class. * - * Expects a directory structure similar to the following: + * Expects a directory structure similar to the following ... * - * - * Vendor/ # your vendor namespace - * App/ # subdirectory for page controllers - * Helper/ # shared helper classes - * ... - * Layout/ # shared layout files - * ... - * Locale/ # shared locale files - * ... - * View/ # shared view scripts - * ... - * Example.php # an example page controller app - * Example/ - * Helper/ # helper classes specific to the page - * ... - * Layout/ # layout files to override shared layouts - * ... - * Locale/ # locale files - * en_US.php - * pt_BR.php - * View/ # view scripts - * _item.php # partial template - * list.php # full template - * edit.php - * }} + * Vendor/ # your vendor namespace + * App/ # subdirectory for page controllers + * Helper/ # shared helper classes + * ... + * Layout/ # shared layout files + * ... + * Locale/ # shared locale files + * ... + * View/ # shared view scripts + * ... + * Example.php # an example page controller app + * Example/ + * Helper/ # helper classes specific to the page + * ... + * Layout/ # layout files to override shared layouts + * ... + * Locale/ # locale files + * en_US.php + * pt_BR.php + * View/ # view scripts + * _item.php # partial template + * list.php # full template + * edit.php * * Note that models are not included in the application itself; this is * for class-name deconfliction reasons. Your models should be stored * elsewhere in the Solar hierarchy, e.g. Vendor_Model_Name. * - * When you call Solar_Controller_Page::fetch(), these intercept methods - * are run in the following order: + * When you call [[fetch()]], these intercept methods + * are run in the following order ... * - * * Solar_Controller_Page::_load() to load class properties from the + * * [[_load()]] to load class properties from the * fetch() URI specification * - * * Solar_Controller_Page::_preRun() before the first action + * * [[_preRun()]] before the first action * - * * Solar_Controller_Page::_preAction() before each action (including + * * [[_preAction()]] before each action (including * _forward()-ed actions) * * * ... The action method itself runs here ... * - * * Solar_Controller_Page::_postAction() after each action + * * [[_postAction()]] after each action * - * * Solar_Controller_Page::_postRun() after the last action, and before - * rendering + * * [[_postRun()]] after the last action, and before rendering * - * * Solar_Controller_Page::_render() to render the view and layout; - * this in its turn calls Solar_Controller_Page::_getView() for - * the view object, and Solar_Controller_Page::_setViewLayout() to + * * [[_render()]] to render the view and layout; + * this in its turn calls [[_getView()]] for + * the view object, and [[_setViewLayout()]] to * reset the view object to use layout templates. * * @category Solar @@ -106,12 +103,12 @@ abstract class Solar_Controller_Page extends Solar_Base { /** * - * Flash-messaging object. + * Session data, including read-once flashes. * - * @var Solar_Flash + * @var Solar_Session * */ - protected $_flash; + protected $_session; /** * @@ -180,6 +177,15 @@ abstract class Solar_Controller_Page extends Solar_Base { */ protected $_view = null; + /** + * + * Request environment details: get, post, etc. + * + * @var Solar_Request + * + */ + protected $_request; + /** * * Constructor. @@ -191,6 +197,9 @@ public function __construct($config = null) { $class = get_class($this); + // create the request object + $this->_request = Solar::factory('Solar_Request'); + // auto-set the name; e.g. Solar_App_Something => 'something' if (empty($this->_name)) { $pos = strrpos($class, '_'); @@ -199,8 +208,8 @@ public function __construct($config = null) } // create the flash object - $this->_flash = Solar::factory( - 'Solar_Flash', + $this->_session = Solar::factory( + 'Solar_Session', array('class' => $class) ); @@ -251,7 +260,7 @@ public function __get($key) * * Executes the requested action and returns its output with layout. * - * @param string $spec The action specification string, e.g.: + * @param string $spec The action specification string, e.g., * "tags/php+framework" or "user/pmjones/php+framework?page=3" * * @return string The results of the action + view + layout. @@ -279,7 +288,7 @@ public function fetch($spec = null) * * Executes the requested action and displays its output. * - * @param string $spec The action specification string, e.g.: + * @param string $spec The action specification string, e.g., * "tags/php+framework" or "user/pmjones/php+framework?page=3" * * @return void @@ -297,7 +306,7 @@ public function display($spec = null) * @return string The results of the view and layout scripts. * */ - public function _render() + protected function _render() { // get a view object and assign variables $view = $this->_getView(); @@ -336,22 +345,22 @@ public function _render() * Creates and returns a new Solar_View object for a view. * * Automatically sets up a template-path stack for you, searching - * for view files in this order: + * for view files in this order ... * - * # Vendor/App/Example/View/ + * 1. Vendor/App/Example/View/ * - * # Vendor/App/View + * 2. Vendor/App/View * * Automatically sets up a helper-class stack for you, searching - * for helper classes in this order: + * for helper classes in this order ... * - * # Vendor_App_Example_Helper_ + * 1. Vendor_App_Example_Helper_ * - * # Vendor_App_Helper_ + * 2. Vendor_App_Helper_ * - * # Vendor_View_Helper_ + * 3. Vendor_View_Helper_ * - * # Solar_View_Helper_ (this is part of Solar_View to begin with) + * 4. Solar_View_Helper_ (this is part of Solar_View to begin with) * * @return Solar_View * @@ -414,13 +423,13 @@ protected function _getView() * the layout with zero effort. * * Automatically sets up a template-path stack for you, searching - * for layout files in this order: + * for layout files in this order ... * - * # Vendor/App/Example/Layout/ + * 1. Vendor/App/Example/Layout/ * - * # Vendor/App/Layout/ + * 2. Vendor/App/Layout/ * - * # Solar/App/Layout/ + * 3. Solar/App/Layout/ * * @param Solar_View $view The Solar_View object to modify. * @@ -658,36 +667,46 @@ protected function _forward($action, $params = null) * * Reports whether or not user requested a specific submit type. * - * By default, looks for $_submit_key in [[Solar::post()]] to get the + * By default, looks for $submit_key in [[Solar_Request::post()]] to get the * value of the submit request. * * Checks against "SUBMIT_$type" locale string for matching. E.g., - * $this->_isSubmit('save') checks Solar::post('submit') against - * $this->locale('SUBMIT_SAVE'). + * $this->_isSubmit('save') checks Solar_Request::post('submit') + * against $this->locale('SUBMIT_SAVE'). * * @param string $type The submit type; e.g., 'save', 'delete', - * 'preview', etc. + * 'preview', etc. If empty, returns true if *any* submission type + * was posted. * * @param string $submit_key If not empty, check against this - * [[Solar::post()]] key instead $this->_submit_key. Default null. + * [[Solar_Request::post()]] key instead $this->_submit_key. Default + * null. * * @return bool * */ - protected function _isSubmit($type, $submit_key = null) - { - $locale_key = 'SUBMIT_' . strtoupper($type); - + protected function _isSubmit($type = null, $submit_key = null) + { + // make sure we know what post-var to look in if (empty($submit_key)) { $submit_key = $this->_submit_key; } - - $submit = Solar::post($submit_key, false); + + // didn't ask for a submission type; answer if *any* submission + // was attempted. + if (empty($type)) { + $any = $this->_request->post($submit_key); + return ! empty($any); + } + + // asked for a submission type, find the locale string for it. + $locale_key = 'SUBMIT_' . strtoupper($type); $locale = $this->locale($locale_key); // $submit must be non-empty, and must match locale string. // not enough just to match the locale string, as it might // be empty. + $submit = $this->_request->post($submit_key, false); return $submit && $submit == $locale; } diff --git a/Solar/Controller/Page/Exception.php b/Solar/Controller/Page/Exception.php new file mode 100644 index 00000000..10914112 --- /dev/null +++ b/Solar/Controller/Page/Exception.php @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/Solar/Controller/Page/Exception/ActionNotFound.php b/Solar/Controller/Page/Exception/ActionNotFound.php new file mode 100644 index 00000000..84e4995b --- /dev/null +++ b/Solar/Controller/Page/Exception/ActionNotFound.php @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/Solar/Controller/Page/Exception/PropertyNotDefined.php b/Solar/Controller/Page/Exception/PropertyNotDefined.php new file mode 100644 index 00000000..80fe38bb --- /dev/null +++ b/Solar/Controller/Page/Exception/PropertyNotDefined.php @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/Solar/Debug/Timer.php b/Solar/Debug/Timer.php index a2201dd4..5a3a0fb0 100755 --- a/Solar/Debug/Timer.php +++ b/Solar/Debug/Timer.php @@ -32,15 +32,15 @@ class Solar_Debug_Timer extends Solar_Base { * * Keys are ... * - * `output`: - * (string) Output mode. Set to 'html' for HTML; + * `output` + * : (string) Output mode. Set to 'html' for HTML; * or 'text' for plain text. Default autodetects by SAPI version. * - * `auto_start`: - * (bool) When true, starts the timer at __construct() time. Default false. + * `auto_start` + * : (bool) When true, starts the timer at __construct() time. Default false. * - * `auto_display`: - * (bool) When true, calls display() at __destruct() time. Default false. + * `auto_display` + * : (bool) When true, calls display() at __destruct() time. Default false. * * @var array * diff --git a/Solar/Debug/Var.php b/Solar/Debug/Var.php index 9b9ff93f..b54bb3e0 100755 --- a/Solar/Debug/Var.php +++ b/Solar/Debug/Var.php @@ -23,7 +23,7 @@ * [[php::var_dump() | ]] and output it to the screen either as * plaintext or in HTML format. * - * For example: + * For example ... * * {{code: php * require_once 'Solar.php'; @@ -58,9 +58,9 @@ class Solar_Debug_Var extends Solar_Base { * * Keys are ... * - * `output`: - * (string) Output mode. Set to 'html' for HTML; - * or 'text' for plain text. Default autodetects by SAPI version. + * `output` + * : (string) Output mode. Set to 'html' for HTML; + * or 'text' for plain text. Default autodetects by SAPI version. * * @var array * diff --git a/Solar/Docs/Apiref.php b/Solar/Docs/Apiref.php index 19c3e5c1..31fa87e3 100755 --- a/Solar/Docs/Apiref.php +++ b/Solar/Docs/Apiref.php @@ -40,15 +40,15 @@ class Solar_Docs_Apiref extends Solar_Base { * * Keys are ... * - * `phpdoc`: - * (dependency) A Solar_Docs_Phpdoc dependency. + * `phpdoc` + * : (dependency) A Solar_Docs_Phpdoc dependency. * - * `log`: - * (dependency) A Solar_Log dependency. + * `log` + * : (dependency) A Solar_Log dependency. * - * `unknown`: - * (string) When a type is unknown or not specified, - * use this value instead. + * `unknown` + * : (string) When a type is unknown or not specified, + * use this value instead. * * @var array * diff --git a/Solar/Docs/Phpdoc.php b/Solar/Docs/Phpdoc.php index 27b7f585..618421f2 100644 --- a/Solar/Docs/Phpdoc.php +++ b/Solar/Docs/Phpdoc.php @@ -25,25 +25,26 @@ * * Supported technical tags are ... * - * - * For classes: - * @category name # category for the package - * @package name # class package name - * @subpackage name # class subpackage name + * For classes ... + * @category name # category for the package + * @package name # class package name + * @subpackage name # class subpackage name * - * For properties: - * @var type [summary] # class property + * For properties ... * - * For methods: - * @param type [$name] [summary] # method parameter - * @return type [summary] # method return - * @throws class [summary] # exceptions thrown by method - * @exception class [summary] # alias to @throws + * @var type [summary] # class property * - * General-purpose: - * @see name # "see also" this element name - * @todo summary # todo item - * }} + * For methods ... + * + * @param type [$name] [summary] # method parameter + * @return type [summary] # method return + * @throws class [summary] # exceptions thrown by method + * @exception class [summary] # alias to @throws + * + * General-purpose ... + * + * @see name # "see also" this element name + * @todo summary # todo item * * @category Solar * @@ -414,7 +415,8 @@ protected function _2part($line) /** * - * WHAT WE PROBABLY WILL SUPPORT: + * WHAT WE PROBABLY WILL SUPPORT + * * @author name * @copyright name date * @deprecated summary @@ -426,7 +428,8 @@ protected function _2part($line) * @staticvar name type summary * @version version * - * WHAT WE PROBABLY WILL NOT SUPPORT: + * WHAT WE PROBABLY WILL NOT SUPPORT + * * @access public or private * @global type $globalvarname * or diff --git a/Solar/Flash.php b/Solar/Flash.php deleted file mode 100644 index 8d320207..00000000 --- a/Solar/Flash.php +++ /dev/null @@ -1,171 +0,0 @@ - - * - * @license http://opensource.org/licenses/bsd-license.php BSD - * - * @version $Id$ - * - */ - -/** - * - * Class for working with read-once flashes. - * - * Taken from ideas popularized by Ruby on Rails, a "flash" is a session - * value that propagates only until it is read, at which time it - * is removed from the session. This is useful for forwarding - * information and messages between page loads without using get-vars - * or cookies. - * - * @category Solar - * - * @package Solar_Access - * - */ -class Solar_Flash extends Solar_Base { - - /** - * - * User-defined configuration values. - * - * Keys are ... - * - * `class`: - * Flash messages are for this class. Default is 'Solar'. - * - * @var array - * - */ - protected $_Solar_Flash = array( - 'class' => 'Solar', - ); - - /** - * - * The class for flashes. - * - * @var array - * - */ - protected $_class = 'Solar'; - - /** - * - * Convenience reference to $_SESSION['Solar_Flash'][$this->_class]. - * - * @var array - * - */ - public $list; - - /** - * - * Constructor. - * - * @param array $config User-defined configuration values. - * - */ - public function __construct($config = null) - { - parent::__construct($config); - - if (session_id() === '') { - session_start(); - } - - $this->_class = trim($this->_config['class']); - if ($this->_class == '') { - $this->_class = 'Solar'; - } - - if (! isset($_SESSION['Solar_Flash'][$this->_class])) { - $_SESSION['Solar_Flash'][$this->_class] = array(); - } - - $this->list =& $_SESSION['Solar_Flash'][$this->_class]; - } - - /** - * - * Sets a "read-once" session value for a class and key. - * - * @param string $key The specific type of information for the class. - * - * @param mixed $val The value for the key; previous values will - * be overwritten. - * - * @return void - * - */ - public function set($key, $val) - { - $this->list[$key] = $val; - } - - /** - * - * Appends a "read-once" session value to a class and key. - * - * @param string $key The specific type of information for the class. - * - * @param mixed $val The flash value to add to the key; this will - * result in the flash becoming an array. - * - * @return void - * - */ - public function add($key, $val) - { - if (! isset($this->list[$key])) { - $this->list[$key] = array(); - } - - if (! is_array($this->list[$key])) { - settype($this->list[$key], 'array'); - } - - $this->list[$key][] = $val; - } - - /** - * - * Retrieves a "read-once" session value, thereby removing the value. - * - * @param string $key The specific type of information for the class. - * - * @param mixed $val If the class and key do not exist, return - * this value. Default null. - * - * @return mixed The "read-once" value. - * - */ - public function get($key, $val = null) - { - if (isset($this->list[$key])) { - $val = $this->list[$key]; - unset($this->list[$key]); - } - return $val; - } - - /** - * - * Resets (clears) all flash keys and values. - * - * @return void - * - */ - public function reset() - { - $this->list = array(); - } -} -?> \ No newline at end of file diff --git a/Solar/Form.php b/Solar/Form.php index 67ba290b..1c9efc0e 100755 --- a/Solar/Form.php +++ b/Solar/Form.php @@ -34,17 +34,17 @@ class Solar_Form extends Solar_Base { * * Keys are ... * - * `attribs`: - * An array of

    tag attributes; used for hinting + * `attribs` + * : An array of tag attributes; used for hinting * the view on how to present the form. Defaults are 'method="post"', - * 'action=$_SERVER["REQUEST_URI"]', and 'enctype="multipart/form-data"'. + * 'action="REQUEST_URI"', and 'enctype="multipart/form-data"'. * - * `success`: - * The overall "success" message when validating form + * `success` + * : The overall "success" message when validating form * input. Default is Solar locale key SUCCESS_FORM. * - * `failure`: - * The overall "failure" message when validating form + * `failure` + * : The overall "failure" message when validating form * input. Default is Solar locale key FAILURE_FORM. * * @var array @@ -141,8 +141,8 @@ class Solar_Form extends Solar_Base { * * Array of submitted values. * - * Populated on the first call to Solar_Form::_populate(), which itself uses - * [[Solar::get()]] or [[Solar::post()]], depending on the value of + * Populated on the first call to [[_populate()]], which itself uses + * [[Solar_Request::get()]] or [[Solar_Request::post()]], depending on the value of * $this->attribs['method']. * * @var array @@ -158,44 +158,44 @@ class Solar_Form extends Solar_Base { * * Keys are ... * - * `name`: - * (string) The name attribute. + * `name` + * : (string) The name attribute. * - * `type`: - * (string) The input or type attribute ('text', 'select', etc). + * `type` + * : (string) The input or type attribute ('text', 'select', etc). * - * `label`: - * (string) A short label for the element. + * `label` + * : (string) A short label for the element. * - * `value`: - * (string) The default or selected value(s) for the element. + * `value` + * : (string) The default or selected value(s) for the element. * - * `descr`: - * (string) A longer description of the element, e.g. a tooltip + * `descr` + * : (string) A longer description of the element, e.g. a tooltip * or help text. * - * `status`: - * (bool) Whether or not the particular elements has + * `status` + * : (bool) Whether or not the particular elements has * passed or failed validation (true or false), or null if there * has been no attempt at validation. * - * `require`: - * (bool) Whether or not the element is required. + * `require` + * : (bool) Whether or not the element is required. * - * `disable`: - * (bool) If disabled, the element is read-only (but is still + * `disable` + * : (bool) If disabled, the element is read-only (but is still * submitted with other elements). * - * `options`: - * (array) The list of allowed values as options for this element + * `options` + * : (array) The list of allowed values as options for this element * as an associative array in the form (value => label). * - * `attribs`: - * (array) Additional XHTML attributes for the element in the + * `attribs` + * : (array) Additional XHTML attributes for the element in the * form (attribute => value). * - * `feedback`: - * (array) An array of feedback messages for this element, + * `feedback` + * : (array) An array of feedback messages for this element, * generally based on validation of previous user input. * * @var array @@ -233,6 +233,15 @@ class Solar_Form extends Solar_Base { */ protected $_obj_valid; + /** + * + * Request environment object. + * + * @var Solar_Request + * + */ + protected $_request; + /** * * Constructor. @@ -242,8 +251,11 @@ class Solar_Form extends Solar_Base { */ public function __construct($config = null) { + // request environment + $this->_request = Solar::factory('Solar_Request'); + // programmatic defaults - $this->_Solar_Form['attribs']['action'] = Solar::server('REQUEST_URI'); + $this->_Solar_Form['attribs']['action'] = $this->_request->server('REQUEST_URI'); $this->_Solar_Form['success'] = $this->locale('SUCCESS_FORM'); $this->_Solar_Form['failure'] = $this->locale('FAILURE_FORM'); @@ -477,8 +489,9 @@ public function addFeedback($list, $array = null) * Populates form elements with specified values. * * @param array $submit The source data array for populating form - * values as array(name => value); if null, will populate from $_POST - * or $_GET as determined from the Solar_Form::$attribs['method'] value. + * values as array(name => value); if null, will populate from POST + * or GET vars as determined from the Solar_Form::$attribs['method'] + * value. * * @return void * @@ -496,10 +509,10 @@ public function populate($submit = null) // from an object $this->_submitted = (array) $submit; } else { - // from $_GET or $_POST, per the form method. + // from GET or POST, per the form method. $method = strtolower($this->attribs['method']); if ($method == 'get' || $method == 'post') { - $this->_submitted = Solar::$method(); + $this->_submitted = $this->_request->$method(); } } @@ -517,8 +530,8 @@ public function populate($submit = null) * passed in $submit. * * @param array $submit The source data array for populating form - * values as array(name => info); if null, will populate from $_POST - * or $_GET as determined from the 'method' attribute. + * values as array(name => info); if null, will populate from POST + * or GET vars as determined from the 'method' attribute. * * @return bool True if all elements are valid, false if not. * @@ -693,11 +706,9 @@ public function getStatus() * 'elements' which contain, respectively, values for $this->attribs * and $this->setElements(). * - * Example use: - * - * - * $form = Solar::factory('Solar_Form'); - * $form->load('Solar_Form_Load_Xml', '/path/to/form.xml'); + * {{code: php + * $form = Solar::factory('Solar_Form'); + * $form->load('Solar_Form_Load_Xml', '/path/to/form.xml'); * }} * * @param string|object $obj If a string, it is treated as a class diff --git a/Solar/Json.php b/Solar/Json.php index aebce97a..80c9996e 100644 --- a/Solar/Json.php +++ b/Solar/Json.php @@ -57,20 +57,20 @@ class Solar_Json extends Solar_Base { * * Keys are ... * - * `bypass_ext`: - * (bool) Flag to instruct Solar_Json to bypass - * native json extension, if installed. - * - * `bypass_mb`: - * (bool) Flag to instruct Solar_Json to bypass - * native mb_convert_encoding() function, if - * installed. - * - * `noerror`: - * (bool) Flag to instruct Solar_Json to return null - * for values it cannot encode rather than throwing - * an exceptions (PHP-only encoding) or PHP warnings - * (native json_encode() function). + * `bypass_ext` + * : (bool) Flag to instruct Solar_Json to bypass + * native json extension, if installed. + * + * `bypass_mb` + * : (bool) Flag to instruct Solar_Json to bypass + * native mb_convert_encoding() function, if + * installed. + * + * `noerror` + * : (bool) Flag to instruct Solar_Json to return null + * for values it cannot encode rather than throwing + * an exceptions (PHP-only encoding) or PHP warnings + * (native json_encode() function). * */ protected $_Solar_Json = array( diff --git a/Solar/Log.php b/Solar/Log.php index 95b4a301..fc35ba41 100644 --- a/Solar/Log.php +++ b/Solar/Log.php @@ -19,8 +19,6 @@ * * Facade for a log adapter. * - * Example: - * * {{code: php * // example setup of a single adapter * $config = array( @@ -49,17 +47,19 @@ class Solar_Log extends Solar_Base { * * Keys are ... * - * `adapter`: - * (string) The adapter class to use, e.g. 'Solar_Log_Adapter_File'. - * Default is 'Solar_Log_Adapter_None'. + * `adapter` + * : (string) The adapter class to use, e.g. 'Solar_Log_Adapter_File'. + * Default is 'Solar_Log_Adapter_None'. * - * All other keys are passed to the adapter class as its $config values. + * `config` + * : (array) Configuration to pass to the adapter. * * @var array * */ protected $_Solar_Log = array( 'adapter' => 'Solar_Log_Adapter_None', + 'config' => null, ); /** @@ -81,12 +81,9 @@ class Solar_Log extends Solar_Base { public function __construct($config = null) { parent::__construct($config); - $adapter_config = $this->_config; - unset($adapter_config['adapter']); - $this->_adapter = Solar::dependency( - $this->_config['adapter'], - $adapter_config - ); + $class = $this->_config['adapter']; + $config = empty($this->_config['config']) ? null : $this->_config['config']; + $this->_adapter = Solar::factory($class, $config); } /** @@ -94,7 +91,7 @@ public function __construct($config = null) * Magic shorthand for saving an event using a method name. * * {{code: php - * // these are equivalent: + * // these are equivalent ... * $log->save('info', 'informational message'); * $log->info('informational message'); * }} diff --git a/Solar/Log/Adapter.php b/Solar/Log/Adapter.php index 80d234ad..53b94881 100644 --- a/Solar/Log/Adapter.php +++ b/Solar/Log/Adapter.php @@ -32,10 +32,10 @@ abstract class Solar_Log_Adapter extends Solar_Base { * * Keys are ... * - * `events`: - * (string|array) The event types this instance - * should recognize; a comma-separated string of events, or - * a sequential array. Default is all events ('*'). + * `events` + * : (string|array) The event types this instance + * should recognize; a comma-separated string of events, or + * a sequential array. Default is all events ('*'). * * @var array * diff --git a/Solar/Log/Adapter/Echo.php b/Solar/Log/Adapter/Echo.php index 102dcba8..aa634e66 100644 --- a/Solar/Log/Adapter/Echo.php +++ b/Solar/Log/Adapter/Echo.php @@ -37,20 +37,20 @@ class Solar_Log_Adapter_Echo extends Solar_Log_Adapter { * * Keys are ... * - * `events`: - * (string|array) The event types this instance - * should recognize; a comma-separated string of events, or - * a sequential array. Default is all events ('*'). - * - * `format`: - * (string) The line format for each saved event. - * Use '%t' for the timestamp, '%e' for the class name, '%e' for - * the event type, '%m' for the event description, and '%%' for a - * literal percent. Default is '%t %c %e %m'. - * - * `output`: - * (string) Output mode. Set to 'html' for HTML; - * or 'text' for plain text. Default autodetects by SAPI version. + * `events` + * : (string|array) The event types this instance + * should recognize; a comma-separated string of events, or + * a sequential array. Default is all events ('*'). + * + * `format` + * : (string) The line format for each saved event. + * Use '%t' for the timestamp, '%e' for the class name, '%e' for + * the event type, '%m' for the event description, and '%%' for a + * literal percent. Default is '%t %c %e %m'. + * + * `output` + * : (string) Output mode. Set to 'html' for HTML; + * or 'text' for plain text. Default autodetects by SAPI version. * * @var array * diff --git a/Solar/Log/Adapter/File.php b/Solar/Log/Adapter/File.php index 5546a158..6fb133b6 100644 --- a/Solar/Log/Adapter/File.php +++ b/Solar/Log/Adapter/File.php @@ -37,20 +37,20 @@ class Solar_Log_Adapter_File extends Solar_Log_Adapter { * * Keys are ... * - * `events`: - * (string|array) The event types this instance - * should recognize; a comma-separated string of events, or - * a sequential array. Default is all events ('*'). - * - * `file`: - * (string) The file where events should be logged; - * e.g. '/www/username/logs/solar.log'. - * - * `format`: - * (string) The line format for each saved event. - * Use '%t' for the timestamp, '%e' for the class name, '%e' for - * the event type, '%m' for the event description, and '%%' for a - * literal percent. Default is '%t %c %e %m'. + * `events` + * : (string|array) The event types this instance + * should recognize; a comma-separated string of events, or + * a sequential array. Default is all events ('*'). + * + * `file` + * : (string) The file where events should be logged; + * e.g. '/www/username/logs/solar.log'. + * + * `format` + * : (string) The line format for each saved event. + * Use '%t' for the timestamp, '%e' for the class name, '%e' for + * the event type, '%m' for the event description, and '%%' for a + * literal percent. Default is '%t %c %e %m'. * * @var array * diff --git a/Solar/Log/Adapter/Multi.php b/Solar/Log/Adapter/Multi.php index 258b4e96..6d977bfa 100644 --- a/Solar/Log/Adapter/Multi.php +++ b/Solar/Log/Adapter/Multi.php @@ -27,7 +27,7 @@ * {{code: php * // config for a multiple log * $config = array( - * 'adapter' => 'Solar_Log_Adapter_Multiple', // could also be a dependency object? + * 'adapter' => 'Solar_Log_Adapter_Multi', // could also be a dependency object? * 'adapters' => array( * array( * 'adapter' => 'Solar_Log_Adapter_File', @@ -67,18 +67,23 @@ class Solar_Log_Adapter_Multi extends Solar_Log_Adapter { * * Keys are ... * - * `adapters`: - * (array) An array of arrays, where each sub-array - * is a separate adapter configuration. + * `adapters` + * : (array) An array of arrays, where each sub-array + * is a separate adapter configuration. * * @var array * + * @todo make the standard events config key the default for + * all sub-adapters. + * */ protected $_Solar_Log_Adapter_Multi = array( 'adapters' => array( array( 'adapter' => 'Solar_Log_Adapter_None', - 'events' => '*', + 'config' => array( + 'events' => '*', + ), ), ), ); @@ -102,10 +107,21 @@ class Solar_Log_Adapter_Multi extends Solar_Log_Adapter { public function __construct($config = null) { parent::__construct($config); - foreach ($this->_config['adapters'] as $adapter_config) { - $class = $adapter_config['adapter']; - unset($adapter_config['adapter']); - $this->_adapters[] = Solar::dependency($class, $adapter_config); + $events = $this->_config['events']; + + // build each sub-adapter + foreach ($this->_config['adapters'] as $val) { + + // basic config + $class = $val['adapter']; + $config = empty($val['config']) ? null : $val['config']; + + // use default events? + if (empty($config['events'])) { + $config['events'] = $events; + } + + $this->_adapters[] = Solar::factory($class, $config); } } diff --git a/Solar/Markdown.php b/Solar/Markdown.php index 18ff1ebe..069ae0b4 100644 --- a/Solar/Markdown.php +++ b/Solar/Markdown.php @@ -61,17 +61,17 @@ class Solar_Markdown extends Solar_Base { * * Keys are ... * - * `tab_width`: - * (int) Number of spaces per tab. Default 4. + * `tab_width` + * : (int) Number of spaces per tab. Default 4. * - * `tidy`: - * (bool|array) If false, do not use Tidy to post-process + * `tidy` + * : (bool|array) If false, do not use Tidy to post-process * the transformed output. If true or an array, is a set of * config options to pass to Tidy when rendering output. * See also . Default true. * - * `plugins`: - * (array) An array of plugins for the parser to use, in + * `plugins` + * : (array) An array of plugins for the parser to use, in * order. * * @var array @@ -718,7 +718,7 @@ public function _encode($text, $in_tag = false) * * [MTRegex]: http://www.bradchoate.com/past/mtregex.php * - * From the original notes: + * From the original notes ... * * > Returns an array of the tokens comprising the input string. * > Each token is either a tag (possibly with nested, tags diff --git a/Solar/Markdown/Extra/DefList.php b/Solar/Markdown/Extra/DefList.php index 9b3b4603..6e03bbad 100644 --- a/Solar/Markdown/Extra/DefList.php +++ b/Solar/Markdown/Extra/DefList.php @@ -26,7 +26,7 @@ * * Block class to form definition lists. * - * Syntax is: + * Syntax is ... * * term * : definition diff --git a/Solar/Markdown/Extra/Header.php b/Solar/Markdown/Extra/Header.php index 2b13f98a..8d60e9b4 100644 --- a/Solar/Markdown/Extra/Header.php +++ b/Solar/Markdown/Extra/Header.php @@ -39,7 +39,7 @@ * Header 2 {#id-word2} * ------------------- * - * ... would become: + * ... would become ... * *

    Header 1

    * diff --git a/Solar/Markdown/Plugin.php b/Solar/Markdown/Plugin.php index 08ce824c..3b602f40 100644 --- a/Solar/Markdown/Plugin.php +++ b/Solar/Markdown/Plugin.php @@ -32,8 +32,8 @@ abstract class Solar_Markdown_Plugin extends Solar_Base { * * Keys are ... * - * `markdown`: - * (Solar_Markdown) The "parent" Markdown object. + * `markdown` + * : (Solar_Markdown) The "parent" Markdown object. * * @var array * diff --git a/Solar/Markdown/Plugin/CodeSpan.php b/Solar/Markdown/Plugin/CodeSpan.php index 63a87880..96460421 100644 --- a/Solar/Markdown/Plugin/CodeSpan.php +++ b/Solar/Markdown/Plugin/CodeSpan.php @@ -31,11 +31,11 @@ * Backtick quotes are used for `` spans. * * You can use multiple backticks as the delimiters if you want to - * include literal backticks in the code span. So, this input: + * include literal backticks in the code span. So, this input ... * * Just type ``foo `bar` baz`` at the prompt. * - * Will translate to: + * ... will translate to ... * *

    Just type foo `bar` baz at the prompt.

    * @@ -43,13 +43,13 @@ * can use as delimters. If you need three consecutive backticks * in your code, use four for delimiters, etc. * - * You can use spaces to get literal backticks at the edges: + * You can use spaces to get literal backticks at the edges ... * - * ... type `` `bar` `` ... + * type `` `bar` `` * - * Turns into: + * ... which turns into ... * - * ... type `bar` ... + * type `bar` * * @category Solar * diff --git a/Solar/Markdown/Plugin/Header.php b/Solar/Markdown/Plugin/Header.php index 50cadefc..30cf66cf 100644 --- a/Solar/Markdown/Plugin/Header.php +++ b/Solar/Markdown/Plugin/Header.php @@ -36,7 +36,7 @@ * Header 2 * -------- * - * ... would become: + * ... would become ... * *

    Header 1

    * @@ -51,7 +51,7 @@ * * ##### Header 5 * - * ... would become: + * ... would become ... * *

    Header 1

    * diff --git a/Solar/Markdown/Plugin/Link.php b/Solar/Markdown/Plugin/Link.php index 3cfca888..fbabb5e0 100644 --- a/Solar/Markdown/Plugin/Link.php +++ b/Solar/Markdown/Plugin/Link.php @@ -30,13 +30,13 @@ * * You can link to another page using `[display text](http://example.com)`. * - * Alternatively, you can use defined links: + * Alternatively, you can use defined links ... * * Show [this link][id]. * * [id]: http://example.com * - * And a shorthand for defined links: + * And a shorthand for defined links ... * * Show the [example][]. * diff --git a/Solar/Markdown/Plugin/StripLinkDefs.php b/Solar/Markdown/Plugin/StripLinkDefs.php index bbea980a..88701094 100644 --- a/Solar/Markdown/Plugin/StripLinkDefs.php +++ b/Solar/Markdown/Plugin/StripLinkDefs.php @@ -30,7 +30,7 @@ * * This is in support of the Link and Image plugins. * - * A named link reference looks like this: + * A named link reference looks like this ... * * [name]: http://example.com "Optional Title" * diff --git a/Solar/Markdown/Plugin/Uri.php b/Solar/Markdown/Plugin/Uri.php index d754df6f..00c77348 100644 --- a/Solar/Markdown/Plugin/Uri.php +++ b/Solar/Markdown/Plugin/Uri.php @@ -28,11 +28,11 @@ * * Span plugin to create anchors from inline URIs. * - * Syntax looks like this: + * Syntax looks like this ... * * * - * That will create the following XHTML: + * That will create the following XHTML ... * * http://example.com * @@ -132,7 +132,7 @@ protected function _parse($matches) * * Support callback for parsing email addresses. * - * From the original notes: + * From the original notes ... * * > Input: an email address, e.g. "foo@example.com" * > diff --git a/Solar/Markdown/Wiki/Header.php b/Solar/Markdown/Wiki/Header.php index 0e7cb333..a1ab4190 100644 --- a/Solar/Markdown/Wiki/Header.php +++ b/Solar/Markdown/Wiki/Header.php @@ -44,7 +44,7 @@ * Sub Section * ----------- * - * ... would become: + * ... would become ... * *

    Title

    * @@ -118,12 +118,44 @@ public function parse($text) $text ); + // atx 1 through 4 + $text = preg_replace_callback( + "{ + ^(\\#{1,4}) # $1 = string of #'s + [ \\t]* + (.+?) # $2 = Header text + [ \\t]* + \\#* # optional closing #'s (not counted) + \\n+ + }xm", + array($this, '_parseAtx'), + $text + ); + + // done return $text; } /** * - * Support callback for H2 headers. + * Support callback for ATX headers. + * + * Only supports 1-4 leading hash marks. + * + * @param array $matches Matches from preg_replace_callback(). + * + * @return string The replacement text. + * + */ + protected function _parseAtx($matches) + { + $tag = 'h' . strlen($matches[1]); + return $this->_header($tag, $matches[2]); + } + + /** + * + * Support callback for H1 headers. * * @param array $matches Matches from preg_replace_callback(). * @@ -137,7 +169,7 @@ protected function _parseTitle($matches) /** * - * Support callback for H3 headers. + * Support callback for H2 headers. * * @param array $matches Matches from preg_replace_callback(). * @@ -151,7 +183,7 @@ protected function _parseSuperSection($matches) /** * - * Support callback for H4 headers. + * Support callback for H3 headers. * * @param array $matches Matches from preg_replace_callback(). * @@ -165,7 +197,7 @@ protected function _parseSection($matches) /** * - * Support callback for H5 headers. + * Support callback for H4 headers. * * @param array $matches Matches from preg_replace_callback(). * diff --git a/Solar/Markdown/Wiki/Link.php b/Solar/Markdown/Wiki/Link.php index 266ed781..22eae8b8 100644 --- a/Solar/Markdown/Wiki/Link.php +++ b/Solar/Markdown/Wiki/Link.php @@ -24,7 +24,7 @@ * * Replaces wiki and interwiki links in source text with XHTML anchors. * - * Wiki links are in this format: + * Wiki links are in this format ... * * [[wiki page]] * [[wiki page #anchor]] @@ -39,7 +39,7 @@ * Page links are replaced with encoded placeholders. At cleanup() * time, the placeholders are transformed into XHTML anchors. * - * This plugin also supports Interwiki links, in this format: + * This plugin also supports Interwiki links, in this format ... * * [[site::page]] * [[site::page #anchor]] @@ -94,22 +94,22 @@ class Solar_Markdown_Wiki_Link extends Solar_Markdown_Plugin { * * Array of information for each link found in the source text. * - * Each element is an array with these keys: + * Each element is an array with these keys ... * - * `norm`: - * The normalized form of the page name. + * `norm` + * : The normalized form of the page name. * - * `page`: - * The page name as entered in the source text. + * `page` + * : The page name as entered in the source text. * - * `frag`: - * A fragment anchor for the target page (e.g., "#example"). + * `frag` + * : A fragment anchor for the target page (e.g., "#example"). * - * `text`: - * The text to display in place of the page name. + * `text` + * : The text to display in place of the page name. * - * `atch`: - * Attached suffix text to go on the end of the displayed text. + * `atch` + * : Attached suffix text to go on the end of the displayed text. * * @var array * diff --git a/Solar/Markdown/Wiki/MethodSynopsis.php b/Solar/Markdown/Wiki/MethodSynopsis.php index 5b19ecfa..12ac8a6f 100644 --- a/Solar/Markdown/Wiki/MethodSynopsis.php +++ b/Solar/Markdown/Wiki/MethodSynopsis.php @@ -24,8 +24,6 @@ * * Block plugin to for method synopsis markup. * - * Example: - * * {{method: methodName * @access level * @param type diff --git a/Solar/Model/Nodes.php b/Solar/Model/Nodes.php index 5ce712e1..84cd351e 100755 --- a/Solar/Model/Nodes.php +++ b/Solar/Model/Nodes.php @@ -101,7 +101,6 @@ protected function _setup() 'type' => 'char', 'size' => 15, 'require' => true, - 'default' => Solar::server('REMOTE_ADDR'), 'valid' => 'ipv4', ); diff --git a/Solar/Model/Tags.php b/Solar/Model/Tags.php index 75f99fa6..aa632a8e 100755 --- a/Solar/Model/Tags.php +++ b/Solar/Model/Tags.php @@ -24,9 +24,9 @@ * * Tags on nodes. * - * See notes on tag schemas here: + * See these notes on [tag schemas][]. * - * http://www.pui.ch/phred/archives/2005/04/tags-database-schemas.html + * [tag schemas]: http://www.pui.ch/phred/archives/2005/04/tags-database-schemas.html * * @category Solar * diff --git a/Solar/Path/Stack.php b/Solar/Path/Stack.php index 4b3e9eea..6873f3cf 100644 --- a/Solar/Path/Stack.php +++ b/Solar/Path/Stack.php @@ -55,8 +55,6 @@ public function get() * * Adds one or more directories to the stack. * - * For example: - * * {{code: php * $stack = Solar::factory('Solar_Path_Stack'); * $stack->add(array('path/1', 'path/2', 'path/3')); @@ -110,8 +108,6 @@ public function add($path) * * Clears the stack and adds one or more directories. * - * For example: - * * {{code: php * $stack = Solar::factory('Solar_Path_Stack'); * $stack->add('path/1'); @@ -144,8 +140,6 @@ public function set($path) * * Relative paths are honored as part of the include_path. * - * For example: - * * {{code: php * $stack = Solar::factory('Solar_Path_Stack'); * $stack->add('path/1'); @@ -184,8 +178,6 @@ public function find($file) * against directory traversal attacks. It only works with absolute * paths; relative paths will fail. * - * For example: - * * {{code: php * $stack = Solar::factory('Solar_Path_Stack'); * $stack->add('/path/1'); diff --git a/Solar/Request.php b/Solar/Request.php new file mode 100644 index 00000000..a8a7b919 --- /dev/null +++ b/Solar/Request.php @@ -0,0 +1,498 @@ + + * + * @author Clay Loveless + * + * @license http://opensource.org/licenses/bsd-license.php BSD + * + * @version $Id$ + * + */ + +/** + * + * Class for gathering details about the request environment. + * + * This is effectively a singleton class; all request variables are + * static, and are shared across all Solar_Request instances. + * + * Which keys can be tampered with for XSS insertions? + * + * For SERVER ... + * + * - All HTTP_* keys + * + * - QUERY_STRING + * + * - REMOTE_HOST + * + * - REQUEST_URI + * + * - SERVER_NAME + * + * - PHP_SELF + * + * - PATH_TRANSLATED + * + * - PATH_INFO + * + * - argv + * + * - PHP_AUTH_USER + * + * - PHP_AUTH_PW + * + * - REMOTE_HOST is usually the result of a DNS lookup made by + * the webserver, so it's much harder to insert XSS. + * + * For FILES, the 'name' and 'type' keys for each file entry. + * + * @category Solar + * + * @package Solar_Request + * + */ +class Solar_Request extends Solar_Base { + + /** + * + * User-defined configuration values. + * + * Keys are ... + * + * `reload` + * : (bool) Forcibly reload static properties at instantiation time. + * Default false, which means properties are loaded only on first + * instantiation. + * + */ + protected $_Solar_Request = array( + 'reload' => false, + ); + + /** + * + * Reference to static $_request['env'] values. + * + * @var array + * + */ + public $env; + + /** + * + * Reference to static $_request['get'] values. + * + * @var array + * + */ + public $get; + + /** + * + * Reference to static $_request['post'] values. + * + * @var array + * + */ + public $post; + + /** + * + * Reference to static $_request['cookie'] values. + * + * @var array + * + */ + public $cookie; + + /** + * + * Reference to static $_request['server'] values. + * + * @var array + * + */ + public $server; + + /** + * + * Reference to static $_request['files'] values. + * + * @var array + * + */ + public $files; + + /** + * + * Reference to static $_request['http'] values. + * + * These are HTTP headers pulled from from the $_request['server'] + * array. + * + * Header keys are normalized and lower-cased; keys and values are + * filtered for control characters. + * + * @var array + * + */ + public $http; + + /** + * + * Processed request values. + * + * Note that these are static; they are the same for every instance + * of Solar_Request. + * + * @var array + * + */ + static protected $_request = array( + 'env' => array(), + 'get' => array(), + 'post' => array(), + 'cookie' => array(), + 'server' => array(), + 'files' => array(), + 'http' => array(), + ); + + /** + * + * Have values been loaded already? + * + * @var bool + * + */ + static protected $_loaded = false; + + /** + * + * Constructor. + * + * @param array $config User-defined configuration values. + * + */ + public function __construct($config = null) + { + parent::__construct($config); + + // map public properties to static storage + $this->env =& self::$_request['env']; + $this->get =& self::$_request['get']; + $this->post =& self::$_request['post']; + $this->cookie =& self::$_request['cookie']; + $this->server =& self::$_request['server']; + $this->files =& self::$_request['files']; + $this->http =& self::$_request['http']; + + // load values + $this->load($this->_config['reload']); + } + + /** + * + * Retrieves a value by key from the [[$get]] property, or an alternate + * default value if that key does not exist. + * + * @param string $key The $get key to retrieve the value of. + * + * @param string $alt The value to return if the key does not exist. + * + * @return mixed The value of $get[$key], or the alternate default + * value. + * + */ + public function get($key = null, $alt = null) + { + return $this->_getValue('get', $key, $alt); + } + + /** + * + * Retrieves a value by key from the [[$post]] property, or an alternate + * default value if that key does not exist. + * + * @param string $key The $post key to retrieve the value of. + * + * @param string $alt The value to return if the key does not exist. + * + * @return mixed The value of $post[$key], or the alternate default + * value. + * + */ + public function post($key = null, $alt = null) + { + return $this->_getValue('post', $key, $alt); + } + + /** + * + * Retrieves a value by key from the [[$cookie]] property, or an alternate + * default value if that key does not exist. + * + * @param string $key The $cookie key to retrieve the value of. + * + * @param string $alt The value to return if the key does not exist. + * + * @return mixed The value of $cookie[$key], or the alternate default + * value. + * + */ + public function cookie($key = null, $alt = null) + { + return $this->_getValue('cookie', $key, $alt); + } + + /** + * + * Retrieves a value by key from the [[$env]] property, or an alternate + * default value if that key does not exist. + * + * @param string $key The $env key to retrieve the value of. + * + * @param string $alt The value to return if the key does not exist. + * + * @return mixed The value of $env[$key], or the alternate default + * value. + * + */ + public function env($key = null, $alt = null) + { + return $this->_getValue('env', $key, $alt); + } + + /** + * + * Retrieves a value by key from the [[$server]] property, or an alternate + * default value if that key does not exist. + * + * @param string $key The $server key to retrieve the value of. + * + * @param string $alt The value to return if the key does not exist. + * + * @return mixed The value of $server[$key], or the alternate default + * value. + * + */ + public function server($key = null, $alt = null) + { + return $this->_getValue('server', $key, $alt); + } + + /** + * + * Retrieves a value by key from the [[$files]] property, or an alternate + * default value if that key does not exist. + * + * @param string $key The $files key to retrieve the value of. + * + * @param string $alt The value to return if the key does not exist. + * + * @return mixed The value of $files[$key], or the alternate default + * value. + * + */ + public function files($key = null, $alt = null) + { + return $this->_getValue('files', $key, $alt); + } + + /** + * + * Retrieves a value by key from the [[$http]] property, or an alternate + * default value if that key does not exist. + * + * @param string $key The $http key to retrieve the value of. + * + * @param string $alt The value to return if the key does not exist. + * + * @return mixed The value of $http[$key], or the alternate default + * value. + * + */ + public function http($key = null, $alt = null) + { + return $this->_getValue('http', strtolower($key), $alt); + } + + /** + * + * Is this a 'GET' request? + * + * @return bool + * + */ + public function isGet() + { + return $this->server('REQUEST_METHOD') == 'GET'; + } + + /** + * + * Is this a 'POST' request? + * + * @return bool + * + */ + public function isPost() + { + return $this->server('REQUEST_METHOD') == 'POST'; + } + + /** + * + * Is this a 'PUT' request? + * + * @return bool + * + */ + public function isPut() + { + return $this->server('REQUEST_METHOD') == 'PUT'; + } + + /** + * + * Is this a 'DELETE' request? + * + * @return bool + * + */ + public function isDelete() + { + return $this->server('REQUEST_METHOD') == 'DELETE'; + } + + /** + * + * Is this an 'XML' request? + * + * Checks if the `X-Requested-With` HTTP header is `XMLHttpRequest`. + * Generally used in addition to the [[isPost()]], [[isGet()]], etc. + * methods to identify Ajax-style HTTP requests. + * + * @return bool + * + */ + public function isXml() + { + return strtolower($this->http('X-Requested-With')) == 'xmlhttprequest'; + } + + /** + * + * Loads properties from the superglobal arrays. + * + * Normalizes HTTP header keys, dispels magic quotes. + * + * @return void + * + */ + public function load($reload = false) + { + if (self::$_loaded && ! $reload) { + // already loaded and not forcing a reload + return; + } + + // load the "real" request vars + $vars = array('env', 'get', 'post', 'cookie', 'server', 'files'); + foreach ($vars as $key) { + $var = '_' . strtoupper($key); + if (isset($GLOBALS[$var])) { + self::$_request[$key] = $GLOBALS[$var]; + } else { + self::$_request[$key] = array(); + } + } + + // dispel magic quotes if they are enabled. + // http://talks.php.net/show/php-best-practices/26 + if (get_magic_quotes_gpc()) { + $in = array(&$_GET, &$_POST, &$_COOKIE); + while (list($k, $v) = each($in)) { + foreach ($v as $key => $val) { + if (! is_array($val)) { + $in[$k][$key] = stripslashes($val); + continue; + } + $in[] =& $in[$k][$key]; + } + } + unset($in); + } + + // load the "fake" http request var + self::$_request['http'] = array(); + foreach (self::$_request['server'] as $key => $val) { + + // only retain HTTP headers + if (substr($key, 0, 4) == 'HTTP') { + + // normalize the header key to lower-case + $nicekey = strtolower( + str_replace('_', '-', substr($key, 5)) + ); + + // strip control characters from keys and values + $nicekey = preg_replace('/[\x00-\x1F]/', '', $nicekey); + self::$_request['http'][$nicekey] = preg_replace('/[\x00-\x1F]/', '', $val); + + // no control characters wanted in self::$_request['server'] for these + self::$_request['server'][$key] = self::$_request['http'][$nicekey]; + + // disallow external setting of X-JSON headers. + if ($nicekey == 'x-json') { + unset(self::$_request['http'][$nicekey]); + unset(self::$_request['server'][$key]); + } + } + } + + // done! + self::$_loaded = true; + } + + /** + * + * Common method to get a static request value and return it. + * + * @param string $var The request variable to fetch from: get, post, + * etc. + * + * @param string $key The array key, if any, to get the value of. + * + * @param string $alt The alternative default value to return if the + * requested key does not exist. + * + * @return mixed The requested value, or the alternative default + * value. + * + */ + protected function _getValue($var, $key, $alt) + { + // get the whole property, or just one key? + if ($key === null) { + // no key selected, return the whole array + return self::$_request[$var]; + } elseif (array_key_exists($key, self::$_request[$var])) { + // found the requested key + return self::$_request[$var][$key]; + } else { + // requested key does not exist + return $alt; + } + } +} +?> \ No newline at end of file diff --git a/Solar/Role.php b/Solar/Role.php index 7c1665d9..0e44c520 100755 --- a/Solar/Role.php +++ b/Solar/Role.php @@ -32,14 +32,14 @@ class Solar_Role extends Solar_Base { * * Keys are ... * - * `adapter`: - * (string) The adapter class to use. + * `adapter` + * : (string) The adapter class to use. * - * `config`: - * (array) Config options for constructing the adapter class. + * `config` + * : (array) Config options for constructing the adapter class. * - * `refresh`: - * (bool) Whether or not to refresh the roles on every load. + * `refresh` + * : (bool) Whether or not to refresh the roles on every load. * * @var array * @@ -70,7 +70,16 @@ class Solar_Role extends Solar_Base { /** * - * A convenient reference to $_SESSION['Solar_Role']. + * A class-segmented session-variable reference. + * + * @var Solar_Session + * + */ + protected $_session; + + /** + * + * A public reference to the session store. * * @var array * @@ -89,6 +98,12 @@ public function __construct($config = null) // basic config option settings parent::__construct($config); + // get a session segment + $this->_session = Solar::factory( + 'Solar_Session', + array('class' => get_class($this)) + ); + // instantiate a adapter object $this->_adapter = Solar::factory( $this->_config['adapter'], @@ -96,10 +111,7 @@ public function __construct($config = null) ); // make sure we have a session value and reference to it. - if (! isset($this->list)) { - $_SESSION['Solar_Role'] = array(); - $this->list =& $_SESSION['Solar_Role']; - } + $this->list =& $this->_session->store; } /** diff --git a/Solar/Role/Adapter/File.php b/Solar/Role/Adapter/File.php index 377b7c3a..ed5c0e2c 100755 --- a/Solar/Role/Adapter/File.php +++ b/Solar/Role/Adapter/File.php @@ -26,13 +26,11 @@ * * Adapter to fetch roles from a Unix-style groups file. * - * The file format is "group:user1,user2,user3\n". Example: + * The file format is "group:user1,user2,user3\n". * - * - * sysadmin:pmjones - * writer:pmjones,boshag,agtsmith - * editor:pmjones,agtsmith - * }} + * sysadmin:pmjones + * writer:pmjones,boshag,agtsmith + * editor:pmjones,agtsmith * * @category Solar * @@ -47,8 +45,8 @@ class Solar_Role_Adapter_File extends Solar_Role_Adapter { * * Keys are ... * - * `file`: - * (string) Where the roles file is located. + * `file` + * : (string) Where the roles file is located. * * @var array * diff --git a/Solar/Role/Adapter/Ldap.php b/Solar/Role/Adapter/Ldap.php index bbf811cb..51ac2fcf 100755 --- a/Solar/Role/Adapter/Ldap.php +++ b/Solar/Role/Adapter/Ldap.php @@ -37,24 +37,24 @@ class Solar_Role_Adapter_Ldap extends Solar_Role_Adapter { * * Keys are ... * - * `url`: - * (string) URL to the LDAP server. Takes the format of "ldaps://example.com:389". + * `url` + * : (string) URL to the LDAP server. Takes the format of "ldaps://example.com:389". * - * `basedn`: - * (string) The base DN for the LDAP search; example: "o=my company,c=us". + * `basedn` + * : (string) The base DN for the LDAP search; example: "o=my company,c=us". * - * `filter`: - * (string) An sprintf() filter string for the LDAP search; %s represents the username. + * `filter` + * : (string) An sprintf() filter string for the LDAP search; %s represents the username. * Example: "uid=%s". * - * `attrib`: - * (string) Use these attributes to find role names. + * `attrib` + * : (string) Use these attributes to find role names. * - * `binddn`: - * (string) Bind to the LDAP server as this distinguished name. + * `binddn` + * : (string) Bind to the LDAP server as this distinguished name. * - * `bindpw`: - * (string) Bind to the LDAP server as with this password. + * `bindpw` + * : (string) Bind to the LDAP server as with this password. * * @var array * diff --git a/Solar/Role/Adapter/Sql.php b/Solar/Role/Adapter/Sql.php index b5d1c418..f5b27e04 100755 --- a/Solar/Role/Adapter/Sql.php +++ b/Solar/Role/Adapter/Sql.php @@ -37,17 +37,17 @@ class Solar_Role_Adapter_Sql extends Solar_Role_Adapter { * * Keys are ... * - * `sql`: - * (dependency) A Solar_Sql dependency. + * `sql` + * : (dependency) A Solar_Sql dependency. * - * `table`: - * (string) The table where roles are stored. + * `table` + * : (string) The table where roles are stored. * - * `handle_col`: - * (string) The column for user handles. + * `handle_col` + * : (string) The column for user handles. * - * `role_col`: - * (string) The column for roles. + * `role_col` + * : (string) The column for roles. * * @var array * diff --git a/Solar/Session.php b/Solar/Session.php new file mode 100644 index 00000000..f8171968 --- /dev/null +++ b/Solar/Session.php @@ -0,0 +1,322 @@ + + * + * @license http://opensource.org/licenses/bsd-license.php BSD + * + * @version $Id: Flash.php 1707 2006-08-22 14:29:36Z pmjones $ + * + */ + +/** + * + * Class for working with the $_SESSION array, including read-once + * flashes. + * + * On instantiation, starts a session if one has not yet been started. + * + * Instantiate this once for each class that wants access to $_SESSION + * values. It automatically segments $_SESSION by class name, so be + * sure to use setClass() (or the 'class' config key) to identify the + * segment properly. + * + * A "flash" is a session value that propagates only until it is read, + * at which time it is removed from the session. Taken from ideas + * popularized by Ruby on Rails, this is useful for forwarding + * information and messages between page loads without using GET vars + * or cookies. + * + * @category Solar + * + * @package Solar_Session + * + */ +class Solar_Session extends Solar_Base { + + /** + * + * User-defined configuration values. + * + * Keys are ... + * + * `class` + * : Store values in this top-level key in $_SESSION. Default is + * 'Solar'. + * + * @var array + * + */ + protected $_Solar_Session = array( + 'class' => 'Solar', + ); + + /** + * + * The top-level $_SESSION class key for segmenting values. + * + * @var array + * + */ + protected $_class = 'Solar'; + + /** + * + * Array of read-once "flash" keys and values. + * + * Convenience reference to $_SESSION['Solar_Session']['flash'][$this->_class]. + * + * @var array + * + */ + public $flash; + + /** + * + * Array of "normal" session keys and values. + * + * Convenience reference to $_SESSION[$this->_class]. + * + * @var array + * + */ + public $store; + + /** + * + * Constructor. + * + * @param array $config User-defined configuration values. + * + */ + public function __construct($config = null) + { + parent::__construct($config); + + // start a session if one does not exist, but not if we're at + // the command line. + if (session_id() === '' && PHP_SAPI != 'cli') { + session_start(); + } + + $this->_class = trim($this->_config['class']); + if ($this->_class == '') { + $this->_class = 'Solar'; + } + + $this->setClass($this->_class); + } + + /** + * + * Sets the class segment for $_SESSION. + * + * @param string $class The class name to segment by. + * + * @return void + * + */ + public function setClass($class) + { + $this->_class = $class; + + // set up the value store + if (empty($_SESSION[$this->_class])) { + $_SESSION[$this->_class] = array(); + } + $this->store =& $_SESSION[$this->_class]; + + // set up the flash store + if (empty($_SESSION['Solar_Session']['flash'][$this->_class])) { + $_SESSION['Solar_Session']['flash'][$this->_class] = array(); + } + $this->flash =& $_SESSION['Solar_Session']['flash'][$this->_class]; + } + + /** + * + * Sets a normal value by key. + * + * @param string $key The data key. + * + * @param mixed $val The value for the key; previous values will + * be overwritten. + * + * @return void + * + */ + public function set($key, $val) + { + $this->store[$key] = $val; + } + + /** + * + * Appends a normal value to a key. + * + * @param string $key The data key. + * + * @param mixed $val The value to add to the key; this will + * result in the value becoming an array. + * + * @return void + * + */ + public function add($key, $val) + { + if (! isset($this->store[$key])) { + $this->store[$key] = array(); + } + + if (! is_array($this->store[$key])) { + settype($this->store[$key], 'array'); + } + + $this->store[$key][] = $val; + } + + /** + * + * Gets a normal value by key, or an alternative default value if + * the key does not exist. + * + * @param string $key The data key. + * + * @param mixed $val If key does not exist, returns this value + * instead. Default null. + * + * @return mixed The value. + * + */ + public function get($key, $val = null) + { + if (array_key_exists($key, $this->store)) { + $val = $this->store[$key]; + } + return $val; + } + + /** + * + * Resets (clears) all normal keys and values. + * + * @return void + * + */ + public function reset() + { + $this->store = array(); + } + + /** + * + * Sets a flash value by key. + * + * @param string $key The flash key. + * + * @param mixed $val The value for the key; previous values will + * be overwritten. + * + * @return void + * + */ + public function setFlash($key, $val) + { + $this->flash[$key] = $val; + } + + /** + * + * Appends a flash value to a key. + * + * @param string $key The flash key. + * + * @param mixed $val The flash value to add to the key; this will + * result in the flash becoming an array. + * + * @return void + * + */ + public function addFlash($key, $val) + { + if (! isset($this->flash[$key])) { + $this->flash[$key] = array(); + } + + if (! is_array($this->flash[$key])) { + settype($this->flash[$key], 'array'); + } + + $this->flash[$key][] = $val; + } + + /** + * + * Gets a flash value by key, thereby removing the value. + * + * @param string $key The flash key. + * + * @param mixed $val If key does not exist, returns this value + * instead. Default null. + * + * @return mixed The flash value. + * + */ + public function getFlash($key, $val = null) + { + if (array_key_exists($key, $this->flash)) { + $val = $this->flash[$key]; + unset($this->flash[$key]); + } + return $val; + } + + /** + * + * Resets (clears) all flash keys and values. + * + * @return void + * + */ + public function resetFlash() + { + $this->flash = array(); + } + + /** + * + * Resets both "normal" and "flash" values. + * + * @return void + * + */ + public function resetAll() + { + $this->reset(); + $this->resetFlash(); + } + + /** + * + * Regenerates the session ID and deletes the previous session store. + * + * Use this every time there is a privilege change. + * + * @return void + * + * @see [[php::session_regenerate_id()]] + * + */ + public function regenerateId() + { + session_regenerate_id(true); + } +} +?> \ No newline at end of file diff --git a/Solar/Sql.php b/Solar/Sql.php index 97c163fe..be27a368 100755 --- a/Solar/Sql.php +++ b/Solar/Sql.php @@ -32,12 +32,12 @@ class Solar_Sql extends Solar_Base { * * Keys are ... * - * `adapter`: - * (string) The adapter class to use, e.g. 'Solar_Sql_Adapter_Mysql'. + * `adapter` + * : (string) The adapter class to use, e.g. 'Solar_Sql_Adapter_Mysql'. * - * `config`: - * (array) Construction-time config keys to pass to the adapter - * to override Solar.config.php values. Default is null. + * `config` + * : (array) Construction-time config keys to pass to the adapter + * to override Solar.config.php values. Default is null. * * @var array * @@ -322,7 +322,7 @@ public function select($type, $spec, $data = array(), $class = null) case 'assoc': $data = array(); while ($row = $result->fetch(PDO::FETCH_ASSOC)) { - $key = array_shift($row); + $key = current($row); // value of the first element $data[$key] = $row; } break; @@ -456,7 +456,7 @@ public function nextSequence($name) * * Creates a portable table. * - * The $cols parameter should be in this format: + * The $cols parameter should be in this format ... * * {{code: php * $cols = array( @@ -562,7 +562,7 @@ public function listTables() * * Adds a portable column to a table in the database. * - * The $info parameter should be in this format: + * The $info parameter should be in this format ... * * {{code: php * $info = array( @@ -609,7 +609,7 @@ public function dropColumn($table, $name) * * Creates a portable index on a table. * - * The $info parameter should be in this format: + * The $info parameter should be in this format ... * * {{code: php * $type = 'normal'; @@ -752,7 +752,7 @@ public function quote($val) * Quotes a value and places into a piece of text at a placeholder. * * The placeholder is a question-mark; all placeholders will be replaced - * with the quoted value. For example: + * with the quoted value. For example ... * * {{code: php * $sql = Solar::factory('Solar_Sql'); @@ -783,7 +783,7 @@ public function quoteInto($txt, $val) * Quote multiple text-and-value pieces. * * The placeholder is a question-mark; all placeholders will be replaced - * with the quoted value. For example: + * with the quoted value. For example ... * * {{code: php * $sql = Solar::factory('Solar_Sql'); @@ -842,7 +842,7 @@ public function quoteMulti($list, $sep = null) * * Builds a column definition string. * - * The $info parameter should be in this format: + * The $info parameter should be in this format ... * * $info = array( * 'type' => bool|char|int|etc, diff --git a/Solar/Sql/Adapter.php b/Solar/Sql/Adapter.php index 953a3cbe..2073146c 100755 --- a/Solar/Sql/Adapter.php +++ b/Solar/Sql/Adapter.php @@ -32,23 +32,23 @@ abstract class Solar_Sql_Adapter extends Solar_Base { * * Keys are ... * - * `host`: - * (string) Host specification (typically 'localhost'). + * `host` + * : (string) Host specification (typically 'localhost'). * - * `port`: - * (string) Port number for the host name. + * `port` + * : (string) Port number for the host name. * - * `user`: - * (string) Connect to the database as this username. + * `user` + * : (string) Connect to the database as this username. * - * `pass`: - * (string) Password associated with the username. + * `pass` + * : (string) Password associated with the username. * - * `name`: - * (string) Database name (or file path, or TNS name). + * `name` + * : (string) Database name (or file path, or TNS name). * - * `mode`: - * (string) For SQLite, an octal file mode. + * `mode` + * : (string) For SQLite, an octal file mode. * * @var array * @@ -77,41 +77,41 @@ abstract class Solar_Sql_Adapter extends Solar_Base { * * The available column types are ... * - * `bool`: - * A true/false boolean, generally stored as an integer 1 or 0. + * `bool` + * : A true/false boolean, generally stored as an integer 1 or 0. * - * `char`: - * A fixed-length string of 1-255 characters. + * `char` + * : A fixed-length string of 1-255 characters. * - * `varchar`: - * A variable-length string of 1-255 characters. + * `varchar` + * : A variable-length string of 1-255 characters. * - * `smallint`: - * A 2-byte integer, value range (-32767 ... +32768). + * `smallint` + * : A 2-byte integer, value range (-32767 ... +32768). * - * `int`: - * A 4-byte integer, value range (-2,147,483,648 ... +2,147,483,647). + * `int` + * : A 4-byte integer, value range (-2,147,483,648 ... +2,147,483,647). * - * `bigint`: - * An 8-byte integer, value range roughly (-9,223,372,036,854,780,000... +9,223,372,036,854,779,999). + * `bigint` + * : An 8-byte integer, value range roughly (-9,223,372,036,854,780,000... +9,223,372,036,854,779,999). * - * `numeric`: - * A fixed-point decimal number. + * `numeric` + * : A fixed-point decimal number. * - * `float`: - * A double-precision floating-point decimal number. + * `float` + * : A double-precision floating-point decimal number. * - * `clob`: - * A character large object with a size of up to 2,147,483,647 bytes (about 2 GB). + * `clob` + * : A character large object with a size of up to 2,147,483,647 bytes (about 2 GB). * - * `date`: - * An ISO 8601 date stored as a 10-character string; e.g., '1979-11-07'. + * `date` + * : An ISO 8601 date stored as a 10-character string; e.g., '1979-11-07'. * - * `time`: - * An ISO 8601 time stored as an 8-character string; e.g., '12:34:56'. + * `time` + * : An ISO 8601 time stored as an 8-character string; e.g., '12:34:56'. * - * `timestamp`: - * An ISO 8601 timestamp stored as a 19-character string (no zone offset); e.g., '1979-11-07T12:34:56'. + * `timestamp` + * : An ISO 8601 timestamp stored as a 19-character string (no zone offset); e.g., '1979-11-07T12:34:56'. * * @var array * diff --git a/Solar/Sql/Result.php b/Solar/Sql/Result.php index 0adf1512..6b366381 100755 --- a/Solar/Sql/Result.php +++ b/Solar/Sql/Result.php @@ -32,9 +32,9 @@ class Solar_Sql_Result extends Solar_Base implements Iterator { * * Keys are ... * - * `PDOStatement`: - * (object) A PDOStatement object to be used as the - * result source. + * `PDOStatement` + * : (object) A PDOStatement object to be used as the + * result source. * * @var array * diff --git a/Solar/Sql/Row.php b/Solar/Sql/Row.php index cbe7d1b7..bae9c090 100644 --- a/Solar/Sql/Row.php +++ b/Solar/Sql/Row.php @@ -39,11 +39,11 @@ class Solar_Sql_Row extends Solar_Struct { * * Keys are ... * - * `data`: - * (array) Key-value pairs of colname => value. + * `data` + * : (array) Key-value pairs of colname => value. * - * `save`: - * (object) Source object for save() calls. + * `save` + * : (object) Source object for save() calls. * */ protected $_Solar_Sql_Row = array( diff --git a/Solar/Sql/Select.php b/Solar/Sql/Select.php index 3b34c7be..c0ea5466 100755 --- a/Solar/Sql/Select.php +++ b/Solar/Sql/Select.php @@ -24,51 +24,48 @@ * * Class for SQL select generation and results. * - * Example usage: - * - * - * $select = Solar::factory('Solar_Sql_Select'); - * - * // select these columns from the 'contacts' table - * $select->from('contacts', array( - * 'id', - * 'n_last', - * 'n_first', - * 'adr_street', - * 'adr_city', - * 'adr_region AS state', - * 'adr_postcode AS zip', - * 'adr_country', - * )); - * - * // on these ANDed conditions - * $select->where('n_last = :lastname'); - * $select->where('adr_city = :city'); - * - * // reverse-ordered by first name - * $select->order('n_first DESC') - * - * // get 50 per page, when we limit by page - * $select->setPaging(50); - * - * // bind data into the query. - * // remember :lastname and :city in the setWhere() calls above. - * $data = ('lastname' => 'Jones', 'city' => 'Memphis'); - * $select->bind($data); - * - * // limit by which page of results we want - * $select->limitPage(1); - * - * // get a Solar_Sql_Result object (the default) - * $result = $select->fetch(); // or fetch('result') - * - * // alternatively, get a Solar_Sql_Rowset object - * $rows = $select->fetch('all'); - * - * // find out the count of rows, and how many pages there are. - * // this comes back as an array('count' => ?, 'pages' => ?). - * $total = $select->countPages(); - * + * {{code: php + * $select = Solar::factory('Solar_Sql_Select'); + * + * // select these columns from the 'contacts' table + * $select->from('contacts', array( + * 'id', + * 'n_last', + * 'n_first', + * 'adr_street', + * 'adr_city', + * 'adr_region AS state', + * 'adr_postcode AS zip', + * 'adr_country', + * )); + * + * // on these ANDed conditions + * $select->where('n_last = :lastname'); + * $select->where('adr_city = :city'); + * + * // reverse-ordered by first name + * $select->order('n_first DESC') + * + * // get 50 per page, when we limit by page + * $select->setPaging(50); + * + * // bind data into the query. + * // remember :lastname and :city in the setWhere() calls above. + * $data = ('lastname' => 'Jones', 'city' => 'Memphis'); + * $select->bind($data); + * + * // limit by which page of results we want + * $select->limitPage(1); + * + * // get a Solar_Sql_Result object (the default) + * $result = $select->fetch(); // or fetch('result') + * + * // alternatively, get a Solar_Sql_Rowset object + * $rows = $select->fetch('all'); + * + * // find out the count of rows, and how many pages there are. + * // this comes back as an array('count' => ?, 'pages' => ?). + * $total = $select->countPages(); * }} * * @category Solar @@ -84,11 +81,11 @@ class Solar_Sql_Select extends Solar_Base { * * Keys are ... * - * `sql`: - * (dependency) A Solar_Sql dependency object. + * `sql` + * : (dependency) A Solar_Sql dependency object. * - * `paging`: - * (int) Number of rows per page. + * `paging` + * : (int) Number of rows per page. * * @var array * diff --git a/Solar/Sql/Table.php b/Solar/Sql/Table.php index 97523f0c..088bf2db 100755 --- a/Solar/Sql/Table.php +++ b/Solar/Sql/Table.php @@ -37,11 +37,11 @@ class Solar_Sql_Table extends Solar_Base { * * Keys are ... * - * `sql`: - * (dependency) A Solar_Sql dependency object. + * `sql` + * : (dependency) A Solar_Sql dependency object. * - * `locale`: - * (string) Path to locale files. + * `locale` + * : (string) Path to locale files. * * @var array * @@ -82,22 +82,22 @@ class Solar_Sql_Table extends Solar_Base { * * The column specification array for all columns in this table. * - * Each element in this array looks like this: - * - * - * $col = array( - * 'colName' => array( - * 'name' => (string) the colName, same as the key - * 'type' => (string) char, varchar, date, etc - * 'size' => (int) column size - * 'scope' => (int) decimal places - * 'valid' => (array) Solar_Valid methods and args - * 'require' => (bool) is this a required (non-null) column? - * 'autoinc' => (bool) auto-increment - * 'default' => (string|array) default value - * 'primary' => (bool) is this part of the primary key? - * ), - * ); + * Each element in this array looks like this... + * + * {{code: php + * $col = array( + * 'colName' => array( + * 'name' => (string) the colName, same as the key + * 'type' => (string) char, varchar, date, etc + * 'size' => (int) column size + * 'scope' => (int) decimal places + * 'valid' => (array) Solar_Valid methods and args + * 'require' => (bool) is this a required (non-null) column? + * 'autoinc' => (bool) auto-increment + * 'default' => (string|array) default value + * 'primary' => (bool) is this part of the primary key? + * ), + * ); * }} * * @var array @@ -107,7 +107,7 @@ class Solar_Sql_Table extends Solar_Base { /** * - * The index specification array for all indexes on this table: + * The index specification array for all indexes on this table. * * @var array * diff --git a/Solar/Struct.php b/Solar/Struct.php index 4c0fdbe8..17beba5e 100644 --- a/Solar/Struct.php +++ b/Solar/Struct.php @@ -23,8 +23,9 @@ * ($foo['bar']) and object notation ($foo->bar). This helps with * moving data among form objects, view helpers, SQL objects, etc. * - * Examples: + * Examples ... * + * {{code: php * $data = array('foo' => 'bar', 'baz' => 'dib', 'zim' => 'gir'); * $struct = Solar::factory('Solar_Struct', array('data' => $data)); * @@ -42,33 +43,40 @@ * * $struct->noSuchKey = 'nothing'; * echo $struct->noSuchKey; // null + * }} * * One problem is that casting the object to an array will not * reveal the data; you'll get an empty array. Instead, you should use * the toArray() method to get a copy of the object data. * + * {{code: php * $data = array('foo' => 'bar', 'baz' => 'dib', 'zim' => 'gir'); * $object = Solar::factory('Solar_Struct', array('data' => $data)); * * $struct = (array) $object; // $struct = array(); * * $struct = $object->toArray(); // $struct = array('foo' => 'bar', ...) + * }} * * Another problem is that you can't use object notation inside double- * quotes directly; you need to wrap in braces. * + * {{code: php * echo "$struct->foo"; // won't work * echo "{$struct->foo}"; // will work + * }} * * A third problem is that you can't address keys inside a foreach() * loop directly using array notation; you have to use object notation. * Originally reported by Antti Holvikari. * + * {{code: php * // will not work * foreach ($struct['subarray'] as $key => $val) { ... } * * // will work * foreach ($struct->subarray as $key => $val) { ... } + * }} * * @category Solar * @@ -83,8 +91,8 @@ class Solar_Struct extends Solar_Base implements ArrayAccess, Countable, Iterato * * Keys are ... * - * `data`: - * (array) Key-value pairs. + * `data` + * : (array) Key-value pairs. * */ protected $_Solar_Struct = array( @@ -119,7 +127,11 @@ class Solar_Struct extends Solar_Base implements ArrayAccess, Countable, Iterato public function __construct($config = null) { parent::__construct($config); - $this->_data = (array) $this->_config['data']; + if (is_array($this->_config['data'])) { + $this->_data = $this->_config['data']; + } else { + $this->_data = array(); + } } /** @@ -215,8 +227,10 @@ public function load($spec, $reset = false) if ($spec instanceof Solar_Struct) { // we can do this because $spec is of the same class $data = $spec->_data; + } elseif (is_array($spec)) { + $data = $spec; } else { - $data = (array) $spec; + $data = array(); } // load new data diff --git a/Solar/Test/Bench.php b/Solar/Test/Bench.php index 52389c6b..be48c557 100644 --- a/Solar/Test/Bench.php +++ b/Solar/Test/Bench.php @@ -35,12 +35,12 @@ class Solar_Test_Bench extends Solar_Base { * * Keys are ... * - * `loop`: - * (int) The number of times the benchmarking methods + * `loop` + * : (int) The number of times the benchmarking methods * should be run when using loop(). Default 1000. * - * `time`: - * (int) The time in minutes each method should run + * `time` + * : (int) The time in minutes each method should run * when using time(). Default 1. * * @var array diff --git a/Solar/Test/Example/PageController.php b/Solar/Test/Example/PageController.php new file mode 100644 index 00000000..624c7eac --- /dev/null +++ b/Solar/Test/Example/PageController.php @@ -0,0 +1,109 @@ + + * + * @license http://opensource.org/licenses/bsd-license.php BSD + * + * @version $Id: Example.php 1438 2006-07-06 13:36:30Z pmjones $ + * + */ + +/** + * Parent class. + */ +Solar::loadClass('Solar_Controller_Page'); + +/** + * + * Example page-controller to support unit tests. + * + * @category Solar + * + * @package Solar_Test + * + */ +class Solar_Test_Example_PageController extends Solar_Controller_Page { + + public $foo = 'bar'; + + protected $_action_default = 'foo'; + + public $hooks = array( + '_setup' => 0, + '_preRun' => 0, + '_preAction' => 0, + '_postAction' => 0, + '_postRun' => 0, + '_preRender' => 0, + '_postRender' => 0, + ); + + public function actionFoo() + { + // do nothing + } + + public function actionBumpyCase() + { + // do nothing + } + + public function actionNoRelatedView() + { + // do nothing + } + + public function actionTestForward() + { + return $this->_forward('foo'); + } + + public function setActionDefault($val) + { + $this->_action_default = $val; + } + + protected function _setup() + { + $this->hooks[__FUNCTION__] ++; + } + + protected function _preRun() + { + $this->hooks[__FUNCTION__] ++; + } + + protected function _preAction() + { + $this->hooks[__FUNCTION__] ++; + } + + protected function _postAction() + { + $this->hooks[__FUNCTION__] ++; + } + + protected function _postRun() + { + $this->hooks[__FUNCTION__] ++; + } + + protected function _preRender($view) + { + $this->hooks[__FUNCTION__] ++; + } + + protected function _postRender($output) + { + $this->hooks[__FUNCTION__] ++; + return $output; + } +} +?> \ No newline at end of file diff --git a/Solar/Test/Example/PageController/View/bumpyCase.php b/Solar/Test/Example/PageController/View/bumpyCase.php new file mode 100644 index 00000000..9724cafc --- /dev/null +++ b/Solar/Test/Example/PageController/View/bumpyCase.php @@ -0,0 +1 @@ +found actionBumpyCase \ No newline at end of file diff --git a/Solar/Test/Example/PageController/View/foo.php b/Solar/Test/Example/PageController/View/foo.php new file mode 100644 index 00000000..a440f8f0 --- /dev/null +++ b/Solar/Test/Example/PageController/View/foo.php @@ -0,0 +1 @@ +foo ?> \ No newline at end of file diff --git a/Solar/Test/Suite.php b/Solar/Test/Suite.php index ffe36dfe..97ef18fe 100644 --- a/Solar/Test/Suite.php +++ b/Solar/Test/Suite.php @@ -24,15 +24,15 @@ * * Class for running suites of unit tests. * - * Expects a directory structure like this: + * Expects a directory structure like this ... * - * Test/ - * Solar.php -- Test_Solar - * Solar/ - * Base.php -- Test_Solar_Base - * Uri.php -- Test_Solar_Uri - * Uri/ - * Action.php -- Test_Solar_Uri_Action + * Test/ + * Solar.php -- Test_Solar + * Solar/ + * Base.php -- Test_Solar_Base + * Uri.php -- Test_Solar_Uri + * Uri/ + * Action.php -- Test_Solar_Uri_Action * * @category Solar * @@ -47,15 +47,15 @@ class Solar_Test_Suite extends Solar_Base { * * Keys are ... * - * `dir`: - * (string) The directory where tests are located. + * `dir` + * : (string) The directory where tests are located. * - * `log`: - * (dependency) A Solar_Log dependency for logging test + * `log` + * : (dependency) A Solar_Log dependency for logging test * results. * - * `error_reporting`: - * (int) The level of error reporting we + * `error_reporting` + * : (int) The level of error reporting we * want to catch; default is E_ALL|E_STRICT. * * @var array @@ -144,9 +144,10 @@ public function __construct($config = null) // create a new log object $log_config = array( 'adapter' => 'Solar_Log_Adapter_Echo', - 'format' => '%m', - 'events' => 'test', - 'output' => 'text', + 'config' => array( + 'format' => '%m', + 'events' => 'test', + ), ); $this->_log = Solar::factory('Solar_Log', $log_config); } @@ -247,28 +248,28 @@ public function addTestMethods($class) * * Runs the test suite (or the sub-test series) and logs as it goes. * - * Returns an array of statistics with these keys: + * Returns an array of statistics with these keys ... * - * `plan`: - * (int) The planned number of tests. + * `plan` + * : (int) The planned number of tests. * - * `done`: - * (int) The number of tests actually done. + * `done` + * : (int) The number of tests actually done. * - * `time`: - * (int) The time, in seconds, it took to run all tests. + * `time` + * : (int) The time, in seconds, it took to run all tests. * - * `pass`: - * (array) Log of tests that passed. + * `pass` + * : (array) Log of tests that passed. * - * `skip`: - * (array) Log of tests that were skipped. + * `skip` + * : (array) Log of tests that were skipped. * - * `todo`: - * (array) Log of tests that are incomplete. + * `todo` + * : (array) Log of tests that are incomplete. * - * `fail`: - * (array) Log of tests that failed. + * `fail` + * : (array) Log of tests that failed. * * @param string $series The sub-test series to run, typically a * class name (not including the 'Test_' prefix). diff --git a/Solar/Uri.php b/Solar/Uri.php index 29b23e42..cffa8d96 100755 --- a/Solar/Uri.php +++ b/Solar/Uri.php @@ -32,9 +32,9 @@ class Solar_Uri extends Solar_Base { * * Keys are ... * - * `path`: - * (string) A path prefix. Generally needed only - * for specific URI subclasses, e.g. Solar_Uri_Action. + * `path` + * : (string) A path prefix. Generally needed only + * for specific URI subclasses, e.g. Solar_Uri_Action. * * @var array * @@ -132,6 +132,15 @@ class Solar_Uri extends Solar_Base { '#' => '%23', ); + /** + * + * Details about the request environment. + * + * @var Solar_Request + * + */ + protected $_request; + /** * * Constructor. @@ -144,6 +153,9 @@ public function __construct($config = null) // real construction parent::__construct($config); + // get the request environment + $this->_request = Solar::factory('Solar_Request'); + // fix the base path by adding leading and trailing slashes if (trim($this->_config['path']) == '') { $this->_config['path'] = '/'; @@ -170,18 +182,24 @@ public function __construct($config = null) public function set($uri = null) { // build a default scheme (with '://' in it) - $ssl = Solar::server('HTTPS', 'off'); + $ssl = $this->_request->server('HTTPS', 'off'); $scheme = (($ssl == 'on') ? 'https' : 'http') . '://'; // get the current host, using a dummy host name if needed. // we need a host name so that parse_url() works properly. // we remove the dummy host name at the end of this method. - $host = Solar::server('HTTP_HOST', 'example.com'); + $host = $this->_request->server('HTTP_HOST', 'example.com'); - // force to the current uri? + // right now, we assume we don't have to force any values. + $forced = false; + + // forcibly set to the current uri? $uri = trim($uri); if (! $uri) { + // we're forcing values + $forced = true; + // add the scheme and host $uri = $scheme . $host; @@ -195,18 +213,19 @@ public function set($uri = null) if (substr($this->_config['path'], -5) == '.php/') { // guess that mod_rewrite is off; build up from // component parts. - $uri .= Solar::server('SCRIPT_NAME') - . Solar::server('PATH_INFO') - . '?' . Solar::server('QUERY_STRING'); + $uri .= $this->_request->server('SCRIPT_NAME') + . $this->_request->server('PATH_INFO') + . '?' . $this->_request->server('QUERY_STRING'); } else { // guess that mod_rewrite is on - $uri .= Solar::server('REQUEST_URI'); + $uri .= $this->_request->server('REQUEST_URI'); } } - // add the scheme and host? + // forcibly add the scheme and host? $pos = strpos($uri, '://'); if ($pos === false) { + $forced = true; $uri = ltrim($uri, '/'); $uri = "$scheme$host/$uri"; } @@ -254,8 +273,8 @@ public function set($uri = null) $this->setPath($elem['path']); $this->setQuery($elem['query']); - // remove any dummy host name - if (! Solar::server('HTTP_HOST')) { + // if we had to force values, remove dummy placeholders + if ($forced && ! $this->_request->server('HTTP_HOST')) { $this->scheme = null; $this->host = null; } diff --git a/Solar/Uri/Action.php b/Solar/Uri/Action.php index 150480cb..46457d1c 100755 --- a/Solar/Uri/Action.php +++ b/Solar/Uri/Action.php @@ -44,8 +44,8 @@ class Solar_Uri_Action extends Solar_Uri { * * Keys are ... * - * `path`: - * (string) A path prefix specifically for actions, e.g. '/index.php/'. + * `path` + * : (string) A path prefix specifically for actions, e.g. '/index.php/'. * * @var array * diff --git a/Solar/Uri/Public.php b/Solar/Uri/Public.php index 07295d83..15ff030a 100644 --- a/Solar/Uri/Public.php +++ b/Solar/Uri/Public.php @@ -44,8 +44,8 @@ class Solar_Uri_Public extends Solar_Uri { * * Keys are ... * - * `path`: - * (string) A path prefix specifically for public resources, e.g. '/public/'. + * `path` + * : (string) A path prefix specifically for public resources, e.g. '/public/'. * * @var array * diff --git a/Solar/View/Helper/Form.php b/Solar/View/Helper/Form.php index c6a49404..82156683 100644 --- a/Solar/View/Helper/Form.php +++ b/Solar/View/Helper/Form.php @@ -166,6 +166,15 @@ class Solar_View_Helper_Form extends Solar_View_Helper { 'feedback' => array(), ); + /** + * + * Details about the request environment. + * + * @var Solar_Request + * + */ + protected $_request; + /** * * Constructor. @@ -175,7 +184,8 @@ class Solar_View_Helper_Form extends Solar_View_Helper { */ public function __construct($config = null) { - $this->_default_attribs['action'] = $_SERVER['REQUEST_URI']; + $this->_request = Solar::factory('Solar_Request'); + $this->_default_attribs['action'] = $this->_request->server('REQUEST_URI'); parent::__construct($config); $this->reset(); } @@ -556,6 +566,12 @@ public function fetch() } } + // SPECIAL CASE: + // checkboxes that are not in groups don't get an "extra" label. + if (strtolower($info['type']) == 'checkbox' && ! $in_group) { + $info['label'] = null; + } + // get the element output $element = $helper->$method($info); diff --git a/Solar/View/Helper/GetTextRaw.php b/Solar/View/Helper/GetTextRaw.php index a1b46920..8a59b0f5 100644 --- a/Solar/View/Helper/GetTextRaw.php +++ b/Solar/View/Helper/GetTextRaw.php @@ -37,8 +37,8 @@ class Solar_View_Helper_GetTextRaw extends Solar_View_Helper { * * Keys are ... * - * `class`: - * (string) The class for locale translations. + * `class` + * : (string) The class for locale translations. * * @var array * diff --git a/Solar/View/Helper/Js.php b/Solar/View/Helper/Js.php index 8b9064ad..f39227d3 100644 --- a/Solar/View/Helper/Js.php +++ b/Solar/View/Helper/Js.php @@ -56,6 +56,15 @@ class Solar_View_Helper_Js extends Solar_View_Helper_JsLibrary { */ public $files; + /** + * + * Array of CSS files required by a JavaScript class + * + * @var array + * + */ + public $styles; + /** * * Array of inline JavaScript needed to provide specified functionality @@ -117,6 +126,27 @@ public function fetchFiles() return $js; } + /** + * + * Build and return list of CSS files for page header + * + * @return string Block of HTML with tag. - * + * * Adds "media" attribute if not specified, and always uses * "type" attribute of "text/css". - * + * * @param string $href The source href for the stylesheet. - * + * * @param array $attribs Additional attributes for the tag. * */ public function style($href, $attribs = null) { settype($attribs, 'array'); + + // force type="text/css" $attribs['type'] = 'text/css'; + + // default to media="screen" if (empty($attribs['media'])) { $attribs['media'] = 'screen'; } - if (substr($href, 0, 4) == 'http' - && strpos($href, '://') !== false) { - $url = $href; - } else { - $url = $this->_view->publicHref($href, true); - } + + // build the URL as an href to a public resource, + // get it back raw. + $url = $this->_view->publicHref($href, true); + + // build and return the tag return '_view->attribs($attribs) . '>' . "@import url(\"$url\");"; } diff --git a/Solar/View/Helper/TypekeyLink.php b/Solar/View/Helper/TypekeyLink.php new file mode 100644 index 00000000..ed7c42cb --- /dev/null +++ b/Solar/View/Helper/TypekeyLink.php @@ -0,0 +1,106 @@ + + * + * @license http://opensource.org/licenses/bsd-license.php BSD + * + * @version $Id: ActionList.php 307 2006-08-11 10:36:44Z clay $ + * + */ + +/** + * Parent class + */ +Solar::loadClass('Solar_View_Helper'); + +/** + * + * Generates a anchor linking to the TypeKey login site. + * + * Uses the same TypeKey token as Solar_Auth_Adapter_TypeKey + * @category Solar + * + * @package Solar_View + * + */ +class Solar_View_Helper_TypekeyLink extends Solar_View_Helper { + + /** + * + * User-defined configuration values. + * + * Keys are ... + * + * `token` + * : (string) The TypeKey site identifier token string. If empty, + * the helper will use the value from the Solar config file under + * the $config['Solar_Auth_Adapter_Typekey']['token'] key. + * + * `href` + * : (string) The HREF of the TypeKey login service. Default is + * "https://www.typekey.com:443/t/typekey/login". + * + * `need_email` + * : (bool) Whether or not to get the TypeKey user's email address. + * Default false. + * + * @var array + * + */ + protected $_Solar_View_Helper_TypekeyLink = array( + 'token' => null, + 'href' => "https://www.typekey.com:443/t/typekey/login", + 'need_email' => false, + ); + + /** + * + * Generates a link to the TypeKey login site. + * + * @param string $text The text to display for the link. + * + * @return string + * + */ + public function typekeyLink($text = null) + { + // get a URI processor; defaults to the current URI. + $uri = Solar::factory('Solar_Uri'); + + // do not retain the GET 'submit' value on the current URI. + // this prevents double-processing of actions submitted via GET. + if (! empty($uri->query['submit'])) { + unset($uri->query['submit']); + } + + // save the current URI as the return location after typekey. + $return = $uri->fetch(true); + + // now reset the URI to point to the typekey service + $uri->set($this->_config['href']); + + // add the typekey token + if (empty($this->_config['token'])) { + $uri->query['t'] = Solar::config('Solar_Auth_Adapter_Typekey', 'token'); + } else { + $uri->query['t'] = $this->_config['token']; + } + + // convert need_email from true/false to 1/0 and add + $uri->query['need_email'] = (int) $this->_config['need_email']; + + // add the return location + $uri->query['_return'] = $return; + + // done! + return $this->_view->anchor($uri->fetch(true), $text); + } +} +?> \ No newline at end of file diff --git a/docs/manual/Main/ConstructorParameters b/docs/manual/Main/ConstructorParameters index 0e27f1d3..8f5fd723 100644 --- a/docs/manual/Main/ConstructorParameters +++ b/docs/manual/Main/ConstructorParameters @@ -51,4 +51,4 @@ In comparison, Solar classes can be instantiated like this: > **Note:** Usually Solar objects are instantiated via [[Solar::factory()]], not via direct instantiation as above, but you get the idea. -Why do things this way? The idea is to make it easy to read data from a ConfigFile and automatically configure object instances. In the Solar_Test_Example class, the constructor parameter could easily be read from a configuration file and passed to the Solar_Test_Example constructor, which is not so easy with the Normal_Example. +Why do things this way? The idea is to make it easy to read data from a [[ConfigFile]] and automatically configure object instances. In the Solar_Test_Example class, the constructor parameter could easily be read from a configuration file and passed to the Solar_Test_Example constructor, which is not so easy with the Normal_Example. diff --git a/docs/manual/Main/LocaleFiles b/docs/manual/Main/LocaleFiles index 85db82f6..fc7b6f62 100644 --- a/docs/manual/Main/LocaleFiles +++ b/docs/manual/Main/LocaleFiles @@ -6,7 +6,7 @@ Solar is designed with built-in localization support. This means you can create As you scan through the Solar distribution, you will see that many class directories have a `Locale/` subdirectory. That subdirectory will have at least one file in it, `en_US.php`. That contains the localization strings for the en_US locale. As Solar gains acceptance, we expect to build more locale files for each localization (e.g., 'de_DE' for German, 'pt_BR' for Brazilian Portuguese, and so on). -As with the ConfigFile, locale files are simply PHP scripts that return an array. Thus, the simplest possible locale file is an empty array: +As with the [[ConfigFile]], locale files are simply PHP scripts that return an array. Thus, the simplest possible locale file is an empty array: {{code: php return array(); diff --git a/docs/manual/Main/NamingConventions b/docs/manual/Main/NamingConventions index 13dde7d9..bf34fcda 100644 --- a/docs/manual/Main/NamingConventions +++ b/docs/manual/Main/NamingConventions @@ -38,33 +38,44 @@ Global Variables Do not use global variables. We say this for two reasons: -1. They open up security problems when `register_globals` is turned on +1. They open up security problems when `register_globals` is turned on. -2. They pollute the global namespace and may collide or overwrite each other in differing contexts +2. They pollute the global namespace and may collide or overwrite each other in differing contexts. -------------------- Session Variables -------------------- -`$_SESSION` keys for Solar classes shall match the class name. Thus, a `Solar_Test_Example` session variable called `something` would be addressed as `$_SESSION['Solar_Test_Example']['something']`. +`$_SESSION` keys for Solar classes shall match the class name. Thus, a `Solar_Test_Example` class using a session variable called `foo` would be addressed as `$_SESSION['Solar_Test_Example']['foo']`. + +The easiest way to ahere to this convention is to use a [[Solar_Session::HomePage | Solar_Session]] storage object within your class. For example ... + +{{code: php + $session = Solar::factory('Solar_Session', + array('class' => 'Solar_Test_Example') + ); + + // equivalent of $_SESSION['Solar_Test_Example']['something'] + $session->store['foo'] = 'bar'; +}} -------------------- Config File Keys -------------------- -Keys in the ConfigFile shall conform to the class name. Thus, a `Solar_Test_Example` constructor configuration would be addressed as an associative array in the `['Solar_Test_Example']` key of the ConfigFile. +Keys in the [[ConfigFile]] shall conform to the class name. Thus, a `Solar_Test_Example` constructor configuration would be addressed as an associative array in the `['Solar_Test_Example']` key of the [[ConfigFile]]. -------------------- Locale File Names -------------------- -LocaleFiles should be named for their locale code. For example, the `en_US` file should be named `en_US.php`, the `pt_BR` file should be named `pt_BR.php`, and so on. +[[LocaleFiles]] should be named for their locale code. For example, the `en_US` file should be named `en_US.php`, the `pt_BR` file should be named `pt_BR.php`, and so on. -------------------- Locale Translation Keys -------------------- -Keys in the LocaleFiles should usually be in all capitals, using only A-Z, 0-9, and underscores for the key name. In general, you should prefix the key name with a hint for its usage: +Keys in the [[LocaleFiles]] should usually be in all capitals, using only A-Z, 0-9, and underscores for the key name. In general, you should prefix the key name with a hint for its usage: | Prefix | Usage | ---------- | ----------------------------------------------- diff --git a/docs/manual/Main/SolarStandards b/docs/manual/Main/SolarStandards index f89eea80..d2fcfb0b 100644 --- a/docs/manual/Main/SolarStandards +++ b/docs/manual/Main/SolarStandards @@ -4,14 +4,14 @@ Standards All Solar classes follow a common set of implementation standards. -* They each adhere to a common StyleGuide of coding standards +* They each adhere to a common [[StyleGuide]] of coding standards -* They use a standard set of NamingConventions +* They use a standard set of [[NamingConventions]] -* Each descends from a common lightweight BaseClass +* Each descends from a common lightweight [[BaseClass]] -* They all use the same ConstructorParameters for instantiation +* They all use the same [[ConstructorParameters]] for instantiation -* Each reads its configuration from a common ConfigFile +* Each reads its configuration from a common [[ConfigFile]] -* All are ready for localization using their own set of LocaleFiles +* All are ready for localization using their own set of [[LocaleFiles]] diff --git a/docs/manual/Solar/ApiRef b/docs/manual/Solar/ApiRef index 5fe2f41e..289a1f22 100644 --- a/docs/manual/Solar/ApiRef +++ b/docs/manual/Solar/ApiRef @@ -40,20 +40,6 @@ Groups At A Glance * [[Solar::$registry]] -* Superglobals - - * [[Solar::get()]] - - * [[Solar::post()]] - - * [[Solar::cookie()]] - - * [[Solar::server()]] - - * [[Solar::session()]] - - * [[Solar::pathinfo()]] - * File-Related * [[Solar::fileExists()]] diff --git a/docs/manual/Solar/HomePage b/docs/manual/Solar/HomePage index 79c3756f..9d43b559 100644 --- a/docs/manual/Solar/HomePage +++ b/docs/manual/Solar/HomePage @@ -17,10 +17,7 @@ it. // just call its static methods. Solar::start(); - $action = Solar::get('action'); - $result = Solar::run('/path/to/file.php'); - Solar::stop(); }} -Once you have loaded the Solar.php file, you can call any of the ClassMethods it provides. +Once you have loaded the Solar.php file, you can call any of the [[ClassMethods]] it provides. diff --git a/docs/manual/Solar/cookie() b/docs/manual/Solar/cookie() deleted file mode 100644 index c05e390c..00000000 --- a/docs/manual/Solar/cookie() +++ /dev/null @@ -1,13 +0,0 @@ -This method accesses the $_COOKIE superglobal array and -returns a copy of the requested key. If no key is -specified, a copy of the entire $_COOKIE array is -returned. If the requested key does not exist in the $_COOKIE -array, the default value is returned instead. - -For example, to get the 'remember_me' key from the $_COOKIE -array, with `false` as the default value, you would do this: - -{{code: php - $remember_me = Solar::cookie('remember_me', false); -}} - diff --git a/docs/manual/Solar/get() b/docs/manual/Solar/get() deleted file mode 100644 index 74c2f58a..00000000 --- a/docs/manual/Solar/get() +++ /dev/null @@ -1,13 +0,0 @@ -This method accesses the $_GET superglobal array and returns a -copy of the requested key. If no key is specified, -a copy of the entire $_GET array is returned. If the -requested key does not exist in the $_GET array, the default -value is returned instead. - -For example, to get the 'user_name' key from the $_GET array, -with 'No Name' as the default value, you would do this: - -{{code: php - $name = Solar::get('user_name', 'No Name'); -}} - diff --git a/docs/manual/Solar/pathinfo() b/docs/manual/Solar/pathinfo() deleted file mode 100644 index 43537cf1..00000000 --- a/docs/manual/Solar/pathinfo() +++ /dev/null @@ -1,35 +0,0 @@ -This method is a bit different from the other superglobal -methods. It accesses the $_SERVER['PATH_INFO'] value, and -returns copy of the requested key. If no key is -specified, the entire path-info set is returned as an array. - -The "path-info" portion of a URI comes after the script name -but before any $_GET parameters. For example, in the -following URI ... - - http://example.com/path/to/script.php/foo/bar/baz?zim=gir - -... the "path-info" is `/foo/bar/baz`. - -Path-info values are addressed by their integer position -number, not by associative array key name (as with $_GET et. -al.). Thus, Solar builds its path-info array for the example -URI to look like this: - -{{code: php - array( - 0 => 'foo', - 1 => 'bar', - 2 => 'baz', - ); -}} - -For example, to get path-info values, you would call the -pathinfo() method like this: - -{{code: php - $info_2 = Solar::pathinfo(2, null); // equals 'baz' - $info_3 = Solar::pathinfo(3, 'dib'); // equals 'dib' -}} - - diff --git a/docs/manual/Solar/post() b/docs/manual/Solar/post() deleted file mode 100644 index 76e41dc5..00000000 --- a/docs/manual/Solar/post() +++ /dev/null @@ -1,15 +0,0 @@ -This method accesses the $_POST superglobal array and returns -a copy of the requested key. If no key is -specified, a copy of the entire $_POST array is -returned. If the requested key does not exist in the $_POST -array, the default value is returned instead. - -For example, to get the 'subject_line' key from the $_POST -array, with 'No Subject' as the default value, you would do -this: - -{{code: php - $subject = Solar::post('subject_line', 'No Subject'); -}} - - diff --git a/docs/manual/Solar/server() b/docs/manual/Solar/server() deleted file mode 100644 index bbb0b712..00000000 --- a/docs/manual/Solar/server() +++ /dev/null @@ -1,13 +0,0 @@ -This method accesses the $_SERVER superglobal array and -returns a copy of the requested key. If no key is -specified, a copy of the entire $_SERVER array is -returned. If the requested key does not exist in the $_SERVER -array, the default value is returned instead. - -For example, to get the 'REQUEST_URI' key from the $_SERVER -array, with `false` as the default value, you would do this: - -{{code: php - $remember_me = Solar::server('REQUEST_URI', false); -}} - diff --git a/docs/manual/Solar/session() b/docs/manual/Solar/session() deleted file mode 100644 index 42937e34..00000000 --- a/docs/manual/Solar/session() +++ /dev/null @@ -1,13 +0,0 @@ -This method accesses the $_SESSION superglobal array and -returns a copy of the requested key. If no key is -specified, a copy of the entire $_SESSION array is -returned. If the requested key does not exist in the -$_SESSION array, the default value is returned instead. - -For example, to get the 'last_active' key from the $_SESSION -array, with `false` as the default value, you would do this: - -{{code: php - $last_active = Solar::session('last_active', false); -}} - diff --git a/docs/manual/Solar_Base/$_config b/docs/manual/Solar_Base/$_config index e4b54274..74f0216f 100644 --- a/docs/manual/Solar_Base/$_config +++ b/docs/manual/Solar_Base/$_config @@ -1,5 +1,10 @@ -The $_config property has a special purpose within Solar classes: it contains the configuration parameters for instantiating an object. +The $_config property has a special purpose within Solar classes: it +contains the configuration parameters for instantiating an object. -When you extend Solar_Base and instantiate the subclassed object, any key in this array will be re-populated with related keys from the [[Main::ConfigFile | config file]], and/or from keys set in the configuration parameter of [[Solar::factory()]] at instantiation time. +When you extend Solar_Base and instantiate the subclassed object, any +key in this array will be re-populated with related keys from the +[[Main::ConfigFile | config file]], and/or from keys set in the +configuration parameter of [[Solar::factory()]] at instantiation time. -Because Solar_Base is intended for extension, you can add as many config keys as you like. \ No newline at end of file +Because Solar_Base is intended for extension, you can add as many config +keys as you like. \ No newline at end of file diff --git a/docs/manual/Solar_Cache/HomePage b/docs/manual/Solar_Cache/HomePage index 0c28c021..3c3db4cf 100644 --- a/docs/manual/Solar_Cache/HomePage +++ b/docs/manual/Solar_Cache/HomePage @@ -118,13 +118,16 @@ we use that instead (thus speeding up the script execution). {{code: php require_once 'Solar.php'; Solar::start(); - - // connect to the cache - $cache = Solar::factory('Solar_Cache'); - + + // discover the request environment + $request = Solar::factory('Solar_Request'); + // every cache entry needs a unique ID; we'll assume that ID // is passed as part of the URL. - $id = Solar::get('id'); + $id = $request->get('id'); + + // connect to the cache + $cache = Solar::factory('Solar_Cache'); // try to get the cache entry. if the entry is past its lifetime, // this will addiitonally delete the entry for us, keeping the cache diff --git a/docs/manual/Solar_Form/GettingStarted b/docs/manual/Solar_Form/GettingStarted index 91bfd903..b2fbba0e 100644 --- a/docs/manual/Solar_Form/GettingStarted +++ b/docs/manual/Solar_Form/GettingStarted @@ -149,10 +149,10 @@ submissions. This is very easy to do: just add a // ... create a Solar_View object, assign, and display. }} -The [[Solar_Form::populate()]] method will look for the -$_POST['user_email'] array value and put it into the $form object for -you. If the user entered "nobody@example.com" as the address, the view -script would display something like this: +The [[Solar_Form::populate()]] method will examine the request for a +POST variable called `user_email` and put its value into the $form +object for you. If the user entered "nobody@example.com" as the address, +the view script would display something like this: {{code: html @@ -161,9 +161,6 @@ script would display something like this: }} -(Technically, the form will use [[Solar::post()]] to get the values -instead of pulling directly from $_POST.) - Validation ==================== @@ -297,8 +294,11 @@ method. // populate the form with user input $form->populate(); + // discover the request environment + $request = Solar::factory('Solar_Request'); + // what was the submission request? - $submit = Solar::post('submit'); + $submit = $request->post('submit'); // process a 'Save' submission if ($submit == 'Save') { @@ -311,8 +311,8 @@ method. } }} -Note the the array returned by [[Solar_Form::values()]] will match the -structure of the $_POST array for the form elements, so if you had -sub-arrays in the form, you will get sub-arrays from -[[Solar_Form::values()]]. Wise use of array key names will help you keep -track of what information goes in which table, and so on. \ No newline at end of file +Note that the array returned by [[Solar_Form::values()]] will match the +array of POST variables in the request, so if you had sub-arrays in the +form, you will get sub-arrays from [[Solar_Form::values()]]. Wise use of +array key names will help you keep track of what information goes in +which table, and so on. \ No newline at end of file diff --git a/docs/manual/Solar_Form/HomePage b/docs/manual/Solar_Form/HomePage index d818a563..e8fdaea4 100644 --- a/docs/manual/Solar_Form/HomePage +++ b/docs/manual/Solar_Form/HomePage @@ -8,16 +8,17 @@ elements should be presented; you then pass the object instance into your view, which can take those "hints" any way it likes. These "hints" are called the [[FormDescriptor]] array. -In addition, the class will draw in data from an array (typically -$_POST), validate the newly-populated structure against filters and +In addition, the class will draw in data from an array (typically POST +variables), validate the newly-populated structure against filters and rules that you sets up, and set feedback messages based on which elements were valid and which were not. In many ways, Solar_Form is a cousin to [HTML_QuickForm](http://pear.php.net/HTML_QuickForm), but with all rendering removed. -You can see a full example on [[GettingStarted]], but here's a short one to -whet your curiosity. Note that we use a small bit of localization -(although a real form would have [[LocalizedForms | everything localized]]). +You can see a full example on [[GettingStarted]], but here's a short one +to whet your curiosity. Note that we use a small bit of localization +(although a real form would have [[LocalizedForms | everything +localized]]). {{code: php require_once 'Solar.php'; @@ -60,8 +61,11 @@ whet your curiosity. Note that we use a small bit of localization // Process submissions // + // discover the request environment + $request = Solar::factory('Solar_Request'); + // get the submission request - $submit = Solar::post('submit'); + $submit = $request->post('submit'); // handle submissions based on the localized button values if ($submit == $this->locale('SUBMIT_SAVE')) { diff --git a/docs/manual/Solar_Form/LocalizedForms b/docs/manual/Solar_Form/LocalizedForms index 56085b64..cf6d9140 100644 --- a/docs/manual/Solar_Form/LocalizedForms +++ b/docs/manual/Solar_Form/LocalizedForms @@ -103,8 +103,11 @@ string for that language. // populate the form with user input $form->populate(); + // discover the request environment + $request = Solar::factory('Solar_Request'); + // capture what operation the user wants to perform - $submit = Solar::post('submit'); + $submit = $request->post('submit'); // process a 'Save' submission. Note that we check against // the localized value of the operation, not a hard-coded diff --git a/docs/manual/Solar_Form/populate() b/docs/manual/Solar_Form/populate() index edb69087..64a57124 100644 --- a/docs/manual/Solar_Form/populate() +++ b/docs/manual/Solar_Form/populate() @@ -1,11 +1,11 @@ This method takes values from the population source and imports them into their corresponding form elements. -* If the source is null (the default), then the population source is - determined based on the ``Solar_Form::$attribs['method']`` key (c.f. - the Solar_Form::$attribs property) -- if 'get', then [[Solar::get()]] - is used (i.e. the filtered $_GET array); if 'post', then - [[Solar::post()]] is used (i.e., the filtered $_POST array). +* If the source is null (the default), then the population source is the + HTTP request (c.f. [[Solar_Request::HomePage | Solar_Request]] and the + key is determined based on the [[Solar_Form::$attribs]] `'method'` + value. If 'get', then [[Solar_Request::get()]]; if 'post', then + [[Solar_Request::post()]] is used. * If the source is an array, then the array key-value pairs are recursively mapped to form element names and the values are imported @@ -41,7 +41,6 @@ array elements would look like this in order to map into the above form elements: {{code: php - // $source can be $_GET, $_POST, your own array, an object, etc $source = array( 'id' => '12345', 'user' => array( diff --git a/docs/manual/Solar_Uri/HomePage b/docs/manual/Solar_Uri/HomePage index 322e05ed..f615380d 100644 --- a/docs/manual/Solar_Uri/HomePage +++ b/docs/manual/Solar_Uri/HomePage @@ -72,5 +72,5 @@ can modify the component parts, then fetch a new URI string. }} As you can see, this class is quite powerful when it comes to URIs. For -more information, be sure to read about the various ClassMethods. +more information, be sure to read about the various [[ClassMethods]]. diff --git a/docs/manual/Solar_Valid/HomePage b/docs/manual/Solar_Valid/HomePage index 079064e7..7df27583 100644 --- a/docs/manual/Solar_Valid/HomePage +++ b/docs/manual/Solar_Valid/HomePage @@ -8,9 +8,12 @@ values and validating database fields. // get a validation object $valid = Solar::factory('Solar_Valid'); - - // Fetch a copy of the $_GET['name'] value - $name = Solar::get('name'); + + // get a request object + $request = Solar::factory('Solar_Request'); + + // Fetch a copy of the GET request variable for 'name' + $name = $request->get('name'); // Does it match the "alpha" validation rule? // (i.e., A-Z and a-z only). @@ -18,9 +21,8 @@ values and validating database fields. echo htmlspecialchars("Name '$name' is not valid."); } - - // Fetch a copy of the $_POST['date'] value - $date = Solar::post('date'); + // Fetch a copy of the POST request variable for 'date' + $date = $request->post('date'); // Is it an ISO-formatted date? (Alternatively, // it can be completely blank.) diff --git a/tests/Test/Solar/Auth/Adapter.php b/tests/Test/Solar/Auth/Adapter.php index a15a28e4..47effd8d 100644 --- a/tests/Test/Solar/Auth/Adapter.php +++ b/tests/Test/Solar/Auth/Adapter.php @@ -16,9 +16,15 @@ abstract class Test_Solar_Auth_Adapter extends Solar_Test { protected $_uri = null; + protected $_request; + public function __construct($config = null) { parent::__construct($config); + + // convert from Test_Solar_Auth_Adapter_Whatever + // to Solar_Auth_Adapter_Whatever + $this->_class = substr(get_class($this), 5); } public function __destruct() @@ -28,10 +34,14 @@ public function __destruct() public function setup() { - // convert from Test_Solar_Adapter_Whatever - // to Solar_Adapter_Whatever - $this->_class = substr(get_class($this), 5); + // get a new adapter $this->_auth = Solar::factory($this->_class, $this->_config); + + // get the request environment ... + $this->_request = Solar::factory('Solar_Request'); + + // and reload it fresh for this test. + $this->_request->load(true); } public function teardown() @@ -70,14 +80,8 @@ public function testSetCommon() public function testIsLoginRequest_true() { // fake the POST parameters - $prior_post = $_POST; - $_POST['submit'] = $this->_auth->locale('SUBMIT_LOGIN'); - - // assert + $this->_request->post['submit'] = $this->_auth->locale('SUBMIT_LOGIN'); $this->assertTrue($this->_auth->isLoginRequest()); - - // restore POST params - $_POST = $prior_post; } public function testIsLoginRequest_false() @@ -88,14 +92,8 @@ public function testIsLoginRequest_false() public function testIsLogoutRequest_true() { // fake the POST parameters - $prior_post = $_POST; - $_POST['submit'] = $this->_auth->locale('SUBMIT_LOGOUT'); - - // assert + $this->_request->post['submit'] = $this->_auth->locale('SUBMIT_LOGOUT'); $this->assertTrue($this->_auth->isLogoutRequest()); - - // restore POST params - $_POST = $prior_post; } public function testIsLogoutRequest_false() @@ -108,7 +106,6 @@ public function testIsLoginValid_true() $this->_fakePostLogin_valid(); $this->assertTrue($this->_auth->isLoginRequest()); $this->assertTrue($this->_auth->isLoginValid()); - $this->_restorePost(); } public function testIsLoginValid_badPasswd() @@ -116,7 +113,6 @@ public function testIsLoginValid_badPasswd() $this->_fakePostLogin_badPasswd(); $this->assertTrue($this->_auth->isLoginRequest()); $this->assertFalse($this->_auth->isLoginValid()); - $this->_restorePost(); } public function testIsLoginValid_noSuchUser() @@ -124,7 +120,6 @@ public function testIsLoginValid_noSuchUser() $this->_fakePostLogin_noSuchUser(); $this->assertTrue($this->_auth->isLoginRequest()); $this->assertFalse($this->_auth->isLoginValid()); - $this->_restorePost(); } public function testReset() @@ -143,7 +138,6 @@ public function testGetHandle() $this->assertTrue($this->_auth->isLoginRequest()); $this->assertTrue($this->_auth->isLoginValid()); $this->assertSame($this->_auth->getHandle(), $this->_handle); - $this->_restorePost(); } public function testGetEmail() @@ -152,7 +146,6 @@ public function testGetEmail() $this->assertTrue($this->_auth->isLoginRequest()); $this->assertTrue($this->_auth->isLoginValid()); $this->assertSame($this->_auth->getEmail(), $this->_email); - $this->_restorePost(); } public function testGetMoniker() @@ -161,7 +154,6 @@ public function testGetMoniker() $this->assertTrue($this->_auth->isLoginRequest()); $this->assertTrue($this->_auth->isLoginValid()); $this->assertSame($this->_auth->getMoniker(), $this->_moniker); - $this->_restorePost(); } public function testGetUri() @@ -170,36 +162,27 @@ public function testGetUri() $this->assertTrue($this->_auth->isLoginRequest()); $this->assertTrue($this->_auth->isLoginValid()); $this->assertSame($this->_auth->getUri(), $this->_uri); - $this->_restorePost(); } protected function _fakePostLogin_valid() { - $this->_post = $_POST; - $_POST['submit'] = $this->_auth->locale('SUBMIT_LOGIN'); - $_POST['handle'] = 'pmjones'; - $_POST['passwd'] = 'jones'; + $this->_request->post['submit'] = $this->_auth->locale('SUBMIT_LOGIN'); + $this->_request->post['handle'] = 'pmjones'; + $this->_request->post['passwd'] = 'jones'; } protected function _fakePostLogin_badPasswd() { - $this->_post = $_POST; - $_POST['submit'] = $this->_auth->locale('SUBMIT_LOGIN'); - $_POST['handle'] = 'pmjones'; - $_POST['passwd'] = 'badpass'; + $this->_request->post['submit'] = $this->_auth->locale('SUBMIT_LOGIN'); + $this->_request->post['handle'] = 'pmjones'; + $this->_request->post['passwd'] = 'badpass'; } protected function _fakePostLogin_noSuchUser() { - $this->_post = $_POST; - $_POST['submit'] = $this->_auth->locale('SUBMIT_LOGIN'); - $_POST['handle'] = 'nouser'; - $_POST['passwd'] = 'badpass'; - } - - protected function _restorePost() - { - $_POST = $this->_post; + $this->_request->post['submit'] = $this->_auth->locale('SUBMIT_LOGIN'); + $this->_request->post['handle'] = 'nouser'; + $this->_request->post['passwd'] = 'badpass'; } } ?> \ No newline at end of file diff --git a/tests/Test/Solar/Auth/Adapter/None.php b/tests/Test/Solar/Auth/Adapter/None.php index 10baf26e..ebf9a3e8 100644 --- a/tests/Test/Solar/Auth/Adapter/None.php +++ b/tests/Test/Solar/Auth/Adapter/None.php @@ -17,7 +17,6 @@ public function testIsLoginValid_true() $this->_fakePostLogin_valid(); $this->assertTrue($this->_auth->isLoginRequest()); $this->assertFalse($this->_auth->isLoginValid()); - $this->_restorePost(); } public function testGetHandle() @@ -26,7 +25,6 @@ public function testGetHandle() $this->assertTrue($this->_auth->isLoginRequest()); $this->assertFalse($this->_auth->isLoginValid()); $this->assertSame($this->_auth->getHandle(), $this->_handle); - $this->_restorePost(); } public function testGetEmail() @@ -35,7 +33,6 @@ public function testGetEmail() $this->assertTrue($this->_auth->isLoginRequest()); $this->assertFalse($this->_auth->isLoginValid()); $this->assertSame($this->_auth->getEmail(), $this->_email); - $this->_restorePost(); } public function testGetMoniker() @@ -44,7 +41,6 @@ public function testGetMoniker() $this->assertTrue($this->_auth->isLoginRequest()); $this->assertFalse($this->_auth->isLoginValid()); $this->assertSame($this->_auth->getMoniker(), $this->_moniker); - $this->_restorePost(); } public function testGetUri() @@ -53,7 +49,6 @@ public function testGetUri() $this->assertTrue($this->_auth->isLoginRequest()); $this->assertFalse($this->_auth->isLoginValid()); $this->assertSame($this->_auth->getUri(), $this->_uri); - $this->_restorePost(); } } ?> \ No newline at end of file diff --git a/tests/Test/Solar/Class/Map.php b/tests/Test/Solar/Class/Map.php index 2ba68262..e14f9509 100644 --- a/tests/Test/Solar/Class/Map.php +++ b/tests/Test/Solar/Class/Map.php @@ -41,6 +41,7 @@ public function testFetch_limited() "Solar_Test_Example" => "$base/Solar/Test/Example.php", "Solar_Test_Example_Exception" => "$base/Solar/Test/Example/Exception.php", "Solar_Test_Example_Exception_CustomCondition" => "$base/Solar/Test/Example/Exception/CustomCondition.php", + "Solar_Test_Example_PageController" => "$base/Solar/Test/Example/PageController.php", "Solar_Test_Exception" => "$base/Solar/Test/Exception.php", "Solar_Test_Exception_Fail" => "$base/Solar/Test/Exception/Fail.php", "Solar_Test_Exception_Skip" => "$base/Solar/Test/Exception/Skip.php", diff --git a/tests/Test/Solar/Controller/Page.php b/tests/Test/Solar/Controller/Page.php new file mode 100644 index 00000000..e011313c --- /dev/null +++ b/tests/Test/Solar/Controller/Page.php @@ -0,0 +1,254 @@ +_request = Solar::factory('Solar_Request'); + $this->_request->load(true); + + // set up the example page controller object + $this->_page = Solar::factory('Solar_Test_Example_PageController'); + } + + public function teardown() + { + parent::teardown(); + } + + public function test__construct() + { + $this->assertInstance($this->_page, 'Solar_Controller_Page'); + } + + public function test__set() + { + try { + $this->_page->foo = 'baz'; + } catch (Exception $e) { + // should *not* have thrown an exception + $this->fail('shoud not have thrown exception: ' . $e->__toString()); + } + + try { + $this->_page->zim = 'dib'; + $this->fail('should have thrown exception on non-existing var'); + } catch (Solar_Controller_Page_Exception_PropertyNotDefined $e) { + // we expect this, do nothing + } + + // done, we need at least one assertion to pass + $this->assertSame($this->_page->foo, 'baz'); + } + + public function test__get() + { + $actual = $this->_page->foo; + $this->assertSame($this->_page->foo, 'bar'); + + try { + $actual = $this->_page->noSuchVar; + $this->fail('should have thrown exception on no-existing var'); + } catch (Solar_Controller_Page_Exception_PropertyNotDefined $e) { + // we expect this, do nothing + } + + } + + public function testFetch() + { + $actual = $this->_page->fetch(); + $expect = 'foo = bar'; + $this->assertSame($actual, $expect); + } + + public function testDisplay() + { + ob_start(); + $this->_page->display(); + $actual = ob_get_clean(); + $expect = 'foo = bar'; + $this->assertSame($actual, $expect); + } + + public function test_hooks() + { + $this->_page->fetch(); + $expect = array( + '_setup' => 1, + '_preRun' => 1, + '_preAction' => 1, + '_postAction' => 1, + '_postRun' => 1, + '_preRender' => 1, + '_postRender' => 1, + ); + $this->assertSame($this->_page->hooks, $expect); + + // fetch again; setup should not trigger this time. + $this->_page->fetch(); + $expect = array( + '_setup' => 1, + '_preRun' => 2, + '_preAction' => 2, + '_postAction' => 2, + '_postRun' => 2, + '_preRender' => 2, + '_postRender' => 2, + ); + $this->assertSame($this->_page->hooks, $expect); + + // fetch **again** with an action that forwards internally; + // the run hooks should hit once, but the action hooks should + // hit twice (once for the orginal method, once for the + // forwarded method). + $this->_page->fetch('test-forward'); + $expect = array( + '_setup' => 1, + '_preRun' => 3, + '_preAction' => 4, + '_postAction' => 4, + '_postRun' => 3, + '_preRender' => 3, + '_postRender' => 3, + ); + $this->assertSame($this->_page->hooks, $expect); + } + + public function testFetch_stringSpecWithAction() + { + $spec = "foo/bar/baz"; + $this->_page->fetch($spec); + + // check the action + $expect = 'foo'; + $this->assertProperty($this->_page, '_action', 'same', $expect); + + // check the pathinfo + $expect = array('bar', 'baz'); + $this->assertProperty($this->_page, '_info', 'same', $expect); + } + + public function testFetch_stringSpecWithoutAction() + { + $spec = "bar/baz"; + $this->_page->fetch($spec); + + // check the action + $expect = 'foo'; + $this->assertProperty($this->_page, '_action', 'same', $expect); + + // check the pathinfo + $expect = array('bar', 'baz'); + $this->assertProperty($this->_page, '_info', 'same', $expect); + } + + public function testFetch_uriSpecWithAction() + { + $spec = Solar::factory('Solar_Uri_Action'); + $spec->setPath('/foo/bar/baz'); + $this->_page->fetch($spec); + + // check the action + $expect = 'foo'; + $this->assertProperty($this->_page, '_action', 'same', $expect); + + // check the pathinfo + $expect = array('bar', 'baz'); + $this->assertProperty($this->_page, '_info', 'same', $expect); + } + + public function testFetch_uriSpecWithoutAction() + { + $spec = Solar::factory('Solar_Uri_Action'); + $spec->setPath('bar/baz'); + $this->_page->fetch($spec); + + // check the action + $expect = 'foo'; + $this->assertProperty($this->_page, '_action', 'same', $expect); + + // check the pathinfo + $expect = array('bar', 'baz'); + $this->assertProperty($this->_page, '_info', 'same', $expect); + } + + public function testFetch_niceActionNames() + { + $expect = "found actionBumpyCase"; + + $actual = $this->_page->fetch("bumpy-case"); + $this->assertSame($actual, $expect); + + $actual = $this->_page->fetch("bumpy_case"); + $this->assertSame($actual, $expect); + + $actual = $this->_page->fetch("bumpyCase"); + $this->assertSame($actual, $expect); + + $actual = $this->_page->fetch("BumpyCase"); + $this->assertSame($actual, $expect); + } + + public function testFetch_noRelatedView() + { + try { + $this->_page->fetch("no-related-view"); + $this->fail('should have thrown TemplateNotFound exception'); + } catch (Solar_View_Exception_TemplateNotFound $e) { + // this is expected + } + + // need an assertion to pass + $this->assertTrue(true); + } + + public function testFetch_actionNotFound() + { + $this->_page->setActionDefault('notFound'); + try { + $this->_page->fetch(); + $this->fail('should have thrown an ActionNotFound exception'); + } catch (Solar_Controller_Page_Exception_ActionNotFound $e) { + // do nothing, we expected this + } + + // need at least one assertion to pass + $this->assertTrue(true); + } +} +?> \ No newline at end of file diff --git a/tests/Test/Solar/Flash.php b/tests/Test/Solar/Flash.php deleted file mode 100644 index f696587f..00000000 --- a/tests/Test/Solar/Flash.php +++ /dev/null @@ -1,70 +0,0 @@ -_flash = Solar::factory('Solar_Flash'); - } - - public function teardown() - { - // make sure $_SESSION values don't pass into the next test - $this->_flash->reset(); - } - - public function test__construct() - { - $this->assertInstance($this->_flash, 'Solar_Flash'); - } - - public function testSet() - { - $this->_flash->set('foo', 'bar'); - $actual = $_SESSION['Solar_Flash']['Solar']['foo']; - $expect = 'bar'; - $this->assertSame($actual, $expect); - } - - public function testAdd() - { - $this->_flash->add('foo', 'bar'); - $this->_flash->add('foo', 'baz'); - $this->_flash->add('foo', 'zim'); - - $actual = $_SESSION['Solar_Flash']['Solar']['foo']; - $expect = array('bar', 'baz', 'zim'); - $this->assertSame($actual, $expect); - } - - public function testGet() - { - // set the value - $this->_flash->set('foo', 'bar'); - $actual = $_SESSION['Solar_Flash']['Solar']['foo']; - $expect = 'bar'; - $this->assertSame($actual, $expect); - - // read the value - $actual = $this->_flash->get('foo'); - $this->assertSame($actual, $expect); - - // should have removed it after reading - $actual = empty($_SESSION['Solar_Flash']['Solar']['foo']); - $this->assertTrue($actual); - } -} -?> \ No newline at end of file diff --git a/tests/Test/Solar/Log/Adapter/Multi.php b/tests/Test/Solar/Log/Adapter/Multi.php index 3af355f0..9f356a0b 100644 --- a/tests/Test/Solar/Log/Adapter/Multi.php +++ b/tests/Test/Solar/Log/Adapter/Multi.php @@ -8,15 +8,19 @@ class Test_Solar_Log_Adapter_Multi extends Test_Solar_Log_Adapter { 'adapters' => array( array( 'adapter' => 'Solar_Log_Adapter_File', - 'events' => 'debug', - 'file' => '/tmp/test_solar_log_adapter_multi.debug.log', - 'format' => '%e %m', + 'config' => array( + 'events' => 'debug', + 'file' => '/tmp/test_solar_log_adapter_multi.debug.log', + 'format' => '%e %m', + ), ), array( 'adapter' => 'Solar_Log_Adapter_File', - 'events' => 'info, notice', - 'file' => '/tmp/test_solar_log_adapter_multi.other.log', - 'format' => '%e %m', + 'config' => array( + 'events' => 'info, notice', + 'file' => '/tmp/test_solar_log_adapter_multi.other.log', + 'format' => '%e %m', + ), ), ), ); @@ -24,14 +28,14 @@ class Test_Solar_Log_Adapter_Multi extends Test_Solar_Log_Adapter { public function setup() { parent::setup(); - @unlink($this->_config['adapters'][0]['file']); - @unlink($this->_config['adapters'][1]['file']); + @unlink($this->_config['adapters'][0]['config']['file']); + @unlink($this->_config['adapters'][1]['config']['file']); } public function teardown() { - @unlink($this->_config['adapters'][0]['file']); - @unlink($this->_config['adapters'][1]['file']); + @unlink($this->_config['adapters'][0]['config']['file']); + @unlink($this->_config['adapters'][1]['config']['file']); parent::teardown(); } @@ -43,12 +47,12 @@ public function testSave_recognized() $this->_log->save($class, 'notice', 'note this message'); // the debug log - $actual = file_get_contents($this->_config['adapters'][0]['file']); + $actual = file_get_contents($this->_config['adapters'][0]['config']['file']); $expect = "debug a debug description\n"; $this->assertSame($actual, $expect); // the other log - $actual = file_get_contents($this->_config['adapters'][1]['file']); + $actual = file_get_contents($this->_config['adapters'][1]['config']['file']); $expect = "info some information\nnotice note this message\n"; $this->assertSame($actual, $expect); } @@ -61,12 +65,12 @@ public function testSave_notRecognized() $this->_log->save($class, 'qwert', 'not recognized'); // the debug log - $actual = file_get_contents($this->_config['adapters'][0]['file']); + $actual = file_get_contents($this->_config['adapters'][0]['config']['file']); $expect = "debug recognized\n"; $this->assertSame($actual, $expect); // the other log - $actual = file_get_contents($this->_config['adapters'][1]['file']); + $actual = file_get_contents($this->_config['adapters'][1]['config']['file']); $expect = "info recognized\n"; $this->assertSame($actual, $expect); } diff --git a/tests/Test/Solar/Markdown/Wiki/Header.php b/tests/Test/Solar/Markdown/Wiki/Header.php index c34ec435..fea58e0b 100644 --- a/tests/Test/Solar/Markdown/Wiki/Header.php +++ b/tests/Test/Solar/Markdown/Wiki/Header.php @@ -122,5 +122,29 @@ public function testRender_subSection() $actual = $this->_markdown->transform($source); $this->assertSame($actual, $expect); } + + public function testRender_Atx() + { + $source = array(); + $source[] = "foo bar"; + $source[] = "# Title"; + $source[] = "## Super-Section"; + $source[] = "### Section"; + $source[] = "#### Sub-Section"; + $source[] = "baz dib"; + $source = implode("\n", $source); + + $expect = array(); + $expect[] = "foo bar"; + $expect[] = "

    Title

    \n"; + $expect[] = "

    Super-Section

    \n"; + $expect[] = "

    Section

    \n"; + $expect[] = "

    Sub-Section

    \n"; + $expect[] = "baz dib"; + $expect = implode("\n", $expect); + + $actual = $this->_markdown->transform($source); + $this->assertSame($actual, $expect); + } } ?> \ No newline at end of file diff --git a/tests/Test/Solar/Request.php b/tests/Test/Solar/Request.php new file mode 100644 index 00000000..4099045c --- /dev/null +++ b/tests/Test/Solar/Request.php @@ -0,0 +1,258 @@ + array(), + '_POST' => array(), + '_COOKIE' => array(), + '_SERVER' => array(), + '_ENV' => array(), + '_FILES' => array(), + ); + + public function __construct($config = null) + { + parent::__construct($config); + } + + public function __destruct() + { + parent::__destruct(); + } + + protected function _getRequest() + { + $config = array('reload' => true); + return Solar::factory('Solar_Request', $config); + } + + public function setup() + { + parent::setup(); + + // save original values + $keys = array_keys($this->_orig); + foreach ($keys as $var) { + if (isset($GLOBALS[$var])) { + $this->_orig[$var] = $GLOBALS[$var]; + } + } + } + + public function teardown() + { + parent::teardown(); + + // return original values + $keys = array_keys($this->_orig); + foreach ($keys as $var) { + $this->_orig[$var] = $GLOBALS[$var] = $this->_orig[$var]; + } + } + + public function test__construct() + { + $request = $this->_getRequest(); + $this->assertInstance($request, 'Solar_Request'); + } + + public function testGet() + { + $_GET['foo'] = 'bar'; + $request = $this->_getRequest(); + + // get a key + $actual = $request->get('foo'); + $this->assertSame($actual, 'bar'); + + // get a non-existent key + $actual = $request->get('baz'); + $this->assertNull($actual); + + // get a non-existent key with default value + $actual = $request->get('baz', 'dib'); + $this->assertSame($actual, 'dib'); + } + + public function testPost() + { + $_POST['foo'] = 'bar'; + $request = $this->_getRequest(); + + // get a key + $actual = $request->post('foo'); + $this->assertSame($actual, 'bar'); + + // get a non-existent key + $actual = $request->post('baz'); + $this->assertNull($actual); + + // get a non-existent key with default value + $actual = $request->post('baz', 'dib'); + $this->assertSame($actual, 'dib'); + } + + public function testCookie() + { + $_COOKIE['foo'] = 'bar'; + $request = $this->_getRequest(); + + // get a key + $actual = $request->cookie('foo'); + $this->assertSame($actual, 'bar'); + + // get a non-existent key + $actual = $request->cookie('baz'); + $this->assertNull($actual); + + // get a non-existent key with default value + $actual = $request->cookie('baz', 'dib'); + $this->assertSame($actual, 'dib'); + } + + public function testEnv() + { + $_ENV['foo'] = 'bar'; + $request = $this->_getRequest(); + + // env a key + $actual = $request->env('foo'); + $this->assertSame($actual, 'bar'); + + // env a non-existent key + $actual = $request->env('baz'); + $this->assertNull($actual); + + // env a non-existent key with default value + $actual = $request->env('baz', 'dib'); + $this->assertSame($actual, 'dib'); + } + + public function testServer() + { + $_SERVER['foo'] = 'bar'; + $request = $this->_getRequest(); + + // get a key + $actual = $request->server('foo'); + $this->assertSame($actual, 'bar'); + + // get a non-existent key + $actual = $request->server('baz'); + $this->assertNull($actual); + + // get a non-existent key with default value + $actual = $request->server('baz', 'dib'); + $this->assertSame($actual, 'dib'); + } + + public function testFiles() + { + $_FILES['foo'] = 'bar'; + $request = $this->_getRequest(); + + // get a key + $actual = $request->files('foo'); + $this->assertSame($actual, 'bar'); + + // get a non-existent key + $actual = $request->files('baz'); + $this->assertNull($actual); + + // get a non-existent key with default value + $actual = $request->files('baz', 'dib'); + $this->assertSame($actual, 'dib'); + } + + public function testHttp() + { + $_SERVER['HTTP_FOO'] = 'bar'; + $request = $this->_getRequest(); + + // get a key + $actual = $request->http('Foo'); + $this->assertSame($actual, 'bar'); + + // get a non-existent key + $actual = $request->http('Baz'); + $this->assertNull($actual); + + // get a non-existent key with default value + $actual = $request->http('Baz', 'dib'); + $this->assertSame($actual, 'dib'); + } + + public function testIsGet() + { + $_SERVER['REQUEST_METHOD'] = 'GET'; + $request = $this->_getRequest(); + $this->assertTrue($request->isGet()); + + $_SERVER['REQUEST_METHOD'] = 'XXX'; + $request = $this->_getRequest(); + $this->assertFalse($request->isGet()); + } + + public function testIsPost() + { + $_SERVER['REQUEST_METHOD'] = 'POST'; + $request = $this->_getRequest(); + $this->assertTrue($request->isPost()); + + $_SERVER['REQUEST_METHOD'] = 'XXX'; + $request = $this->_getRequest(); + $this->assertFalse($request->isPost()); + } + + public function testIsPut() + { + $_SERVER['REQUEST_METHOD'] = 'PUT'; + $request = $this->_getRequest(); + $this->assertTrue($request->isPut()); + + $_SERVER['REQUEST_METHOD'] = 'XXX'; + $request = $this->_getRequest(); + $this->assertFalse($request->isPut()); + } + + public function testIsDelete() + { + $_SERVER['REQUEST_METHOD'] = 'DELETE'; + $request = $this->_getRequest(); + $this->assertTrue($request->isDelete()); + + $_SERVER['REQUEST_METHOD'] = 'XXX'; + $request = $this->_getRequest(); + $this->assertFalse($request->isDelete()); + } + + public function testIsXml() + { + $_SERVER['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'; + $request = $this->_getRequest(); + $this->assertTrue($request->isXml()); + + + $_SERVER['HTTP_X_REQUESTED_WITH'] = 'XXX'; + $request = $this->_getRequest(); + $this->assertFalse($request->isXml()); + } + + public function testLoad() + { + // test loading the first time + $request = Solar::factory('Solar_Request', array('reload' => true)); + + // test changing the vars, should not be reloaded + $_GET['foo'] = 'bar'; + $actual = $request->get('foo'); + $this->assertNull($actual); + + // now reload, should pick up the changed $_GET + $request = Solar::factory('Solar_Request', array('reload' => true)); + $actual = $request->get('foo'); + $this->assertSame($actual, 'bar'); + } +} +?> \ No newline at end of file diff --git a/tests/Test/Solar/Session.php b/tests/Test/Solar/Session.php new file mode 100644 index 00000000..35c413fb --- /dev/null +++ b/tests/Test/Solar/Session.php @@ -0,0 +1,169 @@ +_class = get_class($this); + } + + public function __destruct() + { + parent::__destruct(); + } + + public function setup() + { + parent::setup(); + $this->_session = Solar::factory('Solar_Session'); + $this->_session->setClass($this->_class); + } + + public function teardown() + { + // make sure values don't pass into the next test + $this->_session->resetAll(); + } + + public function test__construct() + { + $this->assertInstance($this->_session, 'Solar_Session'); + } + + public function testSet() + { + $this->_session->set('foo', 'bar'); + $actual = $_SESSION[$this->_class]['foo']; + $expect = 'bar'; + $this->assertSame($actual, $expect); + } + + public function testAdd() + { + $this->_session->add('foo', 'bar'); + $this->_session->add('foo', 'baz'); + $this->_session->add('foo', 'zim'); + + $actual = $_SESSION[$this->_class]['foo']; + $expect = array('bar', 'baz', 'zim'); + $this->assertSame($actual, $expect); + } + + public function testGet() + { + // set the value + $this->_session->set('foo', 'bar'); + $actual = $_SESSION[$this->_class]['foo']; + $expect = 'bar'; + $this->assertSame($actual, $expect); + + // read the value + $actual = $this->_session->get('foo'); + $this->assertSame($actual, $expect); + + // ask for nonexistent value and get default instead + $actual = $this->_session->get('baz', 'dib'); + $expect = 'dib'; + $this->assertSame($actual, $expect); + } + + public function testReset() + { + // set the value + $this->_session->set('foo', 'bar'); + $actual = $_SESSION[$this->_class]['foo']; + $expect = 'bar'; + $this->assertSame($actual, $expect); + + // now reset + $this->_session->reset(); + $actual = $_SESSION[$this->_class]; + $expect = array(); + $this->assertSame($actual, $expect); + } + + public function testSetFlash() + { + $this->_session->setFlash('foo', 'bar'); + $actual = $_SESSION['Solar_Session']['flash'][$this->_class]['foo']; + $expect = 'bar'; + $this->assertSame($actual, $expect); + } + + public function testAddFlash() + { + $this->_session->addFlash('foo', 'bar'); + $this->_session->addFlash('foo', 'baz'); + $this->_session->addFlash('foo', 'zim'); + + $actual = $_SESSION['Solar_Session']['flash'][$this->_class]['foo']; + $expect = array('bar', 'baz', 'zim'); + $this->assertSame($actual, $expect); + } + + public function testGetFlash() + { + // set the value + $this->_session->setFlash('foo', 'bar'); + $actual = $_SESSION['Solar_Session']['flash'][$this->_class]['foo']; + $expect = 'bar'; + $this->assertSame($actual, $expect); + + // read the value + $actual = $this->_session->getFlash('foo'); + $this->assertSame($actual, $expect); + + // should have removed it after reading + $actual = empty($_SESSION['Solar_Session']['flash'][$this->_class]['foo']); + $this->assertTrue($actual); + } + + public function testResetFlash() + { + // set the value + $this->_session->setFlash('foo', 'bar'); + $actual = $_SESSION['Solar_Session']['flash'][$this->_class]['foo']; + $expect = 'bar'; + $this->assertSame($actual, $expect); + + // now reset + $this->_session->resetFlash(); + $actual = $_SESSION['Solar_Session']['flash'][$this->_class]; + $expect = array(); + $this->assertSame($actual, $expect); + } + + public function testResetAll() + { + // set the value + $this->_session->set('foo', 'bar'); + $actual = $_SESSION[$this->_class]['foo']; + $expect = 'bar'; + $this->assertSame($actual, $expect); + + // set the flash value + $this->_session->setFlash('foo', 'bar'); + $actual = $_SESSION['Solar_Session']['flash'][$this->_class]['foo']; + $expect = 'bar'; + $this->assertSame($actual, $expect); + + // reset all + $this->_session->resetAll(); + $expect = array(); + + // should be blank in store ... + $actual = $_SESSION[$this->_class]; + $this->assertSame($actual, $expect); + + // ... and in flash. + $actual = $_SESSION['Solar_Session']['flash'][$this->_class]; + $this->assertSame($actual, $expect); + } + +} +?> \ No newline at end of file diff --git a/tests/Test/Solar/Sql/Adapter.php b/tests/Test/Solar/Sql/Adapter.php index 44f7524f..e3f16bd4 100644 --- a/tests/Test/Solar/Sql/Adapter.php +++ b/tests/Test/Solar/Sql/Adapter.php @@ -211,12 +211,12 @@ public function testSelect_assoc() $this->_insertData(); $actual = $this->_sql->select('assoc', "SELECT name, id FROM $this->_table_name ORDER BY id"); $expect = array( - 'Foo' => array('id' => '1'), - 'Bar' => array('id' => '2'), - 'Baz' => array('id' => '3'), - 'Dib' => array('id' => '4'), - 'Zim' => array('id' => '5'), - 'Gir' => array('id' => '6'), + 'Foo' => array('id' => '1', 'name' => 'Foo'), + 'Bar' => array('id' => '2', 'name' => 'Bar'), + 'Baz' => array('id' => '3', 'name' => 'Baz'), + 'Dib' => array('id' => '4', 'name' => 'Dib'), + 'Zim' => array('id' => '5', 'name' => 'Zim'), + 'Gir' => array('id' => '6', 'name' => 'Gir'), ); $this->assertSame($actual, $expect); } diff --git a/tests/Test/Solar/Struct.php b/tests/Test/Solar/Struct.php index cf646e05..3d97300e 100644 --- a/tests/Test/Solar/Struct.php +++ b/tests/Test/Solar/Struct.php @@ -149,5 +149,26 @@ public function test_iterator() $this->assertSame($val, $expect[$key]); } } + + public function test__construct_dataNotArray() + { + $struct = Solar::factory( + 'Solar_Struct', + array('data' => null) + ); + $this->assertSame($struct->toArray(), array()); + + $struct = Solar::factory( + 'Solar_Struct', + array('data' => '') + ); + $this->assertSame($struct->toArray(), array()); + + $struct = Solar::factory( + 'Solar_Struct', + array('data' => 0) + ); + $this->assertSame($struct->toArray(), array()); + } } ?> \ No newline at end of file diff --git a/tests/Test/Solar/Uri.php b/tests/Test/Solar/Uri.php index 123cae74..d7b13c09 100644 --- a/tests/Test/Solar/Uri.php +++ b/tests/Test/Solar/Uri.php @@ -10,31 +10,26 @@ class Test_Solar_Uri extends Solar_Test { protected $_class = 'Solar_Uri'; + protected $_request; + public function __construct($config = null) { parent::__construct($config); // when running from the command line, these elements are empty. // add them so that web-like testing can occur. - $this->_server = $_SERVER; - $this->_get = $_GET; - $_SERVER['HTTP_HOST'] = 'example.com'; - $_SERVER['SCRIPT_NAME'] = '/path/to/index.php'; - $_SERVER['PATH_INFO'] = '/appname/action'; - $_SERVER['QUERY_STRING'] = 'foo=bar&baz=dib'; - $_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'] - . $_SERVER['PATH_INFO'] - . '?' . $_SERVER['QUERY_STRING']; - - // emulate $_GET vars from the URI - parse_str($_SERVER['QUERY_STRING'], $_GET); - } - - public function __destruct() - { - $_GET = $this->_get; - $_SERVER = $this->_server; - parent::__destruct(); + $this->_request = Solar::factory('Solar_Request'); + $this->_request->server['HTTP_HOST'] = 'example.com'; + $this->_request->server['SCRIPT_NAME'] = '/path/to/index.php'; + $this->_request->server['PATH_INFO'] = '/appname/action'; + $this->_request->server['QUERY_STRING'] = 'foo=bar&baz=dib'; + $this->_request->server['REQUEST_URI'] = $this->_request->server['SCRIPT_NAME'] + . $this->_request->server['PATH_INFO'] + . '?' + . $this->_request->server['QUERY_STRING']; + + // emulate GET vars from the URI + parse_str($this->_request->server['QUERY_STRING'], $this->_request->get); } public function setup() @@ -88,7 +83,6 @@ public function testSet() // import the URI spec and test that it imported properly $this->_uri->set($spec); - // $assert->setLabel('Initial import'); $this->assertSame($this->_uri->scheme, $scheme); $this->assertSame($this->_uri->host, $host); $this->assertSame($this->_uri->port, $port); @@ -99,7 +93,6 @@ public function testSet() // do this to make sure there are no translation errors. $spec = $this->_uri->fetch(true); $this->_uri->set($spec); - // $assert->setLabel('Retranslation'); $this->assertSame($this->_uri->scheme, $scheme); $this->assertSame($this->_uri->host, $host); $this->assertSame($this->_uri->port, $port); @@ -143,17 +136,14 @@ public function testFetch() $this->_uri->set($expect_full); // full fetch - // $assert->setLabel('full'); $this->assertSame($this->_uri->fetch(true), $expect_full); // partial fetch - // $assert->setLabel('part'); - $this->assertSame($this->_uri->fetch(), $expect_part); + $this->assertSame($this->_uri->fetch(false), $expect_part); } public function testQuick() { - // partial $expect = '/path/to/index.php?foo=bar'; $actual = $this->_uri->quick("http://example.com$expect"); diff --git a/tests/Test/Solar/View/Helper/Anchor.php b/tests/Test/Solar/View/Helper/Anchor.php index 4084acb6..5463366f 100644 --- a/tests/Test/Solar/View/Helper/Anchor.php +++ b/tests/Test/Solar/View/Helper/Anchor.php @@ -4,6 +4,14 @@ class Test_Solar_View_Helper_Anchor extends Test_Solar_View_Helper { + public function setup() + { + parent::setup(); + // forcibly reset the request environment + $request = Solar::factory('Solar_Request'); + $request->load(true); + } + public function testAnchor_hrefFromString() { $actual = $this->_view->anchor('/path/to/script.php'); diff --git a/tests/Test/Solar/View/Helper/Form.php b/tests/Test/Solar/View/Helper/Form.php index db5c3c38..b5d96dc7 100644 --- a/tests/Test/Solar/View/Helper/Form.php +++ b/tests/Test/Solar/View/Helper/Form.php @@ -12,31 +12,27 @@ class Test_Solar_View_Helper_Form extends Solar_Test { protected $_get; + protected $_request; + public function __construct($config = null) { parent::__construct($config); + $this->_request = Solar::factory('Solar_Request'); + $this->_request->load(true); // when running from the command line, these elements are empty. // add them so that web-like testing can occur. - $this->_server = $_SERVER; - $this->_get = $_GET; - $_SERVER['HTTP_HOST'] = 'example.com'; - $_SERVER['SCRIPT_NAME'] = '/path/to/index.php'; - $_SERVER['PATH_INFO'] = '/appname/action'; - $_SERVER['QUERY_STRING'] = 'foo=bar&baz=dib'; - $_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'] - . $_SERVER['PATH_INFO'] - . '?' . $_SERVER['QUERY_STRING']; + $this->_request->server['HTTP_HOST'] = 'example.com'; + $this->_request->server['SCRIPT_NAME'] = '/path/to/index.php'; + $this->_request->server['PATH_INFO'] = '/appname/action'; + $this->_request->server['QUERY_STRING'] = 'foo=bar&baz=dib'; + $this->_request->server['REQUEST_URI'] = $this->_request->server['SCRIPT_NAME'] + . $this->_request->server['PATH_INFO'] + . '?' + . $this->_request->server['QUERY_STRING']; - // emulate $_GET vars from the URI - parse_str($_SERVER['QUERY_STRING'], $_GET); - } - - public function __destruct() - { - $_GET = $this->_get; - $_SERVER = $this->_server; - parent::__destruct(); + // emulate GET vars from the URI + parse_str($this->_request->server['QUERY_STRING'], $this->_request->get); } public function setup() @@ -112,7 +108,7 @@ public function testForm_array() $helper = $this->_view->form($attribs); $expect = array( - 'action' => $_SERVER['REQUEST_URI'], + 'action' => $this->_request->server['REQUEST_URI'], 'method' => 'post', 'enctype' => 'multipart/form-data', 'foo' => 'bar', @@ -155,7 +151,7 @@ public function testForm_solarFormObject() public function testSetAttrib() { $expect = array( - 'action' => $_SERVER['REQUEST_URI'], + 'action' => $this->_request->server['REQUEST_URI'], 'method' => 'post', 'enctype' => 'multipart/form-data', 'foo' => 'bar', @@ -500,7 +496,7 @@ public function testReset() // test everything :-( $expect = array( - 'action' => $_SERVER['REQUEST_URI'], + 'action' => $this->_request->server['REQUEST_URI'], 'method' => 'post', 'enctype' => 'multipart/form-data', ); diff --git a/tests/Test/Solar/View/Helper/PublicHref.php b/tests/Test/Solar/View/Helper/PublicHref.php index cdffa17f..e3f36bd7 100644 --- a/tests/Test/Solar/View/Helper/PublicHref.php +++ b/tests/Test/Solar/View/Helper/PublicHref.php @@ -24,10 +24,12 @@ public function testPublicHref_fromUri() public function testPublicHref_raw() { + // should escape $actual = $this->_view->publicHref('/path/to/'); $expect = '/public/path/to/<file>'; $this->assertSame($actual, $expect); + // should not escape $actual = $this->_view->publicHref('/path/to/', true); $expect = '/public/path/to/'; $this->assertSame($actual, $expect); diff --git a/tests/Test/Solar/View/Helper/Style.php b/tests/Test/Solar/View/Helper/Style.php index 4ff84f44..befe0c27 100644 --- a/tests/Test/Solar/View/Helper/Style.php +++ b/tests/Test/Solar/View/Helper/Style.php @@ -14,13 +14,11 @@ public function testStyle() public function testStyle_Remote() { - $actual = $this->_view->style('styles.css'); - $actual .= $this->_view->style('http://example.com/styles.css'); + $actual = $this->_view->style('http://something.com/path/to/styles.css'); $expect = ''; - $expect .= ''; + . '@import url("http://something.com/path/to/styles.css");'; + $this->assertSame($actual, $expect); } diff --git a/tests/Test/Solar/View/Helper/TypekeyLink.php b/tests/Test/Solar/View/Helper/TypekeyLink.php new file mode 100644 index 00000000..9ab78e46 --- /dev/null +++ b/tests/Test/Solar/View/Helper/TypekeyLink.php @@ -0,0 +1,41 @@ +_request = Solar::factory('Solar_Request'); + $this->_request->load(true); + + // when running from the command line, these elements are empty. + // add them so that web-like testing can occur. + $this->_request->server['HTTP_HOST'] = 'example.com'; + $this->_request->server['SCRIPT_NAME'] = '/index.php'; + $this->_request->server['PATH_INFO'] = '/control/action'; + $this->_request->server['QUERY_STRING'] = 'foo=bar&baz=dib&submit=zim'; + $this->_request->server['REQUEST_URI'] = $this->_request->server['SCRIPT_NAME'] + . $this->_request->server['PATH_INFO'] + . '?' + . $this->_request->server['QUERY_STRING']; + + // emulate GET vars from the URI + parse_str($this->_request->server['QUERY_STRING'], $this->_request->get); + } + + public function testTypekeyLink() + { + // note that the 'submit' key should have been removed + $expect = 'Sign In'; + + $actual = $this->_view->typekeyLink('Sign In'); + $this->assertSame($actual, $expect); + } +} +?> \ No newline at end of file diff --git a/tests/config.inc.php b/tests/config.inc.php index 200098f1..1170eb7d 100644 --- a/tests/config.inc.php +++ b/tests/config.inc.php @@ -24,6 +24,8 @@ 'host' => '127.0.0.1', ); +$config['Solar_Auth_Adapter_TypeKey']['token'] = 'foobarbaz'; + $config['Test_Solar_Cache_Adapter_Apc']['run'] = false; $config['Test_Solar_Cache_Adapter_Eaccellerator']['run'] = false; $config['Test_Solar_Cache_Adapter_Xcache']['run'] = false; diff --git a/tests/test.php b/tests/test.php index 14839e7a..f0f2eebb 100755 --- a/tests/test.php +++ b/tests/test.php @@ -1,20 +1,20 @@ \n"; + ob_implicit_flush(); + set_time_limit(600); +} + +/** + * * Now we can proceed with the actual testing. - * + * */ $config = dirname(__FILE__) . '/config.inc.php'; Solar::start($config); @@ -55,7 +68,12 @@ $suite = Solar::factory('Solar_Test_Suite', $config); // run the test series -$series = isset($argv[1]) ? trim($argv[1]) : null; +$series = null; +if (isset($argv[1])) { + $series = $argv[1]; +} elseif (isset($_SERVER['QUERY_STRING']) && strlen($_SERVER['QUERY_STRING']) > 0) { + $series = $_SERVER['QUERY_STRING']; +} $suite->run($series); // put the include_path back