diff options
99 files changed, 7666 insertions, 301 deletions
@@ -7,9 +7,13 @@ FEATURES this would allow to save the logs later by big bulk - Purge log_ when archiving is done - Provide Config file feature for plugins (works as the translation system) +- Translate exception + + BUGS ==== +- the referer URLs with host registered as main_url or alias_url should NOT be counted as referer - the token md5 generation doesn't check that the md5 generated is unique, but it should (the field is unique in the database) - if elements from the config file are deleted, bug without any notice or warning diff --git a/config/config.ini.php b/config/config.ini.php index f1d7d1a5c4..d1ebc77470 100755 --- a/config/config.ini.php +++ b/config/config.ini.php @@ -21,6 +21,8 @@ default = en [Plugins] enabled[] = Login +enabled[] = UsersManager +enabled[] = SitesManager enabled[] = UserSettings enabled[] = Actions @@ -87,7 +89,7 @@ cookie_name = piwik_visitor [log] ; normal messages -logger_message[] = screen +;logger_message[] = screen ;logger_message[] = database ;logger_message[] = file @@ -124,7 +126,10 @@ log = logs/ [smarty] -template_dir = core/views/scripts +template_dir[] = plugins +template_dir[] = themes/default compile_dir = tmp/templates_c config_dir = tmp/configs -cache_dir = tmp/cache
\ No newline at end of file +cache_dir = tmp/cache +error_reporting = E_ALL|E_NOTICE +debugging = TRUE
\ No newline at end of file @@ -42,6 +42,7 @@ require_once "Timer.php"; require_once "Piwik.php"; +require_once "API/APIable.php"; require_once "Access.php"; require_once "Auth.php"; require_once "API/Proxy.php"; @@ -49,114 +50,14 @@ require_once "Site.php"; require_once "Translate.php"; require_once "Url.php"; require_once "Controller.php"; +require_once "FrontController.php"; $controller = new Piwik_FrontController; $controller->init(); $controller->dispatch(); $controller->end(); -exit; - -class Piwik_FrontController -{ - function dispatch() - { - $defaultModule = 'Home'; - - // load the module requested - $module = Piwik_Common::getRequestVar('module', $defaultModule, 'string'); - - if(ctype_alnum($module)) - { - $moduleController = PIWIK_PLUGINS_PATH . "/" . $module . "/Controller.php"; - if(is_readable($moduleController)) - { - require_once $moduleController; - - $controllerClassName = "Piwik_".$module."_Controller"; - - $controller = new $controllerClassName; - - $defaultAction = $controller->getDefaultAction(); - $action = Piwik_Common::getRequestVar('action', $defaultAction, 'string'); - - try{ - $controller->$action(); - } catch(Piwik_Access_NoAccessException $e) { - Piwik::log("NO ACCESS EXCEPTION =>"); - Piwik_PostEvent('FrontController.NoAccessException', $e); - } - } - else - { - throw new Exception("Module controller $moduleController not found!"); - } - } - else - { - throw new Exception("Invalid module name"); - } - - } - - function end() - { - - Piwik::displayZendProfiler(); - Piwik::printMemoryUsage(); - Piwik::printQueryCount(); -// Piwik::uninstall(); - echo $this->timer; - - } - - function init() - { - $this->timer = new Piwik_Timer; - - //move into a init() method - Piwik::createConfigObject(); - - // database object - Piwik::createDatabaseObject(); - - // Create the log objects - Piwik::createLogObject(); - - Piwik::printMemoryUsage('Start program'); - //TODO move all DB related methods in a DB static class - - //Piwik::createDatabase(); - //Piwik::createDatabaseObject(); - - $doNotDrop = array( - Piwik::prefixTable('log_visit'), - Piwik::prefixTable('log_link_visit_action'), - Piwik::prefixTable('log_action'), - Piwik::prefixTable('log_profiling'), - Piwik::prefixTable('archive'), - ); - - Piwik::dropTables($doNotDrop); - Piwik::createTables(); - - // load plugins - Piwik_PluginsManager::getInstance()->setInstallPlugins(); - //TODO plugins install to handle in a better way - Piwik::loadPlugins(); - - // Create auth object - Zend_Registry::set('auth', $authAdapter = new Piwik_Auth()); - - // Setup the auth object - Piwik_PostEvent('FrontController.authSetCredentials'); - - // Perform the authentication query, saving the result - $access = new Piwik_Access($authAdapter); - Zend_Registry::set('access', $access); - Zend_Registry::get('access')->loadAccess(); - } -} +exit; // //main(); diff --git a/libs/PEAR.php b/libs/PEAR.php index f14d8a06f1..b0f224ca7b 100755 --- a/libs/PEAR.php +++ b/libs/PEAR.php @@ -276,7 +276,7 @@ class PEAR * @access public * @return bool true if parameter is an error */ - function isError($data, $code = null) + static function isError($data, $code = null) { if ($data instanceof PEAR_Error) { if (is_null($code)) { diff --git a/libs/Smarty/Smarty.class.php b/libs/Smarty/Smarty.class.php index f05e0dadeb..db74016235 100755 --- a/libs/Smarty/Smarty.class.php +++ b/libs/Smarty/Smarty.class.php @@ -1120,9 +1120,6 @@ class Smarty { static $_cache_info = array(); - $_smarty_old_error_level = $this->debugging ? error_reporting() : error_reporting(isset($this->error_reporting) - ? $this->error_reporting : error_reporting() & ~E_NOTICE); - if (!$this->debugging && $this->debugging_ctrl == 'URL') { $_query_string = $this->request_use_auto_globals ? $_SERVER['QUERY_STRING'] : $GLOBALS['HTTP_SERVER_VARS']['QUERY_STRING']; if (@strstr($_query_string, $this->_smarty_debug_id)) { @@ -1218,12 +1215,10 @@ class Smarty } else { echo $_smarty_results; } - error_reporting($_smarty_old_error_level); // restore initial cache_info $this->_cache_info = array_pop($_cache_info); return true; } else { - error_reporting($_smarty_old_error_level); // restore initial cache_info $this->_cache_info = array_pop($_cache_info); return $_smarty_results; @@ -1303,10 +1298,8 @@ class Smarty require_once(SMARTY_CORE_DIR . 'core.display_debug_console.php'); echo smarty_core_display_debug_console($_params, $this); } - error_reporting($_smarty_old_error_level); return; } else { - error_reporting($_smarty_old_error_level); if (isset($_smarty_results)) { return $_smarty_results; } } } diff --git a/libs/ezcomponents/Url/CREDITS b/libs/ezcomponents/Url/CREDITS new file mode 100644 index 0000000000..2cc9fc2734 --- /dev/null +++ b/libs/ezcomponents/Url/CREDITS @@ -0,0 +1,16 @@ +CREDITS +======= + +eZ Components team +------------------ + +- Sergey Alexeev +- Sebastian Bergmann +- Jan Borsodi +- Raymond Bosman +- Frederik Holljen +- Kore Nordmann +- Derick Rethans +- Vadym Savchuk +- Tobias Schlitt +- Alexandru Stanoi diff --git a/libs/ezcomponents/Url/ChangeLog b/libs/ezcomponents/Url/ChangeLog new file mode 100644 index 0000000000..2b48bbc7a5 --- /dev/null +++ b/libs/ezcomponents/Url/ChangeLog @@ -0,0 +1,41 @@ +1.1 - Monday 02 July 2007 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Documentation updates and fixes. + + +1.1rc1 - Monday 25 June 2007 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Fixed issue #11012: basedir can be now specified as an absolute path also. +- Documentation updates and fixes. + + +1.1beta1 - Monday 07 May 2007 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Implemented feature request #10109: Added support for delayed initialization + for ezcUrlConfiguration. +- Implemented feature request #10444: Added removeOrderedParameter() and + removeUnorderedParameter() methods to ezcUrlConfiguration. Also exposed the + url configuration as a property of ezcUrl objects. + + +1.0 - Monday 18 December 2006 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Removed the defaultConfiguration() method from ezcUrlConfiguration. +- Added missing docblocks. + + +1.0beta2 - Monday 20 November 2006 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Implemented feature #9240: Added ezcUrlCreator class to allow + working with predefined locations (eg. images, design); + + +1.0beta1 - Tuesday 24 October 2006 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Initial release of this package. diff --git a/libs/ezcomponents/Url/DESCRIPTION b/libs/ezcomponents/Url/DESCRIPTION new file mode 100644 index 0000000000..988780b39d --- /dev/null +++ b/libs/ezcomponents/Url/DESCRIPTION @@ -0,0 +1,2 @@ +The Url package provides basic operations to handle urls (parse, build, +get/set path, get/set query). diff --git a/libs/ezcomponents/Url/design/class_diagram.png b/libs/ezcomponents/Url/design/class_diagram.png Binary files differnew file mode 100644 index 0000000000..27ab1cec84 --- /dev/null +++ b/libs/ezcomponents/Url/design/class_diagram.png diff --git a/libs/ezcomponents/Url/design/requirements.txt b/libs/ezcomponents/Url/design/requirements.txt new file mode 100644 index 0000000000..6834d8e0bd --- /dev/null +++ b/libs/ezcomponents/Url/design/requirements.txt @@ -0,0 +1,68 @@ +eZ component: Url, Requirements +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +:Author: Alexandru Stanoi +:Revision: $Revision: $ +:Date: $Date: $ + +Introduction +============ + +Description +----------- + +This component will provide support for handling urls. The applications that +use the url component can create urls, can modify query arguments, can extract +the basedir. The query arguments are urlencoded to protect against attacks with +sql injection. + +Current implementation +---------------------- + +The current implementation of the Url component has functionality similar to +the eZ publish 3.x url handling. ezcUrl is the only class in the package. + +Requirements +============ + +Provide these functionalities: + +- Create a url from scratch or from a string url. + +- System urls (eg. http://www.example.com/index.php/content/edit/13/3/) and + virtual urls (eg. http://www.example.com/company/about/) are supported. + +- Query arguments can be extracted from and inserted into the url + (eg. '?search=foo&lang=en' or '/content/view/article/42/mode/print'). + +- Query arguments are urlencoded/urldecoded. + +- Support relative urls. + +- List of prefixes (eg. 'images' => '/media/pictures') to be prepended to + filenames so the full path is not specified everytime. + +- List of basedirs (eg. 'ex' => 'www.example.com/mydir') to be + prepended to filenames so the basedir is not specified everytime. + +- Extract the basedir from the current location. + +- Remove the basedir from the current location (eg. from + 'http://www.example.com/shop/products/10' return 'products/10'). + +Design goals +============ + +The url passed to the constructor is parsed (broken down into parts) with the +php function parse_url(), and it is reconstructed with the __toString() method. + +A static array holds prefixes (eg. 'images' => '/media/pictures'). Another +static array holds the basedirs (eg. 'ex' => 'http://www.example.com/mydir'). + + + +.. + Local Variables: + mode: rst + fill-column: 79 + End: + vim: et syn=rst tw=79 diff --git a/libs/ezcomponents/Url/docs/tutorial.txt b/libs/ezcomponents/Url/docs/tutorial.txt new file mode 100644 index 0000000000..725be611f3 --- /dev/null +++ b/libs/ezcomponents/Url/docs/tutorial.txt @@ -0,0 +1,273 @@ +eZ Components - Url +~~~~~~~~~~~~~~~~~~~ + +.. contents:: Table of Contents + +Introduction +============ + +The Url component provides basic operations to handle urls (including parse, +build, get/set path parameters, get/set query and create formatted urls). + +Class overview +============== + +ezcUrl + This is the main class of this component. It contains methods for url + parsing, url building, get/set parameters and get/set query. + +ezcUrlConfiguration + This class allows the definition of url configurations (including basedir, + script, ordered parameters, unordered parameters and delimiters for unordered + parameter names). + +ezcUrlCreator + This class allows you to register a url under an alias. You can then use that + alias to generate another url suffixed with a value, or to create urls + formatted using the syntax of the PHP function sprintf(). + +Notes +===== + +Working with path, params and query parts +----------------------------------------- + +Do not work with the path, params and query properties directly, because this +will not work in PHP5.2.0 (that is, do not set/get $url->query[0], because a +notice will be thrown: "Notice: Indirect modification of overloaded property +ezcUrl::$query has no effect"). Instead, use the following methods. + +Using url configurations +------------------------ + +By using the ezcUrlConfiguration class, you can specify a custom configuration +that can be used to parse urls. The properties you can set in objects of this +class are the default base directory, default script name (which will be +hidden when building the url), delimiters for unordered parameter names +and names for accepted parameters. + +Working with the query part +=========================== + +Getting the query part +---------------------- + +Here is an example of getting the query part of urls: + +.. include:: tutorial/tutorial_get_query.php + :literal: + +The output would be: :: + + array(1) { + ["user"]=> + array(3) { + ["name"]=> + string(9) "Bob Smith" + ["age"]=> + string(2) "47" + ["sex"]=> + string(1) "M" + } + } + +Setting the query part +---------------------- + +Here is an example of setting the query part of urls: + +.. include:: tutorial/tutorial_set_query.php + :literal: + +The output would be as follows (wrapped for clarity): :: + + string(139) "http://www.example.com/mydir/index.php/content/view/article/ + 42/mode/print?user[name]=Bob+Smith&user[age]=47&user[sex]=M&user[dob]= + 5/12/1956" + + string(149) "http://www.example.com/mydir/index.php/content/view/article/ + 42/mode/print?user[name]=Bob+Smith&user[age]=47&user[sex]=M&user[dob]= + 5/12/1956&sort=desc" + + string(139) "http://www.example.com/mydir/index.php/content/view/article/ + 42/mode/print?user[name]=Bob+Smith&user[age]=47&user[sex]=M&user[dob]= + 5/12/1956" + +Working with url configurations +=============================== + +Creating and using a custom url configuration +--------------------------------------------- + +The following example creates a custom url configuration and uses it when +creating a new url object: + +.. include:: tutorial/tutorial_cfg_create.php + :literal: + +The output would be: :: + + object(ezcUrlConfiguration)#1 (1) { + ["properties:private"]=> + array(5) { + ["basedir"]=> + string(5) "mydir" + ["script"]=> + string(9) "index.php" + ["unorderedDelimiters"]=> + array(2) { + [0]=> + string(1) "(" + [1]=> + string(1) ")" + } + ["orderedParameters"]=> + array(4) { + ["section"]=> + int(0) + ["group"]=> + int(1) + ["category"]=> + int(2) + ["subcategory"]=> + int(3) + } + ["unorderedParameters"]=> + array(1) { + ["game"]=> + int(0) + } + } + } + +Lazy initialization +------------------- + +Lazy initialization is a mechanism to load and configure a component, only +when it is really used in your application. This mechanism saves time for +parsing the classes and configuration, when the component is not used at all +during one request. You can find a description how you can use it for your +own components and how it works in the `ezcBase tutorial`__. The keyword for +the url component is *ezcUrlConfiguration*. + +__ introduction_Base.html#lazy-initialization + +.. include:: tutorial/tutorial_lazy_initialization.php + :literal: + +This examples configures the URL component exactly like the example before. +The main difference is, that we roll out the configuration to an own class, +and define a callback using ezcBaseInit::setCallback to this class, which +will be called with a url configuration instance as the first parameter on +the first call on ezcUrlConfiguration::getInstance(). + +ezcBaseInit::setCallback accepts as a first parameter a component specific key, +which lets the component later request the right configuration callback. The +second parameter is the name of the class to perform the static callback on. +This class must implement the ezcBaseConfigurationInitializer class. +Each component's lazy initialization calls the static method configureObject() +on the referenced class. + +When the URL is really parsed in your application, like shown in line 35 of +the example, a new ezcUrlConfiguration is instatiated an automatically +configured by the configureObject() method. + +Working with parameters +======================= + +Getting parameters using a url configuration +-------------------------------------------- + +The following example uses the custom url configuration from before to get +the parameters from the provided url: + +.. include:: tutorial/tutorial_get_params.php + :literal: + +The output would be as follows (wrapped for clarity): :: + + string(6) "groups" + string(5) "Games" + string(9) "Adventure" + string(5) "Adult" + array(2) { + [0]=> + string(5) "Larry" + [1]=> + string(1) "7" + } + string(72) "http://www.example.com/mydir/groups/Games/Adventure/Adult/ + (game)/Larry/7" + +Setting parameters using a url configuration +-------------------------------------------- + +The following example uses the custom url configuration from before to set +the parameters into the provided url: + +.. include:: tutorial/tutorial_set_params.php + :literal: + +The output would be as follows (wrapped for clarity): :: + + string(72) "http://www.example.com/mydir/groups/Games/Adventure/Adult/ + (game)/Larry/7" + + string(79) "http://www.example.com/mydir/groups/Games/Adventure/Kids/ + (game)/Monkey_Island/3" + +Changing a url configuration dynamically +---------------------------------------- + +The following example uses the custom url configuration from before to set +the parameters into the provided url: + +.. include:: tutorial/tutorial_cfg_change.php + :literal: + +The output would be: :: + + string(7) "Beatles" + +Using the url creator +===================== + +Appending a suffix to a url +--------------------------- + +With the url creator, you can register a url under an alias, then use that +alias when you want to prepend the url to a file name. + +.. include:: tutorial/tutorial_url_creator.php + :literal: + +The output would be: :: + + string(53) "/images/geo/map_norway.gif?xsize=450&ysize=450&zoom=4" + + string(53) "/images/geo/map_sweden.gif?xsize=450&ysize=450&zoom=4" + + string(38) "/images/geo?xsize=450&ysize=450&zoom=4" + +Using formatting for a url +-------------------------- + +With the url creator, you can register a url under an alias, then use that +alias when you want to apply formatting to a url. + +.. include:: tutorial/tutorial_url_creator_params.php + :literal: + +The output would be: :: + + string(53) "/images/geo/map_norway.gif?xsize=450&ysize=450&zoom=4" + + string(53) "/images/geo/map_sweden.gif?xsize=450&ysize=450&zoom=4" + + +.. + Local Variables: + mode: rst + fill-column: 79 + End: + vim: et syn=rst tw=79 diff --git a/libs/ezcomponents/Url/docs/tutorial/tutorial_autoload.php b/libs/ezcomponents/Url/docs/tutorial/tutorial_autoload.php new file mode 100644 index 0000000000..3045e526ea --- /dev/null +++ b/libs/ezcomponents/Url/docs/tutorial/tutorial_autoload.php @@ -0,0 +1,20 @@ +<?php +$dir = dirname( __FILE__ ); +$dirParts = explode( '/', $dir ); +switch ( $dirParts[count( $dirParts ) - 3] ) +{ + case 'doc': require_once 'ezc/Base/base.php'; break; // pear + case 'trunk': require_once "$dir/../../../Base/src/base.php"; break; // svn + default: require_once "$dir/../../../Base/src/base.php"; break; // bundle +} + +/** + * Autoload ezc classes + * + * @param string $className + */ +function __autoload( $className ) +{ + ezcBase::autoload( $className ); +} +?> diff --git a/libs/ezcomponents/Url/docs/tutorial/tutorial_cfg_change.php b/libs/ezcomponents/Url/docs/tutorial/tutorial_cfg_change.php new file mode 100644 index 0000000000..78bde7fa39 --- /dev/null +++ b/libs/ezcomponents/Url/docs/tutorial/tutorial_cfg_change.php @@ -0,0 +1,50 @@ +<?php +require_once 'tutorial_autoload.php'; + +// create a default url configuration +$urlCfgDefault = new ezcUrlConfiguration(); +$urlCfgDefault->addOrderedParameter( 'section' ); + +// create a configuration for artists +$urlCfgArtist = new ezcUrlConfiguration(); +$urlCfgArtist->addOrderedParameter( 'section' ); +$urlCfgArtist->addOrderedParameter( 'artist_name' ); + +// create a configuration for albums +$urlCfgAlbum = new ezcUrlConfiguration(); +$urlCfgAlbum->addOrderedParameter( 'section' ); +$urlCfgAlbum->addOrderedParameter( 'artist_name' ); +$urlCfgAlbum->addOrderedParameter( 'album_name' ); + +// create a configuration for music genres +$urlCfgGenre = new ezcUrlConfiguration(); +$urlCfgGenre->addOrderedParameter( 'section' ); +$urlCfgGenre->addOrderedParameter( 'genre_name' ); + +$url = new ezcUrl( 'http://mymusicsite.com/showartist/Beatles', $urlCfgDefault ); + +switch ( $url->getParam( 'section' ) ) +{ + case 'showartist': + $url->applyConfiguration( $urlCfgArtist ); + $artist = $url->getParam( 'artist_name' ); + // do stuff with $artist + var_dump( $artist ); + break; + case 'showalbum': + $url->applyConfiguration( $urlCfgAlbum ); + $artist = $url->getParam( 'artist_name' ); + $album = $url->getParam( 'album_name' ); + // do stuff with $artist and $album + var_dump( $artist ); + var_dump( $album ); + break; + case 'showgenre': + $url->applyConfiguration( $urlCfgGenre ); + $genre = $url->getParam( 'genre_name' ); + // do stuff with $genre + var_dump( $genre ); + break; +} + +?> diff --git a/libs/ezcomponents/Url/docs/tutorial/tutorial_cfg_create.php b/libs/ezcomponents/Url/docs/tutorial/tutorial_cfg_create.php new file mode 100644 index 0000000000..3810c691ea --- /dev/null +++ b/libs/ezcomponents/Url/docs/tutorial/tutorial_cfg_create.php @@ -0,0 +1,29 @@ +<?php +require_once 'tutorial_autoload.php'; + +// create an ezcUrlConfiguration object +$urlCfg = new ezcUrlConfiguration(); + +// set the basedir and script values +$urlCfg->basedir = 'mydir'; +$urlCfg->script = 'index.php'; + +// define delimiters for unordered parameter names +$urlCfg->unorderedDelimiters = array( '(', ')' ); + +// define ordered parameters +$urlCfg->addOrderedParameter( 'section' ); +$urlCfg->addOrderedParameter( 'group' ); +$urlCfg->addOrderedParameter( 'category' ); +$urlCfg->addOrderedParameter( 'subcategory' ); + +// define unordered parameters +$urlCfg->addUnorderedParameter( 'game' ); + +// visualize the $urlCfg object +var_dump( $urlCfg ); + +// create a new ezcUrl object from a string url and use the above $urlCfg +$url = new ezcUrl( 'http://www.example.com/mydir/index.php/groups/Games/Adventure/Adult/(game)/Larry/7', $urlCfg ); + +?> diff --git a/libs/ezcomponents/Url/docs/tutorial/tutorial_get_params.php b/libs/ezcomponents/Url/docs/tutorial/tutorial_get_params.php new file mode 100644 index 0000000000..b45ba8c790 --- /dev/null +++ b/libs/ezcomponents/Url/docs/tutorial/tutorial_get_params.php @@ -0,0 +1,36 @@ +<?php +require_once 'tutorial_autoload.php'; + +// create an ezcUrlConfiguration object +$urlCfg = new ezcUrlConfiguration(); + +// set the basedir and script values +$urlCfg->basedir = 'mydir'; +$urlCfg->script = 'index.php'; + +// define delimiters for unordered parameter names +$urlCfg->unorderedDelimiters = array( '(', ')' ); + +// define ordered parameters +$urlCfg->addOrderedParameter( 'section' ); +$urlCfg->addOrderedParameter( 'group' ); +$urlCfg->addOrderedParameter( 'category' ); +$urlCfg->addOrderedParameter( 'subcategory' ); + +// define unordered parameters +$urlCfg->addUnorderedParameter( 'game', ezcUrlConfiguration::MULTIPLE_ARGUMENTS ); + +// create a new ezcUrl object from a string url and use the above $urlCfg +$url = new ezcUrl( 'http://www.example.com/mydir/index.php/groups/Games/Adventure/Adult/(game)/Larry/7', $urlCfg ); + +// get the parameter values from the url +var_dump( $url->getParam( 'section' ) ); +var_dump( $url->getParam( 'group' ) ); +var_dump( $url->getParam( 'category' ) ); +var_dump( $url->getParam( 'subcategory' ) ); +var_dump( $url->getParam( 'game' ) ); + +// output the url (index.php will not be there) +var_dump( $url->buildUrl() ); + +?> diff --git a/libs/ezcomponents/Url/docs/tutorial/tutorial_get_query.php b/libs/ezcomponents/Url/docs/tutorial/tutorial_get_query.php new file mode 100644 index 0000000000..b7f8dfaee7 --- /dev/null +++ b/libs/ezcomponents/Url/docs/tutorial/tutorial_get_query.php @@ -0,0 +1,10 @@ +<?php +require_once 'tutorial_autoload.php'; + +// create a new Url object from a string url +$url = new ezcUrl( 'http://www.example.com/mydir/index.php/content/view/article/42/mode/print?user[name]=Bob+Smith&user[age]=47&user[sex]=M' ); + +// get the query parts +var_dump( $url->getQuery() ); + +?> diff --git a/libs/ezcomponents/Url/docs/tutorial/tutorial_lazy_initialization.php b/libs/ezcomponents/Url/docs/tutorial/tutorial_lazy_initialization.php new file mode 100644 index 0000000000..4c22770cd8 --- /dev/null +++ b/libs/ezcomponents/Url/docs/tutorial/tutorial_lazy_initialization.php @@ -0,0 +1,39 @@ +<?php +require_once 'tutorial_autoload.php'; + +class customLazyUrlConfiguration implements ezcBaseConfigurationInitializer +{ + public static function configureObject( $urlCfg ) + { + // create an ezcUrlConfiguration object + $urlCfg = new ezcUrlConfiguration(); + + // set the basedir and script values + $urlCfg->basedir = 'mydir'; + $urlCfg->script = 'index.php'; + + // define delimiters for unordered parameter names + $urlCfg->unorderedDelimiters = array( '(', ')' ); + + // define ordered parameters + $urlCfg->addOrderedParameter( 'section' ); + $urlCfg->addOrderedParameter( 'group' ); + $urlCfg->addOrderedParameter( 'category' ); + $urlCfg->addOrderedParameter( 'subcategory' ); + + // define unordered parameters + $urlCfg->addUnorderedParameter( 'game' ); + } +} + +ezcBaseInit::setCallback( + 'ezcUrlConfiguration', + 'customLazyUrlConfiguration' +); + +// Classes loaded and configured on first request +$url = new ezcUrl( + 'http://www.example.com/mydir/index.php/groups/Games/Adventure/Adult/(game)/Larry/7', + ezcUrlConfiguration::getInstance() +); +?> diff --git a/libs/ezcomponents/Url/docs/tutorial/tutorial_set_params.php b/libs/ezcomponents/Url/docs/tutorial/tutorial_set_params.php new file mode 100644 index 0000000000..bcc57933f6 --- /dev/null +++ b/libs/ezcomponents/Url/docs/tutorial/tutorial_set_params.php @@ -0,0 +1,32 @@ +<?php +require_once 'tutorial_autoload.php'; + +// create an ezcUrlConfiguration object +$urlCfg = new ezcUrlConfiguration(); + +// set the basedir and script values +$urlCfg->basedir = 'mydir'; +$urlCfg->script = 'index.php'; + +// define delimiters for unordered parameter names +$urlCfg->unorderedDelimiters = array( '(', ')' ); + +// define ordered parameters +$urlCfg->addOrderedParameter( 'section' ); +$urlCfg->addOrderedParameter( 'group' ); +$urlCfg->addOrderedParameter( 'category' ); +$urlCfg->addOrderedParameter( 'subcategory' ); + +// define unordered parameters +$urlCfg->addUnorderedParameter( 'game', ezcUrlConfiguration::MULTIPLE_ARGUMENTS ); + +// create a new ezcUrl object from a string url and use the above $urlCfg +$url = new ezcUrl( 'http://www.example.com/mydir/index.php/groups/Games/Adventure/Adult/(game)/Larry/7', $urlCfg ); +var_dump( $url->buildUrl() ); + +// set the parameter values in the url +$url->setParam( 'subcategory', 'Kids' ); +$url->setParam( 'game', array( 'Monkey_Island', '3' ) ); +var_dump( $url->buildUrl() ); + +?> diff --git a/libs/ezcomponents/Url/docs/tutorial/tutorial_set_query.php b/libs/ezcomponents/Url/docs/tutorial/tutorial_set_query.php new file mode 100644 index 0000000000..9d886ba1ac --- /dev/null +++ b/libs/ezcomponents/Url/docs/tutorial/tutorial_set_query.php @@ -0,0 +1,26 @@ +<?php +require_once 'tutorial_autoload.php'; + +// create a new Url object from a string url +$url = new ezcUrl( 'http://www.example.com/mydir/index.php/content/view/article/42/mode/print?user[name]=Bob+Smith&user[age]=47&user[sex]=M' ); + +// create an array which will be used to set the query part +$query = array( 'user' => array( 'name' => 'Bob Smith', + 'age' => '47', + 'sex' => 'M', + 'dob' => '5/12/1956'), + ); + +// set the query part of the Url object +$url->setQuery( $query ); +var_dump( rawurldecode( $url ) ); + +// add a query parameter to the query part +$url->setQuery( array_merge( $url->getQuery(), array( 'sort' => 'desc' ) ) ); +var_dump( rawurldecode( $url ) ); + +// remove a query parameter from the query part +$url->setQuery( array_diff_key( $url->getQuery(), array( 'sort' => null ) ) ); +var_dump( rawurldecode( $url ) ); + +?> diff --git a/libs/ezcomponents/Url/docs/tutorial/tutorial_url_creator.php b/libs/ezcomponents/Url/docs/tutorial/tutorial_url_creator.php new file mode 100644 index 0000000000..faaddbbf8e --- /dev/null +++ b/libs/ezcomponents/Url/docs/tutorial/tutorial_url_creator.php @@ -0,0 +1,16 @@ +<?php +require_once 'tutorial_autoload.php'; + +// register an url under the alias 'map' +ezcUrlCreator::registerUrl( 'map', '/images/geo?xsize=450&ysize=450&zoom=4' ); + +// display the the url prepended to map_norway.gif +var_dump( ezcUrlCreator::prependUrl( 'map', 'map_norway.gif' ) ); + +// display the the url prepended to map_sweden.gif +var_dump( ezcUrlCreator::prependUrl( 'map', 'map_sweden.gif' ) ); + +// display the stored url under the alias 'map' +var_dump( ezcUrlCreator::getUrl( 'map' ) ); + +?> diff --git a/libs/ezcomponents/Url/docs/tutorial/tutorial_url_creator_params.php b/libs/ezcomponents/Url/docs/tutorial/tutorial_url_creator_params.php new file mode 100644 index 0000000000..52c544e673 --- /dev/null +++ b/libs/ezcomponents/Url/docs/tutorial/tutorial_url_creator_params.php @@ -0,0 +1,12 @@ +<?php +require_once 'tutorial_autoload.php'; + +// register an url under the alias 'map' +ezcUrlCreator::registerUrl( 'map', '/images/geo/%s?xsize=%d&ysize=%d&zoom=%d' ); + +// display the stored url under the alias 'map' formatted with parameters +var_dump( ezcUrlCreator::getUrl( 'map', 'map_norway.gif', 450, 450, 4 ) ); + +// display the stored url under the alias 'map' formatted with other parameters +var_dump( ezcUrlCreator::getUrl( 'map', 'map_sweden.gif', 450, 450, 4 ) ); +?> diff --git a/libs/ezcomponents/Url/src/exceptions/url_exception.php b/libs/ezcomponents/Url/src/exceptions/url_exception.php new file mode 100644 index 0000000000..841232f113 --- /dev/null +++ b/libs/ezcomponents/Url/src/exceptions/url_exception.php @@ -0,0 +1,30 @@ +<?php +/** + * File containing the ezcUrlException class + * + * @package Mail + * @version 1.1 + * @copyright Copyright (C) 2005-2007 eZ systems as. All rights reserved. + * @license http://ez.no/licenses/new_bsd New BSD License + */ + +/** + * ezcUrlExceptions are thrown when an exceptional state + * occures in the Url package. + * + * @package Url + * @version 1.1 + */ +class ezcUrlException extends ezcBaseException +{ + /** + * Constructs a new ezcUrlException with error message $message. + * + * @param string $message + */ + public function __construct( $message ) + { + parent::__construct( $message ); + } +} +?> diff --git a/libs/ezcomponents/Url/src/exceptions/url_invalid_parameter_exception.php b/libs/ezcomponents/Url/src/exceptions/url_invalid_parameter_exception.php new file mode 100644 index 0000000000..2dc8f50145 --- /dev/null +++ b/libs/ezcomponents/Url/src/exceptions/url_invalid_parameter_exception.php @@ -0,0 +1,30 @@ +<?php +/** + * File containing the ezcUrlInvalidParameterException class + * + * @package Mail + * @version 1.1 + * @copyright Copyright (C) 2005-2007 eZ systems as. All rights reserved. + * @license http://ez.no/licenses/new_bsd New BSD License + */ +/** + * ezcUrlInvalidParameterException is thrown at get/set of a parameter + * undefined in the configuration. + * + * @package Url + * @version 1.1 + */ +class ezcUrlInvalidParameterException extends ezcUrlException +{ + /** + * Constructs a new ezcInvalidParameterException. + * + * @param string $param + */ + public function __construct( $param ) + { + $message = "The parameter '{$param}' could not be set/get because it is not defined in the configuration."; + parent::__construct( $message ); + } +} +?> diff --git a/libs/ezcomponents/Url/src/exceptions/url_no_configuration_exception.php b/libs/ezcomponents/Url/src/exceptions/url_no_configuration_exception.php new file mode 100644 index 0000000000..9fcc0c0423 --- /dev/null +++ b/libs/ezcomponents/Url/src/exceptions/url_no_configuration_exception.php @@ -0,0 +1,30 @@ +<?php +/** + * File containing the ezcUrlNoConfigurationException class + * + * @package Mail + * @version 1.1 + * @copyright Copyright (C) 2005-2007 eZ systems as. All rights reserved. + * @license http://ez.no/licenses/new_bsd New BSD License + */ +/** + * ezcUrlNoConfigurationException is thrown whenever you try to use a url + * configuration that is not defined. + * + * @package Url + * @version 1.1 + */ +class ezcUrlNoConfigurationException extends ezcUrlException +{ + /** + * Constructs a new ezcUrlNoConfigurationException. + * + * @param string $param + */ + public function __construct( $param ) + { + $message = "The parameter '{$param}' could not be set/get because the url doesn't have a configuration defined."; + parent::__construct( $message ); + } +} +?> diff --git a/libs/ezcomponents/Url/src/exceptions/url_not_registered_exception.php b/libs/ezcomponents/Url/src/exceptions/url_not_registered_exception.php new file mode 100644 index 0000000000..f6c2aece66 --- /dev/null +++ b/libs/ezcomponents/Url/src/exceptions/url_not_registered_exception.php @@ -0,0 +1,30 @@ +<?php +/** + * File containing the ezcUrlNotRegisteredException class + * + * @package Mail + * @version 1.1 + * @copyright Copyright (C) 2005-2007 eZ systems as. All rights reserved. + * @license http://ez.no/licenses/new_bsd New BSD License + */ +/** + * ezcUrlNotRegisteredException is thrown whenever you try to use a url + * that is not registered. + * + * @package Url + * @version 1.1 + */ +class ezcUrlNotRegisteredException extends ezcUrlException +{ + /** + * Constructs a new ezcUrlNotRegisteredException. + * + * @param string $name + */ + public function __construct( $name ) + { + $message = "The url '{$name}' is not registered."; + parent::__construct( $message ); + } +} +?> diff --git a/libs/ezcomponents/Url/src/url.php b/libs/ezcomponents/Url/src/url.php new file mode 100644 index 0000000000..a0bbe52c5d --- /dev/null +++ b/libs/ezcomponents/Url/src/url.php @@ -0,0 +1,576 @@ +<?php +/** + * File containing the ezcUrl class. + * + * @copyright Copyright (C) 2005-2007 eZ systems as. All rights reserved. + * @license http://ez.no/licenses/new_bsd New BSD License + * @version 1.1 + * @filesource + * @package Url + */ + +/** + * ezcUrl stores an URL both absolute and relative and contains methods to + * retrieve the various parts of the URL and to manipulate them. + * + * Example of use: + * <code> + * // create an ezcUrlConfiguration object + * $urlCfg = new ezcUrlConfiguration(); + * // set the basedir and script values + * $urlCfg->basedir = 'mydir'; + * $urlCfg->script = 'index.php'; + * + * // define delimiters for unordered parameter names + * $urlCfg->unorderedDelimiters = array( '(', ')' ); + * + * // define ordered parameters + * $urlCfg->addOrderedParameter( 'section' ); + * $urlCfg->addOrderedParameter( 'group' ); + * $urlCfg->addOrderedParameter( 'category' ); + * $urlCfg->addOrderedParameter( 'subcategory' ); + * + * // define unordered parameters + * $urlCfg->addUnorderedParameter( 'game', ezcUrlConfiguration::MULTIPLE_ARGUMENTS ); + * + * // create a new ezcUrl object from a string URL and use the above $urlCfg + * $url = new ezcUrl( 'http://www.example.com/mydir/index.php/groups/Games/Adventure/Adult/(game)/Larry/7', $urlCfg ); + * + * // to get the parameter values from the URL use $url->getParam(): + * $section = $url->getParam( 'section' ); // will be "groups" + * $group = $url->getParam( 'group' ); // will be "Games" + * $category = $url->getParam( 'category' ); // will be "Adventure" + * $subcategory = $url->getParam( 'subcategory' ); // will be "Adult" + * $game = $url->getParam( 'game' ); // will be array( "Larry", "7" ) + * </code> + * + * @property string $host + * Hostname or null + * @property string $path + * Complete path as an array. + * @property string $user + * User or null. + * @property string $pass + * Password or null. + * @property string $port + * Port or null. + * @property string $scheme + * Protocol or null. + * @property string $query + * Complete query string as an associative array. + * @property string $fragment + * Anchor or null. + * @property string $basedir + * Base directory or null. + * @property string $script + * Script name or null. + * @property string $params + * Complete ordered parameters as array. + * @property string $uparams + * Complete unordered parameters as associative array. + * @property ezcUrlConfiguration $configuration + * The URL configuration defined for this URL, or null. + * + * @package Url + * @version 1.1 + * @mainclass + */ +class ezcUrl +{ + /** + * Holds the properties of this class. + * + * @var array(string=>mixed) + */ + private $properties = array(); + + /** + * Constructs a new ezcUrl object from the string $url. + * + * If the $configuration parameter is provided, then it will apply the + * configuration to the URL by calling {@link applyConfiguration()}. + * + * @param string $url A string URL from which to construct the URL object + * @param ezcUrlConfiguration $configuration An optional URL configuration used when parsing and building the URL + */ + public function __construct( $url = null, ezcUrlConfiguration $configuration = null ) + { + $this->parseUrl( $url ); + $this->configuration = $configuration; + if ( $configuration != null ) + { + $this->applyConfiguration( $configuration ); + } + } + + /** + * Sets the property $name to $value. + * + * @throws ezcBasePropertyNotFoundException + * if the property $name does not exist + * @throws ezcBaseValueException + * if $value is not correct for the property $name + * @param string $name The name of the property to set + * @param mixed $value The new value of the property + * @ignore + */ + public function __set( $name, $value ) + { + switch ( $name ) + { + case 'host': + case 'path': + case 'user': + case 'pass': + case 'port': + case 'scheme': + case 'fragment': + case 'query': + case 'basedir': + case 'script': + case 'params': + case 'uparams': + $this->properties[$name] = $value; + break; + + case 'configuration': + if ( $value === null || $value instanceof ezcUrlConfiguration ) + { + $this->properties[$name] = $value; + } + else + { + throw new ezcBaseValueException( $name, $value, 'instance of ezcUrlConfiguration' ); + } + break; + + default: + throw new ezcBasePropertyNotFoundException( $name ); + break; + } + } + + /** + * Returns the property $name. + * + * @throws ezcBasePropertyNotFoundException + * if the property $name does not exist + * @param string $name The name of the property for which to return the value + * @return mixed + * @ignore + */ + public function __get( $name ) + { + switch ( $name ) + { + case 'host': + case 'path': + case 'user': + case 'pass': + case 'port': + case 'scheme': + case 'fragment': + case 'query': + case 'basedir': + case 'script': + case 'params': + case 'uparams': + case 'configuration': + return $this->properties[$name]; + + default: + throw new ezcBasePropertyNotFoundException( $name ); + } + } + + /** + * Returns true if the property $name is set, otherwise false. + * + * @param string $name The name of the property to test if it is set + * @return bool + * @ignore + */ + public function __isset( $name ) + { + switch ( $name ) + { + case 'host': + case 'path': + case 'user': + case 'pass': + case 'port': + case 'scheme': + case 'fragment': + case 'query': + case 'basedir': + case 'script': + case 'params': + case 'uparams': + case 'configuration': + return isset( $this->properties[$name] ); + + default: + return false; + } + } + + /** + * Returns this URL as a string by calling {@link buildUrl()}. + * + * @return string + */ + public function __toString() + { + return $this->buildUrl(); + } + + /** + * Parses the string $url and sets the class properties. + * + * @param string $url A string URL to parse + */ + private function parseUrl( $url = null ) + { + $urlArray = parse_url( $url ); + + $this->properties['host'] = isset( $urlArray['host'] ) ? $urlArray['host'] : null; + $this->properties['user'] = isset( $urlArray['user'] ) ? $urlArray['user'] : null; + $this->properties['pass'] = isset( $urlArray['pass'] ) ? $urlArray['pass'] : null; + $this->properties['port'] = isset( $urlArray['port'] ) ? $urlArray['port'] : null; + $this->properties['scheme'] = isset( $urlArray['scheme'] ) ? $urlArray['scheme'] : null; + $this->properties['fragment'] = isset( $urlArray['fragment'] ) ? $urlArray['fragment'] : null; + $this->properties['path'] = isset( $urlArray['path'] ) ? explode( '/', trim( $urlArray['path'], '/' ) ) : array(); + + $this->properties['basedir'] = array(); + $this->properties['script'] = array(); + $this->properties['params'] = array(); + $this->properties['uparams'] = array(); + + if ( isset( $urlArray['query'] ) ) + { + parse_str( $urlArray['query'] , $this->properties['query'] ); + } + else + { + $this->properties['query'] = array(); + } + } + + /** + * Applies the URL configuration $configuration to the current url. + * + * It fills the arrays $basedir, $script, $params and $uparams with values + * from $path. + * + * It also sets the property configuration to the value of $configuration. + * + * @param ezcUrlConfiguration $configuration An URL configuration used in parsing + */ + public function applyConfiguration( ezcUrlConfiguration $configuration ) + { + $this->configuration = $configuration; + $this->basedir = $this->parsePathElement( $configuration->basedir, 0 ); + $this->script = $this->parsePathElement( $configuration->script, count( $this->basedir ) ); + $this->params = $this->parseOrderedParameters( $configuration->orderedParameters, count( $this->basedir ) + count( $this->script ) ); + $this->uparams = $this->parseUnorderedParameters( $configuration->unorderedParameters, count( $this->basedir ) + count( $this->script ) + count( $this->params ) ); + } + + /** + * Parses $path based on the configuration $config, starting from $index. + * + * Returns the first few elements of $this->path matching $config, + * starting from $index. + * + * @param string $config A string which will be matched against the path part of the URL + * @param int $index The index in the URL path part from where to start the matching of $config + * @return array(string=>mixed) + */ + private function parsePathElement( $config, $index ) + { + $config = trim( $config, '/' ); + $paramParts = explode( '/', $config ); + $pathElement = array(); + foreach ( $paramParts as $part ) + { + if ( isset( $this->path[$index] ) && $part == $this->path[$index] ) + { + $pathElement[] = $part; + } + $index++; + } + return $pathElement; + } + + /** + * Returns ordered parameters from the $path array. + * + * @param array(string) $config An array of ordered parameters names, from the URL configuration used in parsing + * @param int $index The index in the URL path part from where to start the matching of $config + * @return array(string=>mixed) + */ + public function parseOrderedParameters( $config, $index ) + { + $result = array(); + $pathCount = count( $this->path ); + for ( $i = 0; $i < count( $config ); $i++ ) + { + if ( isset( $this->path[$index + $i] ) ) + { + $result[] = $this->path[$index + $i]; + } + else + { + $result[] = null; + } + } + return $result; + } + + /** + * Returns unordered parameters from the $path array. + * + * @param array(string) $config An array of unordered parameters names, from the URL configuration used in parsing + * @param int $index The index in the URL path part from where to start the matching of $config + * @return array(string=>mixed) + */ + public function parseUnorderedParameters( $config, $index ) + { + $result = array(); + $pathCount = count( $this->path ); + if ( $pathCount == 0 || ( $pathCount == 1 && trim( $this->path[0] ) === "" ) ) + { + // special case: a bug? in parse_url() which makes $this->path + // be array( "" ) if the provided URL is null or empty + return $result; + } + for ( $i = $index; $i < $pathCount; $i++ ) + { + $param = $this->path[$i]; + if ( $param{0} == $this->configuration->unorderedDelimiters[0] ) + { + $param = trim( trim( $param, $this->configuration->unorderedDelimiters[0] ), $this->configuration->unorderedDelimiters[1] ); + $result[$param] = array(); + $j = 1; + while ( ( $i + $j ) < $pathCount && $this->path[$i + $j]{0} != $this->configuration->unorderedDelimiters[0] ) + { + $result[$param][] = trim( trim( $this->path[$i + $j], $this->configuration->unorderedDelimiters[0] ), $this->configuration->unorderedDelimiters[1] ); + $j++; + } + } + } + return $result; + } + + /** + * Returns this URL as a string. + * + * The query part of the URL is build with http_build_query() which + * encodes the query in a similar way to urlencode(). + * + * @return string + */ + public function buildUrl() + { + $url = ''; + + if ( $this->scheme ) + { + $url .= $this->scheme . '://'; + } + + if ( $this->host ) + { + if ( $this->user ) + { + $url .= $this->user; + if ( $this->pass ) + { + $url .= ':' . $this->pass; + } + $url .= '@'; + } + + $url .= $this->host; + if ( $this->port ) + { + $url .= ':' . $this->port; + } + } + + if ( $this->configuration != null ) + { + if ( $this->basedir ) + { + if ( !( count( $this->basedir ) == 0 || trim( $this->basedir[0] ) === "" ) ) + { + $url .= '/' . implode( '/', $this->basedir ); + } + } + + if ( $this->params && count( $this->params ) != 0 ) + { + $url .= '/' . implode( '/', $this->params ); + } + + if ( $this->uparams && count( $this->uparams ) != 0 ) + { + foreach ( $this->properties['uparams'] as $key => $values ) + { + $url .= '/(' . $key . ')/' . implode( '/', $values ); + } + } + } + else + { + if ( $this->path ) + { + $url .= '/' . implode( '/', $this->path ); + } + } + + if ( $this->query ) + { + $url .= '?' . http_build_query( $this->query ); + } + + if ( $this->fragment ) + { + $url .= '#' . $this->fragment; + } + + return $url; + } + + /** + * Returns true if this URL is relative and false if the URL is absolute. + * + * @return bool + */ + public function isRelative() + { + if ( $this->host === null || $this->host == '' ) + { + return true; + } + return false; + } + + /** + * Returns the specified parameter from the URL based on the URL configuration. + * + * @throws ezcUrlNoConfigurationException + * if an URL configuration is not defined + * @throws ezcUrlInvalidParameterException + * if the specified parameter is not defined in the URL configuration + * @param string $name The name of the parameter for which to return the value + * @return mixed + */ + public function getParam( $name ) + { + if ( $this->configuration != null ) + { + if ( !( isset( $this->configuration->orderedParameters[$name] ) || + isset( $this->configuration->unorderedParameters[$name] ) ) ) + { + throw new ezcUrlInvalidParameterException( $name ); + } + + $params = $this->params; + $uparams = $this->uparams; + if ( isset( $this->configuration->orderedParameters[$name] ) && + isset( $params[$this->configuration->orderedParameters[$name]] ) ) + { + return $params[$this->configuration->orderedParameters[$name]]; + } + + if ( isset( $this->configuration->unorderedParameters[$name] ) && + isset( $uparams[$name] ) ) + { + if ( $this->configuration->unorderedParameters[$name] == ezcUrlConfiguration::SINGLE_ARGUMENT ) + { + if ( count( $uparams[$name] ) > 0 ) + { + return $uparams[$name][0]; + } + } + else + { + return $uparams[$name]; + } + } + return null; + } + throw new ezcUrlNoConfigurationException( $name ); + } + + /** + * Sets the specified parameter in the URL based on the URL configuration. + * + * @throws ezcUrlNoConfigurationException + * if an URL configuration is not defined + * @throws ezcUrlInvalidParameterException + * if the specified parameter is not defined in the URL configuration + * @param string $name The name of the parameter to set + * @param string $value The new value of the parameter + */ + public function setParam( $name, $value ) + { + if ( $this->configuration != null ) + { + if ( !( isset( $this->configuration->orderedParameters[$name] ) || + isset( $this->configuration->unorderedParameters[$name] ) ) ) + { + throw new ezcUrlInvalidParameterException( $name ); + } + + if ( isset( $this->configuration->orderedParameters[$name] ) ) + { + $this->properties['params'][$this->configuration->orderedParameters[$name]] = $value; + return; + } + if ( isset( $this->configuration->unorderedParameters[$name] ) ) + { + if ( is_array( $value ) ) + { + $this->properties['uparams'][$name] = $value; + } + else + { + $this->properties['uparams'][$name] = array( $value ); + } + } + return; + } + throw new ezcUrlNoConfigurationException( $name ); + } + + /** + * Returns the query elements as an associative array. + * + * Example: + * for 'http://www.example.com/mydir/shop?content=view&products=10' + * returns array( 'content' => 'view', 'products' => '10' ) + * + * @return array(string=>mixed) + */ + public function getQuery() + { + return $this->query; + } + + /** + * Set the query elements using the associative array provided. + * + * Example: + * for 'http://www.example.com/mydir/shop' + * and $query = array( 'content' => 'view', 'products' => '10' ) + * then 'http://www.example.com/mydir/shop?content=view&products=10' + * + * @param array(string=>mixed) $query The new value of the query part + */ + public function setQuery( $query ) + { + $this->query = $query; + } +} +?> diff --git a/libs/ezcomponents/Url/src/url_configuration.php b/libs/ezcomponents/Url/src/url_configuration.php new file mode 100644 index 0000000000..96ae57ce96 --- /dev/null +++ b/libs/ezcomponents/Url/src/url_configuration.php @@ -0,0 +1,270 @@ +<?php +/** + * File containing the ezcUrlConfiguration class. + * + * @copyright Copyright (C) 2005-2007 eZ systems as. All rights reserved. + * @license http://ez.no/licenses/new_bsd New BSD License + * @version 1.1 + * @filesource + * @package Url + */ + +/** + * ezcUrlConfiguration makes it possible to use a custom URL form in your application. + * + * Example of use: + * <code> + * // create an ezcUrlConfiguration object + * $urlCfg = new ezcUrlConfiguration(); + * // set the basedir and script values + * $urlCfg->basedir = 'mydir'; + * $urlCfg->script = 'index.php'; + * + * // define delimiters for unordered parameter names + * $urlCfg->unorderedDelimiters = array( '(', ')' ); + * + * // define ordered parameters + * $urlCfg->addOrderedParameter( 'section' ); + * $urlCfg->addOrderedParameter( 'group' ); + * $urlCfg->addOrderedParameter( 'category' ); + * $urlCfg->addOrderedParameter( 'subcategory' ); + * + * // define unordered parameters + * $urlCfg->addUnorderedParameter( 'game', ezcUrlConfiguration::MULTIPLE_ARGUMENTS ); + * + * // create a new ezcUrl object from a string URL and use the above $urlCfg + * $url = new ezcUrl( 'http://www.example.com/mydir/index.php/groups/Games/Adventure/Adult/(game)/Larry/7', $urlCfg ); + * + * // to get the parameter values from the URL use $url->getParam(): + * $section = $url->getParam( 'section' ); // will be "groups" + * $group = $url->getParam( 'group' ); // will be "Games" + * $category = $url->getParam( 'category' ); // will be "Adventure" + * $subcategory = $url->getParam( 'subcategory' ); // will be "Adult" + * $game = $url->getParam( 'game' ); // will be array( "Larry", "7" ) + * + * // to remove parameters from the URL configuration $urlCfg + * $urlCfg->removeOrderedParameter( 'subcategory' ); + * $urlCfg->removeUnorderedParameter( 'game' ); + * + * // to remove parameters from the URL configuration stored in the URL + * $url->configuration->removeOrderedParameter( 'subcategory' ); + * $url->configuration->removeUnorderedParameter( 'game' ); + * </code> + * + * @property string $basedir + * The part of the URL after the first slash. It can be null. + * Example: $basedir = shop in http://www.example.com/shop + * @property string $script + * The default php script, which comes after the basedir. Can be null + * if the web server configuration is set to hide it. + * Example: $script = index.php in http://www.example.com/shop/index.php + * @property array $unorderedDelimiters + * The delimiters for the unordered parameters names. + * Example: $unorderedDelimiters = array( '(', ')' ) for + * url = http://www.example.com/doc/(file)/classtrees_Base.html + * @property string $orderedParameters + * The ordered parameters of the URL. + * Example: $orderedParameters = array( 'section' => 0, 'module' => 1, 'view' => 2, 'content' => 3 ); + * url = http://www.example.com/doc/components/view/trunk + * The numbers in the array represent the indices for each parameter. + * @property string $unorderedParameters + * The unordered parameters of the URL. + * Example: $unorderedParameters = array( 'file' => SINGLE_ARGUMENT ); + * url = http://www.example.com/doc/(file)/classtrees_Base.html + * The keys of the array represent the parameter names, and the values + * in the array represent the types of the parameters. + * + * @package Url + * @version 1.1 + */ +class ezcUrlConfiguration +{ + /** + * Flag for specifying single arguments for unordered parameters. + */ + const SINGLE_ARGUMENT = 1; + + /** + * Flag for specifying multiple arguments for unordered parameters. + */ + const MULTIPLE_ARGUMENTS = 2; + + /** + * Holds the properties of this class. + * + * @var array(string=>mixed) + */ + private $properties = array(); + + /** + * Stores the instance of this class. + * + * @var ezcUrlConfiguration + */ + private static $instance = null; + + /** + * Constructs a new ezcUrlConfiguration object. + * + * The properties of the object get default values, which can be changed by + * setting the properties directly, like: + * <code> + * $urlCfg = new ezcUrlConfiguration(); + * $urlCfg->basedir = 'mydir'; + * $urlCfg->script = 'index.php'; + * </code> + */ + public function __construct() + { + $this->basedir = null; + $this->script = null; + $this->unorderedDelimiters = array( '(', ')' ); + $this->orderedParameters = array(); + $this->unorderedParameters = array(); + } + + /** + * Returns the instance of the class. + * + * @return ezcUrlConfiguration + */ + public static function getInstance() + { + if ( is_null( self::$instance ) ) + { + self::$instance = new ezcUrlConfiguration(); + ezcBaseInit::fetchConfig( 'ezcUrlConfiguration', self::$instance ); + } + return self::$instance; + } + + /** + * Sets the property $name to $value. + * + * @throws ezcBasePropertyNotFoundException + * if the property does not exist. + * @param string $name The name of the property to set + * @param mixed $value The new value of the property + * @ignore + */ + public function __set( $name, $value ) + { + switch ( $name ) + { + case 'basedir': + case 'script': + case 'unorderedDelimiters': + case 'orderedParameters': + case 'unorderedParameters': + $this->properties[$name] = $value; + break; + + default: + throw new ezcBasePropertyNotFoundException( $name ); + } + } + + /** + * Returns the property $name. + * + * @throws ezcBasePropertyNotFoundException + * if the property does not exist. + * @param string $name The name of the property for which to return the value + * @return mixed + * @ignore + */ + public function __get( $name ) + { + switch ( $name ) + { + case 'basedir': + case 'script': + case 'unorderedDelimiters': + case 'orderedParameters': + case 'unorderedParameters': + return $this->properties[$name]; + + default: + throw new ezcBasePropertyNotFoundException( $name ); + } + } + + /** + * Returns true if the property $name is set, otherwise false. + * + * @param string $name The name of the property to test if it is set + * @return bool + * @ignore + */ + public function __isset( $name ) + { + switch ( $name ) + { + case 'basedir': + case 'script': + case 'unorderedDelimiters': + case 'orderedParameters': + case 'unorderedParameters': + return isset( $this->properties[$name] ); + + default: + return false; + } + } + + /** + * Adds an ordered parameter to the URL configuration. + * + * @param string $name The name of the ordered parameter to add to the configuration + */ + public function addOrderedParameter( $name ) + { + $this->properties['orderedParameters'][$name] = count( $this->properties['orderedParameters'] ); + } + + /** + * Removes an ordered parameter from the URL configuration. + * + * @param string $name The name of the ordered parameter to remove from the configuration + */ + public function removeOrderedParameter( $name ) + { + if ( isset( $this->properties['orderedParameters'][$name] ) ) + { + unset( $this->properties['orderedParameters'][$name] ); + } + } + + /** + * Adds an unordered parameter to the URL configuration. + * + * The default type of the parameter is {@link SINGLE_ARGUMENT}. + * + * Other valid types are {@link MULTIPLE_ARGUMENTS}. + * + * @param string $name The name of the unordered parameter to add to the configuration + * @param int $type The type of the unordered parameter + */ + public function addUnorderedParameter( $name, $type = null ) + { + if ( $type == null ) + { + $type = self::SINGLE_ARGUMENT; + } + $this->properties['unorderedParameters'][$name] = $type; + } + + /** + * Removes an unordered parameter from the URL configuration. + * + * @param string $name The name of the unordered parameter to remove from the configuration + */ + public function removeUnorderedParameter( $name ) + { + if ( isset( $this->properties['unorderedParameters'][$name] ) ) + { + unset( $this->properties['unorderedParameters'][$name] ); + } + } +} +?> diff --git a/libs/ezcomponents/Url/src/url_creator.php b/libs/ezcomponents/Url/src/url_creator.php new file mode 100644 index 0000000000..d403ef606f --- /dev/null +++ b/libs/ezcomponents/Url/src/url_creator.php @@ -0,0 +1,124 @@ +<?php +/** + * File containing the ezcUrlCreator class. + * + * @copyright Copyright (C) 2005-2007 eZ systems as. All rights reserved. + * @license http://ez.no/licenses/new_bsd New BSD License + * @version 1.1 + * @filesource + * @package Url + */ + +/** + * ezcUrlCreator makes it easy to create urls from scratch. + * + * Holds a list of urls mapped to aliases. The aliases are used to refer to the + * urls stored, so the urls will not be hardcoded all over the application code. + * + * Example of use: + * <code> + * // register an URL under the alias 'map' + * ezcUrlCreator::registerUrl( 'map', '/images/geo/%s?xsize=%d&ysize=%d&zoom=%d' ); + * + * // retrieve the stored URL under the alias 'map' formatted with parameters + * $url = ezcUrlCreator::getUrl( 'map', 'map_norway.gif', 450, 450, 4 ); + * // will be: "/images/geo/map_norway.gif?xsize=450&ysize=450&zoom=4" + * + * // retrieve the stored URL under the alias 'map' formatted with other parameters + * $url = ezcUrlCreator::getUrl( 'map', 'map_sweden.gif', 450, 450, 4 ); + * // will be: "/images/geo/map_sweden.gif?xsize=450&ysize=450&zoom=4" + * </code> + * + * @package Url + * @version 1.1 + */ +class ezcUrlCreator +{ + /** + * Holds the registered urls. + * + * @var array(string=>string) + */ + private static $urls = array(); + + /** + * Registers $url as $name in the URLs list. + * + * If $name is already registered, it will be overwritten. + * + * @param string $name The name associated with the URL + * @param string $url The URL to register + */ + public static function registerUrl( $name, $url ) + { + self::$urls[$name] = $url; + } + + /** + * Returns the URL registerd as $name prepended to $suffix. + * + * Example: + * <code> + * ezcUrlCreator::registerUrl( 'map', '/images/geo?xsize=450&ysize=450&zoom=4' ); + * echo ezcUrlCreator::prependUrl( 'map', 'map_sweden.gif' ); + * </code> + * will output: + * /images/geo/map_sweden.gif?xsize=450&ysize=450&zoom=4 + * + * @throws ezcUrlNotRegisteredException + * if $name is not registered + * @param string $name The name associated with the URL that will be appended with $suffix + * @param string $suffix The string which will be appended to the URL + * @return string + */ + public static function prependUrl( $name, $suffix ) + { + if ( !isset( self::$urls[$name] ) ) + { + throw new ezcUrlNotRegisteredException( $name ); + } + + $url = new ezcUrl( self::$urls[$name] ); + $url->path = array_merge( $url->path, explode( '/', $suffix ) ); + return $url->buildUrl(); + } + + /** + * Returns the URL registered as $name. + * + * This function accepts a variable number of arguments like the sprintf() + * function. If you specify more than 1 arguments when calling this + * function, the registered URL will be formatted using those arguments + * similar with the sprintf() function. + * Example: + * <code> + * ezcUrlCreator::registerUrl( 'map', '/images/geo/%s?xsize=%d&ysize=%d&zoom=%d' ); + * echo ezcUrlCreator::getUrl( 'map', 'map_sweden.gif', 450, 450, 4 ); + * </code> + * will output: + * /images/geo/map_sweden.gif?xsize=450&ysize=450&zoom=4 + * + * @throws ezcUrlNotRegisteredException + * if $name is not registered + * @param string $name The name associated with the URL + * @param mixed $args,... Optional values which will be vsprintf-ed in the URL + * @return string + */ + public static function getUrl( $name ) + { + if ( !isset( self::$urls[$name] ) ) + { + throw new ezcUrlNotRegisteredException( $name ); + } + + if ( func_num_args() > 1 ) + { + $args = func_get_args(); + // get rid of the first argument ($name) + unset( $args[0] ); + return vsprintf( self::$urls[$name], $args ); + } + return self::$urls[$name]; + } +} +?> diff --git a/libs/ezcomponents/Url/tests/suite.php b/libs/ezcomponents/Url/tests/suite.php new file mode 100644 index 0000000000..b4131771d2 --- /dev/null +++ b/libs/ezcomponents/Url/tests/suite.php @@ -0,0 +1,39 @@ +<?php +/** + * @copyright Copyright (C) 2005-2007 eZ systems as. All rights reserved. + * @license http://ez.no/licenses/new_bsd New BSD License + * @version 1.1 + * @filesource + * @package Url + * @subpackage Tests + */ + +/** + * Including the tests + */ +require_once( "url_test.php" ); +require_once( "url_configuration_test.php" ); +require_once( "url_creator_test.php" ); + +/** + * @package Url + * @subpackage Tests + */ +class ezcUrlSuite extends PHPUnit_Framework_TestSuite +{ + public function __construct() + { + parent::__construct(); + $this->setName("Url"); + + $this->addTest( ezcUrlTest::suite() ); + $this->addTest( ezcUrlConfigurationTest::suite() ); + $this->addTest( ezcUrlCreatorTest::suite() ); + } + + public static function suite() + { + return new ezcUrlSuite(); + } +} +?> diff --git a/libs/ezcomponents/Url/tests/url_configuration_test.php b/libs/ezcomponents/Url/tests/url_configuration_test.php new file mode 100644 index 0000000000..3a2814aa69 --- /dev/null +++ b/libs/ezcomponents/Url/tests/url_configuration_test.php @@ -0,0 +1,122 @@ +<?php +/** + * @copyright Copyright (C) 2005-2007 eZ systems as. All rights reserved. + * @license http://ez.no/licenses/new_bsd New BSD License + * @version 1.1 + * @filesource + * @package Url + * @subpackage Tests + */ + +/** + * @package Url + * @subpackage Tests + */ +class ezcUrlConfigurationTest extends ezcTestCase +{ + public function testPropertiesGet() + { + $urlCfg = new ezcUrlConfiguration(); + $this->assertEquals( null, $urlCfg->basedir ); + $this->assertEquals( null, $urlCfg->script ); + $this->assertEquals( array(), $urlCfg->orderedParameters ); + $this->assertEquals( array(), $urlCfg->unorderedParameters ); + $this->assertEquals( array( '(', ')' ), $urlCfg->unorderedDelimiters ); + } + + public function testPropertiesGetInvalid() + { + $urlCfg = new ezcUrlConfiguration(); + try + { + $urlCfg->no_such_property; + $this->fail( 'Expected exception was not thrown' ); + } + catch ( ezcBasePropertyNotFoundException $e ) + { + $expected = "No such property name 'no_such_property'."; + $this->assertEquals( $expected, $e->getMessage() ); + } + } + + public function testPropertiesSet() + { + $urlCfg = new ezcUrlConfiguration(); + $urlCfg->basedir = '/mydir/shop'; + $urlCfg->script = 'index.php'; + $urlCfg->unorderedDelimiters = array( '_', '_' ); + $urlCfg->addOrderedParameter( 'section' ); + $urlCfg->addOrderedParameter( 'module' ); + $urlCfg->addOrderedParameter( 'view' ); + $urlCfg->addOrderedParameter( 'branch' ); + $urlCfg->addUnorderedParameter( 'file' ); + + $this->assertEquals( '/mydir/shop', $urlCfg->basedir ); + $this->assertEquals( 'index.php', $urlCfg->script ); + $this->assertEquals( array( 'section' => 0, 'module' => 1, 'view' => 2, 'branch' => 3 ), + $urlCfg->orderedParameters ); + $this->assertEquals( array( 'file' => 1 ), $urlCfg->unorderedParameters ); + $this->assertEquals( array( '_', '_' ), $urlCfg->unorderedDelimiters ); + } + + public function testPropertiesSetInvalid() + { + $urlCfg = new ezcUrlConfiguration(); + try + { + $urlCfg->no_such_property = 'some value'; + $this->fail( 'Expected exception was not thrown' ); + } + catch ( ezcBasePropertyNotFoundException $e ) + { + $expected = "No such property name 'no_such_property'."; + $this->assertEquals( $expected, $e->getMessage() ); + } + } + + public function testAddOrderedParameter() + { + $urlCfg = new ezcUrlConfiguration(); + $urlCfg->addOrderedParameter( 'folder' ); + } + + public function testAddUnorderedParameter() + { + $urlCfg = new ezcUrlConfiguration(); + $urlCfg->addUnorderedParameter( 'folder' ); + } + + public function testIsSet() + { + $urlCfg = new ezcUrlConfiguration(); + $this->assertEquals( false, isset( $urlCfg->basedir ) ); + $this->assertEquals( false, isset( $urlCfg->script ) ); + $this->assertEquals( true, isset( $urlCfg->unorderedDelimiters ) ); + $this->assertEquals( true, isset( $urlCfg->orderedParameters ) ); + $this->assertEquals( true, isset( $urlCfg->unorderedParameters ) ); + $this->assertEquals( false, isset( $urlCfg->no_such_property ) ); + } + + public function testDelayedInit() + { + ezcBaseInit::setCallback( 'ezcUrlConfiguration', 'testDelayedInitUrlConfiguration' ); + $urlCfg = ezcUrlConfiguration::getInstance(); + $this->assertEquals( array( 'section' => 0 ), $urlCfg->orderedParameters ); + $this->assertEquals( array( 'article' => 1 ), $urlCfg->unorderedParameters ); + } + + public static function suite() + { + return new PHPUnit_Framework_TestSuite( "ezcUrlConfigurationTest" ); + } +} + +class testDelayedInitUrlConfiguration +{ + static function configureObject( $object ) + { + $object->addOrderedParameter( 'section' ); + $object->addUnorderedParameter( 'article' ); + } +} +?> diff --git a/libs/ezcomponents/Url/tests/url_creator_test.php b/libs/ezcomponents/Url/tests/url_creator_test.php new file mode 100644 index 0000000000..ce6eef3e1f --- /dev/null +++ b/libs/ezcomponents/Url/tests/url_creator_test.php @@ -0,0 +1,71 @@ +<?php +/** + * @copyright Copyright (C) 2005-2007 eZ systems as. All rights reserved. + * @license http://ez.no/licenses/new_bsd New BSD License + * @version 1.1 + * @filesource + * @package Url + * @subpackage Tests + */ + +/** + * @package Url + * @subpackage Tests + */ +class ezcUrlCreatorTest extends ezcTestCase +{ + public function testGetUrl() + { + ezcUrlCreator::registerUrl( 'map', 'http://www.example.com' ); + $expected = 'http://www.example.com'; + $this->assertEquals( $expected, ezcUrlCreator::getUrl( 'map' ) ); + } + + public function testGetUrlNotRegistered() + { + try + { + ezcUrlCreator::getUrl( 'not registered url' ); + $this->fail( 'Expected exception was not thrown' ); + } + catch ( ezcUrlNotRegisteredException $e ) + { + $expected = "The url 'not registered url' is not registered."; + $this->assertEquals( $expected, $e->getMessage() ); + } + } + + public function testGetUrlFormatted() + { + ezcUrlCreator::registerUrl( 'map', 'http://www.example.com/images/%s?xsize=%d&ysize=%d&zoom=%d' ); + $expected = 'http://www.example.com/images/map_sweden.gif?xsize=400&ysize=300&zoom=4'; + $this->assertEquals( $expected, ezcUrlCreator::getUrl( 'map', 'map_sweden.gif', 400, 300, 4 ) ); + } + + public function testPrependUrl() + { + ezcUrlCreator::registerUrl( 'map', 'http://www.example.com?id=1' ); + $expected = 'http://www.example.com/images?id=1'; + $this->assertEquals( $expected, ezcUrlCreator::prependUrl( 'map', 'images' ) ); + } + + public function testPrependUrlNotRegistered() + { + try + { + ezcUrlCreator::prependUrl( 'not registered url', 'images' ); + $this->fail( 'Expected exception was not thrown' ); + } + catch ( ezcUrlNotRegisteredException $e ) + { + $expected = "The url 'not registered url' is not registered."; + $this->assertEquals( $expected, $e->getMessage() ); + } + } + + public static function suite() + { + return new PHPUnit_Framework_TestSuite( "ezcUrlCreatorTest" ); + } +} +?> diff --git a/libs/ezcomponents/Url/tests/url_test.php b/libs/ezcomponents/Url/tests/url_test.php new file mode 100644 index 0000000000..983d8d57c8 --- /dev/null +++ b/libs/ezcomponents/Url/tests/url_test.php @@ -0,0 +1,572 @@ +<?php +/** + * @copyright Copyright (C) 2005-2007 eZ systems as. All rights reserved. + * @license http://ez.no/licenses/new_bsd New BSD License + * @version 1.1 + * @filesource + * @package Url + * @subpackage Tests + */ + +/** + * @package Url + * @subpackage Tests + */ +class ezcUrlTest extends ezcTestCase +{ + public function testPropertiesGet() + { + $url = new ezcUrl( 'http://user:password@www.example.com:82/index.php/content/view?products=10&mode=print#cat' ); + $this->assertEquals( 'http', $url->scheme ); + $this->assertEquals( 'www.example.com', $url->host ); + $this->assertEquals( 'user', $url->user ); + $this->assertEquals( 'password', $url->pass ); + $this->assertEquals( 82, $url->port ); + $this->assertEquals( array( 'index.php', 'content', 'view' ), $url->path ); + $this->assertEquals( array( 'products' => '10', 'mode' => 'print' ), $url->query ); + $this->assertEquals( 'cat', $url->fragment ); + } + + public function testPropertiesGetInvalid() + { + $url = new ezcUrl( 'http://www.example.com' ); + try + { + $url->no_such_property = 'data'; + $this->fail( 'Expected exception was not thrown' ); + } + catch ( ezcBasePropertyNotFoundException $e ) + { + $expected = "No such property name 'no_such_property'."; + $this->assertEquals( $expected, $e->getMessage() ); + } + } + + public function testPropertiesSet() + { + $url = new ezcUrl(); + $url->scheme = 'http'; + $url->user = 'user'; + $url->pass = 'pass'; + $url->host = 'www.example.com'; + $url->port = 82; + $url->path = array( 'content', 'view' ); + $url->query = array( 'products' => 10, 'mode' => 'print' ); + $url->fragment = 'cat'; + $expected = "http://user:pass@www.example.com:82/content/view?products=10&mode=print#cat"; + $this->assertEquals( $expected, $url->buildUrl() ); + } + + public function testPropertiesSetInvalid() + { + $url = new ezcUrl( 'http://www.example.com' ); + try + { + $data = $url->no_such_property; + $this->fail( 'Expected exception was not thrown' ); + } + catch ( ezcBasePropertyNotFoundException $e ) + { + $expected = "No such property name 'no_such_property'."; + $this->assertEquals( $expected, $e->getMessage() ); + } + + try + { + $url->configuration = "value"; + $this->fail( "Expected exception was not thrown." ); + } + catch ( ezcBaseValueException $e ) + { + $this->assertEquals( "The value 'value' that you were trying to assign to setting 'configuration' is invalid. Allowed values are: instance of ezcUrlConfiguration.", $e->getMessage() ); + } + } + + public function testConstructor() + { + $url = new ezcUrl( 'http://www.example.com/content/view/products/10/mode/print' ); + $expected = 'http://www.example.com/content/view/products/10/mode/print'; + $this->assertEquals( $expected, $url->buildUrl() ); + } + + public function testBuildUrl() + { + $urlStrings = array(); + $urlStrings[] = 'http://www.example.com'; + $urlStrings[] = 'http://www.example.com/mydir/index.php'; + $urlStrings[] = 'http://www.example.com/mydir/index.php/other/stuff#cat'; + $urlStrings[] = 'http://www.example.com:82/mydir/index.php/other/stuff#cat'; + $urlStrings[] = 'http://user:password@www.example.com:82/mydir/index.php/other/stuff#cat'; + $urlStrings[] = 'http://user:password@www.example.com:82/mydir/index.php/other/stuff?me=you&arr[0]=yes&arr[1]=no#cat'; + + foreach ( $urlStrings as $urlString ) + { + $url = new ezcUrl( $urlString ); + $this->assertEquals( $urlString, urldecode( $url->buildUrl() ) ); + $this->assertEquals( $urlString, urldecode( $url->__toString() ) ); + } + } + + public function testBuildUrlWithBasedir() + { + $urlCfg = new ezcUrlConfiguration(); + $urlCfg->basedir = 'mydir/shop'; + $urlCfg->script = 'index.php'; + $urlCfg->addOrderedParameter( 'section' ); + $urlCfg->addOrderedParameter( 'module' ); + $urlCfg->addOrderedParameter( 'view' ); + $urlCfg->addOrderedParameter( 'content' ); + + $url = new ezcUrl( 'http://www.example.com/mydir/shop/index.php/doc/components/view/trunk', $urlCfg ); + $expected = 'http://www.example.com/mydir/shop/doc/components/view/trunk'; + $this->assertEquals( $expected, $url->buildUrl() ); + } + + public function testIsRelativeFalse() + { + $url = new ezcUrl( 'http://www.example.com/blah/index.php' ); + $this->assertEquals( false, $url->isRelative() ); + } + + public function testIsRelativeTrue() + { + $url = new ezcUrl( 'blah/index.php' ); + $this->assertEquals( true, $url->isRelative() ); + } + + public function testGetQuery() + { + $url = new ezcUrl( 'http://www.example.com/mydir/shop?content=view&products=10&mode=print' ); + $expected = array( 'content' => 'view', 'products' => '10', 'mode' => 'print' ); + $this->assertEquals( $expected, $url->getQuery() ); + } + + public function testGetQueryEmpty() + { + $url = new ezcUrl( 'http://www.example.com/mydir/shop' ); + $expected = array(); + $this->assertEquals( $expected, $url->getQuery() ); + } + + public function testSetQuery() + { + $url = new ezcUrl( 'http://www.example.com/mydir/shop' ); + $url->setQuery( array( 'content' => 'view', 'products' => '10', 'mode' => 'print' ) ); + $expected = 'http://www.example.com/mydir/shop?content=view&products=10&mode=print'; + $this->assertEquals( $expected, $url->buildUrl() ); + } + + public function testGetOrderedParameter() + { + $urlCfg = new ezcUrlConfiguration(); + $urlCfg->addOrderedParameter( 'section' ); + $urlCfg->addOrderedParameter( 'module' ); + $urlCfg->addOrderedParameter( 'view' ); + $urlCfg->addOrderedParameter( 'branch' ); + + $url = new ezcUrl( 'http://www.example.com/doc/components/view/trunk', $urlCfg ); + $this->assertEquals( 'doc', $url->getParam( 'section' ) ); + $this->assertEquals( 'components', $url->getParam( 'module' ) ); + $this->assertEquals( 'view', $url->getParam( 'view' ) ); + $this->assertEquals( 'trunk', $url->getParam( 'branch' ) ); + } + + public function testGetOrderedParameterEmpty() + { + $urlCfg = new ezcUrlConfiguration(); + $urlCfg->addOrderedParameter( 'section' ); + $urlCfg->addOrderedParameter( 'module' ); + $urlCfg->addOrderedParameter( 'view' ); + $urlCfg->addOrderedParameter( 'branch' ); + + $url = new ezcUrl( 'http://www.example.com', $urlCfg ); + $this->assertEquals( null, $url->getParam( 'section' ) ); + $this->assertEquals( null, $url->getParam( 'module' ) ); + $this->assertEquals( null, $url->getParam( 'view' ) ); + $this->assertEquals( null, $url->getParam( 'branch' ) ); + } + + public function testGetOrderedParameterInvalid() + { + $urlCfg = new ezcUrlConfiguration(); + + $url = new ezcUrl( 'http://www.example.com', $urlCfg ); + try + { + $url->getParam( 'section' ); + $this->fail( 'Expected exception was not thrown.' ); + } + catch ( ezcUrlInvalidParameterException $e ) + { + $expected = "The parameter 'section' could not be set/get because it is not defined in the configuration."; + $this->assertEquals( $expected, $e->getMessage() ); + } + } + + public function testGetOrderedParameterNoCfg() + { + $url = new ezcUrl( 'http://www.example.com' ); + try + { + $url->getParam( 'section' ); + $this->fail( 'Expected exception was not thrown.' ); + } + catch ( ezcUrlNoConfigurationException $e ) + { + $expected = "The parameter 'section' could not be set/get because the url doesn't have a configuration defined."; + $this->assertEquals( $expected, $e->getMessage() ); + } + } + + public function testSetOrderedParameter() + { + $urlCfg = new ezcUrlConfiguration(); + $urlCfg->addOrderedParameter( 'section' ); + $urlCfg->addOrderedParameter( 'module' ); + $urlCfg->addOrderedParameter( 'view' ); + $urlCfg->addOrderedParameter( 'branch' ); + + $url = new ezcUrl( 'http://www.example.com/doc/components/view/trunk', $urlCfg ); + $expected = 'http://www.example.com/bugs/components/view/trunk'; + $url->setParam( 'section', 'bugs' ); + $this->assertEquals( $expected, $url->buildUrl() ); + } + + public function testSetOrderedParameterInvalid() + { + $urlCfg = new ezcUrlConfiguration(); + + $url = new ezcUrl( 'http://www.example.com', $urlCfg ); + try + { + $url->setParam( 'section', 'value' ); + $this->fail( 'Expected exception was not thrown.' ); + } + catch ( ezcUrlInvalidParameterException $e ) + { + $expected = "The parameter 'section' could not be set/get because it is not defined in the configuration."; + $this->assertEquals( $expected, $e->getMessage() ); + } + } + + public function testSetOrderedParameterNoCfg() + { + $url = new ezcUrl( 'http://www.example.com' ); + try + { + $url->setParam( 'section', 'doc' ); + $this->fail( 'Expected exception was not thrown.' ); + } + catch ( ezcUrlNoConfigurationException $e ) + { + $expected = "The parameter 'section' could not be set/get because the url doesn't have a configuration defined."; + $this->assertEquals( $expected, $e->getMessage() ); + } + } + + public function testGetUnorderedParameterSingle() + { + $urlCfg = new ezcUrlConfiguration(); + $urlCfg->addUnorderedParameter( 'file' ); + + $url = new ezcUrl( 'http://www.example.com/doc/components/view/trunk/(file)/classtrees_Base.html', $urlCfg ); + $this->assertEquals( 'classtrees_Base.html', $url->getParam( 'file' ) ); + } + + public function testGetUnorderedParameterMultiple() + { + $urlCfg = new ezcUrlConfiguration(); + $urlCfg->addUnorderedParameter( 'file', ezcUrlConfiguration::MULTIPLE_ARGUMENTS ); + + $url = new ezcUrl( 'http://www.example.com/doc/components/view/trunk/(file)/Base/ezcBase.html', $urlCfg ); + $this->assertEquals( array( 'Base', 'ezcBase.html' ), $url->getParam( 'file' ) ); + } + + public function testGetUnorderedParameterEmpty() + { + $urlCfg = new ezcUrlConfiguration(); + $urlCfg->addUnorderedParameter( 'file' ); + + $url = new ezcUrl( 'http://www.example.com/doc/components/view/trunk/(file)', $urlCfg ); + $this->assertEquals( null, $url->getParam( 'file' ) ); + } + + public function testGetUnorderedParameterInvalid() + { + $urlCfg = new ezcUrlConfiguration(); + + $url = new ezcUrl( 'http://www.example.com', $urlCfg ); + try + { + $url->getParam( 'file' ); + $this->fail( 'Expected exception was not thrown.' ); + } + catch ( ezcUrlInvalidParameterException $e ) + { + $expected = "The parameter 'file' could not be set/get because it is not defined in the configuration."; + $this->assertEquals( $expected, $e->getMessage() ); + } + } + + public function testGetUnorderedParameterNoCfg() + { + $url = new ezcUrl( 'http://www.example.com' ); + try + { + $url->getParam( 'file' ); + $this->fail( 'Expected exception was not thrown.' ); + } + catch ( ezcUrlNoConfigurationException $e ) + { + $expected = "The parameter 'file' could not be set/get because the url doesn't have a configuration defined."; + $this->assertEquals( $expected, $e->getMessage() ); + } + } + + public function testSetUnorderedParameterSingle() + { + $urlCfg = new ezcUrlConfiguration(); + $urlCfg->addUnorderedParameter( 'file' ); + $urlCfg->addOrderedParameter( 'section' ); + $urlCfg->addOrderedParameter( 'module' ); + $urlCfg->addOrderedParameter( 'view' ); + $urlCfg->addOrderedParameter( 'content' ); + + $url = new ezcUrl( 'http://www.example.com/doc/components/view/trunk', $urlCfg ); + $expected = 'http://www.example.com/doc/components/view/trunk/(file)/Base'; + $url->setParam( 'file', 'Base' ); + $this->assertEquals( $expected, $url->buildUrl() ); + } + + public function testSetUnorderedParameterMultiple() + { + $urlCfg = new ezcUrlConfiguration(); + $urlCfg->addUnorderedParameter( 'file', ezcUrlConfiguration::MULTIPLE_ARGUMENTS ); + $urlCfg->addOrderedParameter( 'section' ); + $urlCfg->addOrderedParameter( 'module' ); + $urlCfg->addOrderedParameter( 'view' ); + $urlCfg->addOrderedParameter( 'content' ); + + $url = new ezcUrl( 'http://www.example.com/doc/components/view/trunk', $urlCfg ); + $expected = 'http://www.example.com/doc/components/view/trunk/(file)/Base/ezcBase.html'; + $url->setParam( 'file', array( 'Base', 'ezcBase.html' ) ); + $this->assertEquals( $expected, $url->buildUrl() ); + } + + public function testSetUnorderedParameterInvalid() + { + $urlCfg = new ezcUrlConfiguration(); + + $url = new ezcUrl( 'http://www.example.com', $urlCfg ); + try + { + $url->setParam( 'file', array( 'Base', 'ezcBase.html' ) ); + $this->fail( 'Expected exception was not thrown.' ); + } + catch ( ezcUrlInvalidParameterException $e ) + { + $expected = "The parameter 'file' could not be set/get because it is not defined in the configuration."; + $this->assertEquals( $expected, $e->getMessage() ); + } + } + + public function testSetUnorderedParameterNoCfg() + { + $url = new ezcUrl( 'http://www.example.com' ); + try + { + $url->setParam( 'file', array( 'Base', 'ezcBase.html' ) ); + $this->fail( 'Expected exception was not thrown.' ); + } + catch ( ezcUrlNoConfigurationException $e ) + { + $expected = "The parameter 'file' could not be set/get because the url doesn't have a configuration defined."; + $this->assertEquals( $expected, $e->getMessage() ); + } + } + + public function testRemoveOrderedParameter() + { + $urlCfg = new ezcUrlConfiguration(); + $urlCfg->addOrderedParameter( 'section' ); + $urlCfg->addOrderedParameter( 'module' ); + $urlCfg->addOrderedParameter( 'view' ); + + $url = new ezcUrl( 'http://www.example.com/doc/components', $urlCfg ); + $this->assertEquals( array( 'section' => 0, 'module' => 1, 'view' => 2 ), $url->configuration->orderedParameters ); + $this->assertEquals( 'doc', $url->getParam( 'section' ) ); + $this->assertEquals( 'components', $url->getParam( 'module' ) ); + + $url->configuration->removeOrderedParameter( 'view' ); + $this->assertEquals( array( 'section' => 0, 'module' => 1 ), $url->configuration->orderedParameters ); + + try + { + $this->assertEquals( null, $url->getParam( 'view' ) ); + $this->fail( 'Expected exception was not thrown.' ); + } + catch ( ezcUrlInvalidParameterException $e ) + { + $expected = "The parameter 'view' could not be set/get because it is not defined in the configuration."; + $this->assertEquals( $expected, $e->getMessage() ); + } + + // try removing again - nothing bad should happen + $url->configuration->removeOrderedParameter( 'view' ); + try + { + $this->assertEquals( null, $url->getParam( 'view' ) ); + $this->fail( 'Expected exception was not thrown.' ); + } + catch ( ezcUrlInvalidParameterException $e ) + { + $expected = "The parameter 'view' could not be set/get because it is not defined in the configuration."; + $this->assertEquals( $expected, $e->getMessage() ); + } + } + + public function testRemoveUnorderedParameter() + { + $urlCfg = new ezcUrlConfiguration(); + $urlCfg->addOrderedParameter( 'section' ); + $urlCfg->addOrderedParameter( 'module' ); + $urlCfg->addUnorderedParameter( 'file', ezcUrlConfiguration::MULTIPLE_ARGUMENTS ); + + $url = new ezcUrl( 'http://www.example.com/doc/components/(file)/Base/ezcBase.html', $urlCfg ); + $this->assertEquals( array( 'file' => 2 ), $url->configuration->unorderedParameters ); + $this->assertEquals( 'doc', $url->getParam( 'section' ) ); + $this->assertEquals( 'components', $url->getParam( 'module' ) ); + $this->assertEquals( array( 'Base', 'ezcBase.html' ), $url->getParam( 'file' ) ); + + $url->configuration->removeUnorderedParameter( 'file' ); + $this->assertEquals( array(), $url->configuration->unorderedParameters ); + + try + { + $this->assertEquals( null, $url->getParam( 'file' ) ); + $this->fail( 'Expected exception was not thrown.' ); + } + catch ( ezcUrlInvalidParameterException $e ) + { + $expected = "The parameter 'file' could not be set/get because it is not defined in the configuration."; + $this->assertEquals( $expected, $e->getMessage() ); + } + + // try removing again - nothing bad should happen + $url->configuration->removeUnorderedParameter( 'file' ); + try + { + $this->assertEquals( null, $url->getParam( 'file' ) ); + $this->fail( 'Expected exception was not thrown.' ); + } + catch ( ezcUrlInvalidParameterException $e ) + { + $expected = "The parameter 'file' could not be set/get because it is not defined in the configuration."; + $this->assertEquals( $expected, $e->getMessage() ); + } + } + + public function testBuildUrlWithBasedirAppendedSlash() + { + $urlCfg = new ezcUrlConfiguration(); + $urlCfg->basedir = 'mydir/shop/'; + $urlCfg->script = 'index.php'; + $urlCfg->addOrderedParameter( 'section' ); + $urlCfg->addOrderedParameter( 'module' ); + $urlCfg->addOrderedParameter( 'view' ); + $urlCfg->addOrderedParameter( 'content' ); + + $url = new ezcUrl( 'http://www.example.com/mydir/shop/index.php/doc/components/view/trunk', $urlCfg ); + $expected = 'http://www.example.com/mydir/shop/doc/components/view/trunk'; + $this->assertEquals( $expected, $url->buildUrl() ); + } + + public function testBuildUrlWithAbsoluteBasedir() + { + $urlCfg = new ezcUrlConfiguration(); + $urlCfg->basedir = '/mydir/shop'; + $urlCfg->script = 'index.php'; + $urlCfg->addOrderedParameter( 'section' ); + $urlCfg->addOrderedParameter( 'module' ); + $urlCfg->addOrderedParameter( 'view' ); + $urlCfg->addOrderedParameter( 'content' ); + + $url = new ezcUrl( 'http://www.example.com/mydir/shop/index.php/doc/components/view/trunk', $urlCfg ); + $expected = 'http://www.example.com/mydir/shop/doc/components/view/trunk'; + $this->assertEquals( $expected, $url->buildUrl() ); + } + + public function testBuildUrlWithAbsoluteBasedirAppendedSlash() + { + $urlCfg = new ezcUrlConfiguration(); + $urlCfg->basedir = '/mydir/shop/'; + $urlCfg->script = 'index.php'; + $urlCfg->addOrderedParameter( 'section' ); + $urlCfg->addOrderedParameter( 'module' ); + $urlCfg->addOrderedParameter( 'view' ); + $urlCfg->addOrderedParameter( 'content' ); + + $url = new ezcUrl( 'http://www.example.com/mydir/shop/index.php/doc/components/view/trunk', $urlCfg ); + $expected = 'http://www.example.com/mydir/shop/doc/components/view/trunk'; + $this->assertEquals( $expected, $url->buildUrl() ); + } + + public function testGetOrderedParameterBasedir() + { + $urlCfg = new ezcUrlConfiguration(); + $urlCfg->basedir = 'mydir/'; + $urlCfg->script = 'index.php'; + $urlCfg->addOrderedParameter( 'section' ); + $urlCfg->addOrderedParameter( 'module' ); + $urlCfg->addOrderedParameter( 'view' ); + $urlCfg->addOrderedParameter( 'branch' ); + + $url = new ezcUrl( 'http://www.example.com/mydir/index.php/doc/components/view/trunk', $urlCfg ); + $this->assertEquals( 'doc', $url->getParam( 'section' ) ); + $this->assertEquals( 'components', $url->getParam( 'module' ) ); + $this->assertEquals( 'view', $url->getParam( 'view' ) ); + $this->assertEquals( 'trunk', $url->getParam( 'branch' ) ); + } + + public function testGetOrderedParameterAbsoluteBasedir() + { + $urlCfg = new ezcUrlConfiguration(); + $urlCfg->basedir = '/mydir/'; + $urlCfg->script = 'index.php'; + $urlCfg->addOrderedParameter( 'section' ); + $urlCfg->addOrderedParameter( 'module' ); + $urlCfg->addOrderedParameter( 'view' ); + $urlCfg->addOrderedParameter( 'branch' ); + + $url = new ezcUrl( 'http://www.example.com/mydir/index.php/doc/components/view/trunk', $urlCfg ); + $this->assertEquals( 'doc', $url->getParam( 'section' ) ); + $this->assertEquals( 'components', $url->getParam( 'module' ) ); + $this->assertEquals( 'view', $url->getParam( 'view' ) ); + $this->assertEquals( 'trunk', $url->getParam( 'branch' ) ); + } + + public function testIsSet() + { + $url = new ezcUrl( 'http://www.example.com' ); + $this->assertEquals( true, isset( $url->host ) ); + $this->assertEquals( false, isset( $url->user ) ); + $this->assertEquals( false, isset( $url->pass ) ); + $this->assertEquals( false, isset( $url->port ) ); + $this->assertEquals( true, isset( $url->scheme ) ); + $this->assertEquals( false, isset( $url->fragment ) ); + $this->assertEquals( true, isset( $url->path ) ); + $this->assertEquals( true, isset( $url->basedir ) ); + $this->assertEquals( true, isset( $url->script ) ); + $this->assertEquals( true, isset( $url->params ) ); + $this->assertEquals( true, isset( $url->uparams ) ); + $this->assertEquals( true, isset( $url->query ) ); + $this->assertEquals( false, isset( $url->configuration ) ); + $this->assertEquals( false, isset( $url->no_such_property ) ); + } + + public static function suite() + { + return new PHPUnit_Framework_TestSuite( "ezcUrlTest" ); + } +} +?> diff --git a/libs/jquery/jquery.js b/libs/jquery/jquery.js new file mode 100644 index 0000000000..a0702cf6ee --- /dev/null +++ b/libs/jquery/jquery.js @@ -0,0 +1,2508 @@ +(function(){ +/* + * jQuery 1.1.4 - New Wave Javascript + * + * Copyright (c) 2007 John Resig (jquery.com) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * $Date: 2007-08-23 21:49:27 -0400 (Thu, 23 Aug 2007) $ + * $Rev: 2862 $ + */ +// Map over jQuery in case of overwrite +if ( typeof jQuery != "undefined" ) + var _jQuery = jQuery; + +var jQuery = window.jQuery = function(a,c) { + // If the context is global, return a new object + if ( window == this || !this.init ) + return new jQuery(a,c); + + return this.init(a,c); +}; + +// Map over the $ in case of overwrite +if ( typeof $ != "undefined" ) + var _$ = $; + +// Map the jQuery namespace to the '$' one +window.$ = jQuery; + +var quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/; + +jQuery.fn = jQuery.prototype = { + init: function(a,c) { + // Make sure that a selection was provided + a = a || document; + + // Handle HTML strings + if ( typeof a == "string" ) { + var m = quickExpr.exec(a); + if ( m && (m[1] || !c) ) { + // HANDLE: $(html) -> $(array) + if ( m[1] ) + a = jQuery.clean( [ m[1] ] ); + + // HANDLE: $("#id") + else { + var tmp = document.getElementById( m[3] ); + if ( tmp ) + // Handle the case where IE and Opera return items + // by name instead of ID + if ( tmp.id != m[3] ) + return jQuery().find( a ); + else { + this[0] = tmp; + this.length = 1; + return this; + } + else + a = []; + } + + // HANDLE: $(expr) + } else + return new jQuery( c ).find( a ); + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction(a) ) + return new jQuery(document)[ jQuery.fn.ready ? "ready" : "load" ]( a ); + + return this.setArray( + // HANDLE: $(array) + a.constructor == Array && a || + + // HANDLE: $(arraylike) + // Watch for when an array-like object is passed as the selector + (a.jquery || a.length && a != window && !a.nodeType && a[0] != undefined && a[0].nodeType) && jQuery.makeArray( a ) || + + // HANDLE: $(*) + [ a ] ); + }, + jquery: "1.1.4", + + size: function() { + return this.length; + }, + + length: 0, + + get: function( num ) { + return num == undefined ? + + // Return a 'clean' array + jQuery.makeArray( this ) : + + // Return just the object + this[num]; + }, + pushStack: function( a ) { + var ret = jQuery(a); + ret.prevObject = this; + return ret; + }, + setArray: function( a ) { + this.length = 0; + Array.prototype.push.apply( this, a ); + return this; + }, + each: function( fn, args ) { + return jQuery.each( this, fn, args ); + }, + index: function( obj ) { + var pos = -1; + this.each(function(i){ + if ( this == obj ) pos = i; + }); + return pos; + }, + + attr: function( key, value, type ) { + var obj = key; + + // Look for the case where we're accessing a style value + if ( key.constructor == String ) + if ( value == undefined ) + return this.length && jQuery[ type || "attr" ]( this[0], key ) || undefined; + else { + obj = {}; + obj[ key ] = value; + } + + // Check to see if we're setting style values + return this.each(function(index){ + // Set all the styles + for ( var prop in obj ) + jQuery.attr( + type ? this.style : this, + prop, jQuery.prop(this, obj[prop], type, index, prop) + ); + }); + }, + + css: function( key, value ) { + return this.attr( key, value, "curCSS" ); + }, + + text: function(e) { + if ( typeof e != "object" && e != null ) + return this.empty().append( document.createTextNode( e ) ); + + var t = ""; + jQuery.each( e || this, function(){ + jQuery.each( this.childNodes, function(){ + if ( this.nodeType != 8 ) + t += this.nodeType != 1 ? + this.nodeValue : jQuery.fn.text([ this ]); + }); + }); + return t; + }, + + wrap: function() { + // The elements to wrap the target around + var a, args = arguments; + + // Wrap each of the matched elements individually + return this.each(function(){ + if ( !a ) + a = jQuery.clean(args, this.ownerDocument); + + // Clone the structure that we're using to wrap + var b = a[0].cloneNode(true); + + // Insert it before the element to be wrapped + this.parentNode.insertBefore( b, this ); + + // Find the deepest point in the wrap structure + while ( b.firstChild ) + b = b.firstChild; + + // Move the matched element to within the wrap structure + b.appendChild( this ); + }); + }, + append: function() { + return this.domManip(arguments, true, 1, function(a){ + this.appendChild( a ); + }); + }, + prepend: function() { + return this.domManip(arguments, true, -1, function(a){ + this.insertBefore( a, this.firstChild ); + }); + }, + before: function() { + return this.domManip(arguments, false, 1, function(a){ + this.parentNode.insertBefore( a, this ); + }); + }, + after: function() { + return this.domManip(arguments, false, -1, function(a){ + this.parentNode.insertBefore( a, this.nextSibling ); + }); + }, + end: function() { + return this.prevObject || jQuery([]); + }, + find: function(t) { + var data = jQuery.map(this, function(a){ return jQuery.find(t,a); }); + return this.pushStack( /[^+>] [^+>]/.test( t ) || t.indexOf("..") > -1 ? + jQuery.unique( data ) : data ); + }, + clone: function(deep) { + deep = deep != undefined ? deep : true; + var $this = this.add(this.find("*")); + if (jQuery.browser.msie) { + // Need to remove events on the element and its descendants + $this.each(function() { + this._$events = {}; + for (var type in this.$events) + this._$events[type] = jQuery.extend({},this.$events[type]); + }).unbind(); + } + + // Do the clone + var r = this.pushStack( jQuery.map( this, function(a){ + return a.cloneNode( deep ); + }) ); + + if (jQuery.browser.msie) { + $this.each(function() { + // Add the events back to the original and its descendants + var events = this._$events; + for (var type in events) + for (var handler in events[type]) + jQuery.event.add(this, type, events[type][handler], events[type][handler].data); + this._$events = null; + }); + } + + // copy form values over + if (deep) { + var inputs = r.add(r.find('*')).filter('select,input[@type=checkbox]'); + $this.filter('select,input[@type=checkbox]').each(function(i) { + if (this.selectedIndex) + inputs[i].selectedIndex = this.selectedIndex; + if (this.checked) + inputs[i].checked = true; + }); + } + + // Return the cloned set + return r; + }, + + filter: function(t) { + return this.pushStack( + jQuery.isFunction( t ) && + jQuery.grep(this, function(el, index){ + return t.apply(el, [index]); + }) || + + jQuery.multiFilter(t,this) ); + }, + + not: function(t) { + return this.pushStack( + t.constructor == String && + jQuery.multiFilter(t, this, true) || + + jQuery.grep(this, function(a) { + return ( t.constructor == Array || t.jquery ) + ? jQuery.inArray( a, t ) < 0 + : a != t; + }) + ); + }, + + add: function(t) { + return this.pushStack( jQuery.merge( + this.get(), + t.constructor == String ? + jQuery(t).get() : + t.length != undefined && (!t.nodeName || t.nodeName == "FORM") ? + t : [t] ) + ); + }, + is: function(expr) { + return expr ? jQuery.multiFilter(expr,this).length > 0 : false; + }, + + val: function( val ) { + return val == undefined ? + ( this.length ? this[0].value : null ) : + this.attr( "value", val ); + }, + + html: function( val ) { + return val == undefined ? + ( this.length ? this[0].innerHTML : null ) : + this.empty().append( val ); + }, + + slice: function() { + return this.pushStack( Array.prototype.slice.apply( this, arguments ) ); + }, + domManip: function(args, table, dir, fn){ + var clone = this.length > 1, a; + + return this.each(function(){ + if ( !a ) { + a = jQuery.clean(args, this.ownerDocument); + if ( dir < 0 ) + a.reverse(); + } + + var obj = this; + + if ( table && jQuery.nodeName(this, "table") && jQuery.nodeName(a[0], "tr") ) + obj = this.getElementsByTagName("tbody")[0] || this.appendChild(document.createElement("tbody")); + + jQuery.each( a, function(){ + if ( jQuery.nodeName(this, "script") ) { + if ( this.src ) + jQuery.ajax({ url: this.src, async: false, dataType: "script" }); + else + jQuery.globalEval( this.text || this.textContent || this.innerHTML || "" ); + } else + fn.apply( obj, [ clone ? this.cloneNode(true) : this ] ); + }); + }); + } +}; + +jQuery.extend = jQuery.fn.extend = function() { + // copy reference to target object + var target = arguments[0] || {}, a = 1, al = arguments.length, deep = false; + + // Handle a deep copy situation + if ( target.constructor == Boolean ) { + deep = target; + target = arguments[1] || {}; + } + + // extend jQuery itself if only one argument is passed + if ( al == 1 ) { + target = this; + a = 0; + } + + var prop; + + for ( ; a < al; a++ ) + // Only deal with non-null/undefined values + if ( (prop = arguments[a]) != null ) + // Extend the base object + for ( var i in prop ) { + // Prevent never-ending loop + if ( target == prop[i] ) + continue; + + // Recurse if we're merging object values + if ( deep && typeof prop[i] == 'object' && target[i] ) + jQuery.extend( target[i], prop[i] ); + + // Don't bring in undefined values + else if ( prop[i] != undefined ) + target[i] = prop[i]; + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + noConflict: function(deep) { + window.$ = _$; + if ( deep ) + window.jQuery = _jQuery; + return jQuery; + }, + + // This may seem like some crazy code, but trust me when I say that this + // is the only cross-browser way to do this. --John + isFunction: function( fn ) { + return !!fn && typeof fn != "string" && !fn.nodeName && + fn.constructor != Array && /function/i.test( fn + "" ); + }, + + // check if an element is in a XML document + isXMLDoc: function(elem) { + return elem.documentElement && !elem.body || + elem.tagName && elem.ownerDocument && !elem.ownerDocument.body; + }, + + // Evalulates a script in a global context + // Evaluates Async. in Safari 2 :-( + globalEval: function( data ) { + data = jQuery.trim( data ); + if ( data ) { + if ( window.execScript ) + window.execScript( data ); + else if ( jQuery.browser.safari ) + // safari doesn't provide a synchronous global eval + window.setTimeout( data, 0 ); + else + eval.call( window, data ); + } + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase(); + }, + // args is for internal usage only + each: function( obj, fn, args ) { + if ( args ) { + if ( obj.length == undefined ) + for ( var i in obj ) + fn.apply( obj[i], args ); + else + for ( var i = 0, ol = obj.length; i < ol; i++ ) + if ( fn.apply( obj[i], args ) === false ) break; + + // A special, fast, case for the most common use of each + } else { + if ( obj.length == undefined ) + for ( var i in obj ) + fn.call( obj[i], i, obj[i] ); + else + for ( var i = 0, ol = obj.length, val = obj[0]; + i < ol && fn.call(val,i,val) !== false; val = obj[++i] ){} + } + + return obj; + }, + + prop: function(elem, value, type, index, prop){ + // Handle executable functions + if ( jQuery.isFunction( value ) ) + value = value.call( elem, [index] ); + + // exclude the following css properties to add px + var exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i; + + // Handle passing in a number to a CSS property + return value && value.constructor == Number && type == "curCSS" && !exclude.test(prop) ? + value + "px" : + value; + }, + + className: { + // internal only, use addClass("class") + add: function( elem, c ){ + jQuery.each( (c || "").split(/\s+/), function(i, cur){ + if ( !jQuery.className.has( elem.className, cur ) ) + elem.className += ( elem.className ? " " : "" ) + cur; + }); + }, + + // internal only, use removeClass("class") + remove: function( elem, c ){ + elem.className = c != undefined ? + jQuery.grep( elem.className.split(/\s+/), function(cur){ + return !jQuery.className.has( c, cur ); + }).join(" ") : ""; + }, + + // internal only, use is(".class") + has: function( t, c ) { + return jQuery.inArray( c, (t.className || t).toString().split(/\s+/) ) > -1; + } + }, + swap: function(e,o,f) { + for ( var i in o ) { + e.style["old"+i] = e.style[i]; + e.style[i] = o[i]; + } + f.apply( e, [] ); + for ( var i in o ) + e.style[i] = e.style["old"+i]; + }, + + css: function(e,p) { + if ( p == "height" || p == "width" ) { + var old = {}, oHeight, oWidth, d = ["Top","Bottom","Right","Left"]; + + jQuery.each( d, function(){ + old["padding" + this] = 0; + old["border" + this + "Width"] = 0; + }); + + jQuery.swap( e, old, function() { + if ( jQuery(e).is(':visible') ) { + oHeight = e.offsetHeight; + oWidth = e.offsetWidth; + } else { + e = jQuery(e.cloneNode(true)) + .find(":radio").removeAttr("checked").end() + .css({ + visibility: "hidden", position: "absolute", display: "block", right: "0", left: "0" + }).appendTo(e.parentNode)[0]; + + var parPos = jQuery.css(e.parentNode,"position") || "static"; + if ( parPos == "static" ) + e.parentNode.style.position = "relative"; + + oHeight = e.clientHeight; + oWidth = e.clientWidth; + + if ( parPos == "static" ) + e.parentNode.style.position = "static"; + + e.parentNode.removeChild(e); + } + }); + + return p == "height" ? oHeight : oWidth; + } + + return jQuery.curCSS( e, p ); + }, + + curCSS: function(elem, prop, force) { + var ret, stack = [], swap = []; + + // A helper method for determining if an element's values are broken + function color(a){ + if ( !jQuery.browser.safari ) + return false; + + var ret = document.defaultView.getComputedStyle(a,null); + return !ret || ret.getPropertyValue("color") == ""; + } + + if (prop == "opacity" && jQuery.browser.msie) { + ret = jQuery.attr(elem.style, "opacity"); + return ret == "" ? "1" : ret; + } + + if (prop.match(/float/i)) + prop = styleFloat; + + if (!force && elem.style[prop]) + ret = elem.style[prop]; + + else if (document.defaultView && document.defaultView.getComputedStyle) { + + if (prop.match(/float/i)) + prop = "float"; + + prop = prop.replace(/([A-Z])/g,"-$1").toLowerCase(); + var cur = document.defaultView.getComputedStyle(elem, null); + + if ( cur && !color(elem) ) + ret = cur.getPropertyValue(prop); + + // If the element isn't reporting its values properly in Safari + // then some display: none elements are involved + else { + // Locate all of the parent display: none elements + for ( var a = elem; a && color(a); a = a.parentNode ) + stack.unshift(a); + + // Go through and make them visible, but in reverse + // (It would be better if we knew the exact display type that they had) + for ( a = 0; a < stack.length; a++ ) + if ( color(stack[a]) ) { + swap[a] = stack[a].style.display; + stack[a].style.display = "block"; + } + + // Since we flip the display style, we have to handle that + // one special, otherwise get the value + ret = prop == "display" && swap[stack.length-1] != null ? + "none" : + document.defaultView.getComputedStyle(elem,null).getPropertyValue(prop) || ""; + + // Finally, revert the display styles back + for ( a = 0; a < swap.length; a++ ) + if ( swap[a] != null ) + stack[a].style.display = swap[a]; + } + + if ( prop == "opacity" && ret == "" ) + ret = "1"; + + } else if (elem.currentStyle) { + var newProp = prop.replace(/\-(\w)/g,function(m,c){return c.toUpperCase();}); + ret = elem.currentStyle[prop] || elem.currentStyle[newProp]; + } + + return ret; + }, + + clean: function(a, doc) { + var r = []; + doc = doc || document; + + jQuery.each( a, function(i,arg){ + if ( !arg ) return; + + if ( arg.constructor == Number ) + arg = arg.toString(); + + // Convert html string into DOM nodes + if ( typeof arg == "string" ) { + // Trim whitespace, otherwise indexOf won't work as expected + var s = jQuery.trim(arg).toLowerCase(), div = doc.createElement("div"), tb = []; + + var wrap = + // option or optgroup + !s.indexOf("<opt") && + [1, "<select>", "</select>"] || + + !s.indexOf("<leg") && + [1, "<fieldset>", "</fieldset>"] || + + s.match(/^<(thead|tbody|tfoot|colg|cap)/) && + [1, "<table>", "</table>"] || + + !s.indexOf("<tr") && + [2, "<table><tbody>", "</tbody></table>"] || + + // <thead> matched above + (!s.indexOf("<td") || !s.indexOf("<th")) && + [3, "<table><tbody><tr>", "</tr></tbody></table>"] || + + !s.indexOf("<col") && + [2, "<table><tbody></tbody><colgroup>", "</colgroup></table>"] || + + // IE can't serialize <link> and <script> tags normally + jQuery.browser.msie && + [1, "div<div>", "</div>"] || + + [0,"",""]; + + // Go to html and back, then peel off extra wrappers + div.innerHTML = wrap[1] + arg + wrap[2]; + + // Move to the right depth + while ( wrap[0]-- ) + div = div.lastChild; + + // Remove IE's autoinserted <tbody> from table fragments + if ( jQuery.browser.msie ) { + + // String was a <table>, *may* have spurious <tbody> + if ( !s.indexOf("<table") && s.indexOf("<tbody") < 0 ) + tb = div.firstChild && div.firstChild.childNodes; + + // String was a bare <thead> or <tfoot> + else if ( wrap[1] == "<table>" && s.indexOf("<tbody") < 0 ) + tb = div.childNodes; + + for ( var n = tb.length-1; n >= 0 ; --n ) + if ( jQuery.nodeName(tb[n], "tbody") && !tb[n].childNodes.length ) + tb[n].parentNode.removeChild(tb[n]); + + // IE completely kills leading whitespace when innerHTML is used + if ( /^\s/.test(arg) ) + div.insertBefore( doc.createTextNode( arg.match(/^\s*/)[0] ), div.firstChild ); + + } + + arg = jQuery.makeArray( div.childNodes ); + } + + if ( 0 === arg.length && (!jQuery.nodeName(arg, "form") && !jQuery.nodeName(arg, "select")) ) + return; + + if ( arg[0] == undefined || jQuery.nodeName(arg, "form") || arg.options ) + r.push( arg ); + else + r = jQuery.merge( r, arg ); + + }); + + return r; + }, + + attr: function(elem, name, value){ + var fix = jQuery.isXMLDoc(elem) ? {} : jQuery.props; + + // Safari mis-reports the default selected property of a hidden option + // Accessing the parent's selectedIndex property fixes it + if ( name == "selected" && jQuery.browser.safari ) + elem.parentNode.selectedIndex; + + // Certain attributes only work when accessed via the old DOM 0 way + if ( fix[name] ) { + if ( value != undefined ) elem[fix[name]] = value; + return elem[fix[name]]; + } else if ( jQuery.browser.msie && name == "style" ) + return jQuery.attr( elem.style, "cssText", value ); + + else if ( value == undefined && jQuery.browser.msie && jQuery.nodeName(elem, "form") && (name == "action" || name == "method") ) + return elem.getAttributeNode(name).nodeValue; + + // IE elem.getAttribute passes even for style + else if ( elem.tagName ) { + + if ( value != undefined ) elem.setAttribute( name, value ); + if ( jQuery.browser.msie && /href|src/.test(name) && !jQuery.isXMLDoc(elem) ) + return elem.getAttribute( name, 2 ); + return elem.getAttribute( name ); + + // elem is actually elem.style ... set the style + } else { + // IE actually uses filters for opacity + if ( name == "opacity" && jQuery.browser.msie ) { + if ( value != undefined ) { + // IE has trouble with opacity if it does not have layout + // Force it by setting the zoom level + elem.zoom = 1; + + // Set the alpha filter to set the opacity + elem.filter = (elem.filter || "").replace(/alpha\([^)]*\)/,"") + + (parseFloat(value).toString() == "NaN" ? "" : "alpha(opacity=" + value * 100 + ")"); + } + + return elem.filter ? + (parseFloat( elem.filter.match(/opacity=([^)]*)/)[1] ) / 100).toString() : ""; + } + name = name.replace(/-([a-z])/ig,function(z,b){return b.toUpperCase();}); + if ( value != undefined ) elem[name] = value; + return elem[name]; + } + }, + trim: function(t){ + return (t||"").replace(/^\s+|\s+$/g, ""); + }, + + makeArray: function( a ) { + var r = []; + + // Need to use typeof to fight Safari childNodes crashes + if ( typeof a != "array" ) + for ( var i = 0, al = a.length; i < al; i++ ) + r.push( a[i] ); + else + r = a.slice( 0 ); + + return r; + }, + + inArray: function( b, a ) { + for ( var i = 0, al = a.length; i < al; i++ ) + if ( a[i] == b ) + return i; + return -1; + }, + merge: function(first, second) { + // We have to loop this way because IE & Opera overwrite the length + // expando of getElementsByTagName + + // Also, we need to make sure that the correct elements are being returned + // (IE returns comment nodes in a '*' query) + if ( jQuery.browser.msie ) { + for ( var i = 0; second[i]; i++ ) + if ( second[i].nodeType != 8 ) + first.push(second[i]); + } else + for ( var i = 0; second[i]; i++ ) + first.push(second[i]); + + return first; + }, + unique: function(first) { + var r = [], num = jQuery.mergeNum++; + + try { + for ( var i = 0, fl = first.length; i < fl; i++ ) + if ( num != first[i].mergeNum ) { + first[i].mergeNum = num; + r.push(first[i]); + } + } catch(e) { + r = first; + } + + return r; + }, + + mergeNum: 0, + grep: function(elems, fn, inv) { + // If a string is passed in for the function, make a function + // for it (a handy shortcut) + if ( typeof fn == "string" ) + fn = eval("false||function(a,i){return " + fn + "}"); + + var result = []; + + // Go through the array, only saving the items + // that pass the validator function + for ( var i = 0, el = elems.length; i < el; i++ ) + if ( !inv && fn(elems[i],i) || inv && !fn(elems[i],i) ) + result.push( elems[i] ); + + return result; + }, + map: function(elems, fn) { + // If a string is passed in for the function, make a function + // for it (a handy shortcut) + if ( typeof fn == "string" ) + fn = eval("false||function(a){return " + fn + "}"); + + var result = []; + + // Go through the array, translating each of the items to their + // new value (or values). + for ( var i = 0, el = elems.length; i < el; i++ ) { + var val = fn(elems[i],i); + + if ( val !== null && val != undefined ) { + if ( val.constructor != Array ) val = [val]; + result = result.concat( val ); + } + } + + return result; + } +}); + +/* + * Whether the W3C compliant box model is being used. + * + * @property + * @name $.boxModel + * @type Boolean + * @cat JavaScript + */ +var userAgent = navigator.userAgent.toLowerCase(); + +// Figure out what browser is being used +jQuery.browser = { + version: (userAgent.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/) || [])[1], + safari: /webkit/.test(userAgent), + opera: /opera/.test(userAgent), + msie: /msie/.test(userAgent) && !/opera/.test(userAgent), + mozilla: /mozilla/.test(userAgent) && !/(compatible|webkit)/.test(userAgent) +}; + +var styleFloat = jQuery.browser.msie ? "styleFloat" : "cssFloat"; + +jQuery.extend({ + // Check to see if the W3C box model is being used + boxModel: !jQuery.browser.msie || document.compatMode == "CSS1Compat", + + styleFloat: jQuery.browser.msie ? "styleFloat" : "cssFloat", + + props: { + "for": "htmlFor", + "class": "className", + "float": styleFloat, + cssFloat: styleFloat, + styleFloat: styleFloat, + innerHTML: "innerHTML", + className: "className", + value: "value", + disabled: "disabled", + checked: "checked", + readonly: "readOnly", + selected: "selected", + maxlength: "maxLength" + } +}); + +jQuery.each({ + parent: "a.parentNode", + parents: "jQuery.parents(a)", + next: "jQuery.nth(a,2,'nextSibling')", + prev: "jQuery.nth(a,2,'previousSibling')", + siblings: "jQuery.sibling(a.parentNode.firstChild,a)", + children: "jQuery.sibling(a.firstChild)" +}, function(i,n){ + jQuery.fn[ i ] = function(a) { + var ret = jQuery.map(this,n); + if ( a && typeof a == "string" ) + ret = jQuery.multiFilter(a,ret); + return this.pushStack( jQuery.unique(ret) ); + }; +}); + +jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after" +}, function(i,n){ + jQuery.fn[ i ] = function(){ + var a = arguments; + return this.each(function(){ + for ( var j = 0, al = a.length; j < al; j++ ) + jQuery(a[j])[n]( this ); + }); + }; +}); + +jQuery.each( { + removeAttr: function( key ) { + jQuery.attr( this, key, "" ); + this.removeAttribute( key ); + }, + addClass: function(c){ + jQuery.className.add(this,c); + }, + removeClass: function(c){ + jQuery.className.remove(this,c); + }, + toggleClass: function( c ){ + jQuery.className[ jQuery.className.has(this,c) ? "remove" : "add" ](this, c); + }, + remove: function(a){ + if ( !a || jQuery.filter( a, [this] ).r.length ) + this.parentNode.removeChild( this ); + }, + empty: function() { + while ( this.firstChild ) + this.removeChild( this.firstChild ); + } +}, function(i,n){ + jQuery.fn[ i ] = function() { + return this.each( n, arguments ); + }; +}); + +// DEPRECATED +jQuery.each( [ "eq", "lt", "gt", "contains" ], function(i,n){ + jQuery.fn[ n ] = function(num,fn) { + return this.filter( ":" + n + "(" + num + ")", fn ); + }; +}); + +jQuery.each( [ "height", "width" ], function(i,n){ + jQuery.fn[ n ] = function(h) { + return h == undefined ? + ( this.length ? jQuery.css( this[0], n ) : null ) : + this.css( n, h.constructor == String ? h : h + "px" ); + }; +}); + +var chars = jQuery.browser.safari && parseInt(jQuery.browser.version) < 417 ? + "(?:[\\w*_-]|\\\\.)" : + "(?:[\\w\u0128-\uFFFF*_-]|\\\\.)", + quickChild = new RegExp("^[/>]\\s*(" + chars + "+)"), + quickID = new RegExp("^(" + chars + "+)(#)(" + chars + "+)"), + quickClass = new RegExp("^([#.]?)(" + chars + "*)"); + +jQuery.extend({ + expr: { + "": "m[2]=='*'||jQuery.nodeName(a,m[2])", + "#": "a.getAttribute('id')==m[2]", + ":": { + // Position Checks + lt: "i<m[3]-0", + gt: "i>m[3]-0", + nth: "m[3]-0==i", + eq: "m[3]-0==i", + first: "i==0", + last: "i==r.length-1", + even: "i%2==0", + odd: "i%2", + + // Child Checks + "first-child": "a.parentNode.getElementsByTagName('*')[0]==a", + "last-child": "jQuery.nth(a.parentNode.lastChild,1,'previousSibling')==a", + "only-child": "!jQuery.nth(a.parentNode.lastChild,2,'previousSibling')", + + // Parent Checks + parent: "a.firstChild", + empty: "!a.firstChild", + + // Text Check + contains: "(a.textContent||a.innerText||'').indexOf(m[3])>=0", + + // Visibility + visible: '"hidden"!=a.type&&jQuery.css(a,"display")!="none"&&jQuery.css(a,"visibility")!="hidden"', + hidden: '"hidden"==a.type||jQuery.css(a,"display")=="none"||jQuery.css(a,"visibility")=="hidden"', + + // Form attributes + enabled: "!a.disabled", + disabled: "a.disabled", + checked: "a.checked", + selected: "a.selected||jQuery.attr(a,'selected')", + + // Form elements + text: "'text'==a.type", + radio: "'radio'==a.type", + checkbox: "'checkbox'==a.type", + file: "'file'==a.type", + password: "'password'==a.type", + submit: "'submit'==a.type", + image: "'image'==a.type", + reset: "'reset'==a.type", + button: '"button"==a.type||jQuery.nodeName(a,"button")', + input: "/input|select|textarea|button/i.test(a.nodeName)", + + // :has() + has: "jQuery.find(m[3],a).length" + }, + // DEPRECATED + "[": "jQuery.find(m[2],a).length" + }, + + // The regular expressions that power the parsing engine + parse: [ + // Match: [@value='test'], [@foo] + /^\[ *(@)([\w-]+) *([!*$^~=]*) *('?"?)(.*?)\4 *\]/, + + // DEPRECATED + // Match: [div], [div p] + /^(\[)\s*(.*?(\[.*?\])?[^[]*?)\s*\]/, + + // Match: :contains('foo') + /^(:)([\w-]+)\("?'?(.*?(\(.*?\))?[^(]*?)"?'?\)/, + + // Match: :even, :last-chlid, #id, .class + new RegExp("^([:.#]*)(" + chars + "+)") + ], + + multiFilter: function( expr, elems, not ) { + var old, cur = []; + + while ( expr && expr != old ) { + old = expr; + var f = jQuery.filter( expr, elems, not ); + expr = f.t.replace(/^\s*,\s*/, "" ); + cur = not ? elems = f.r : jQuery.merge( cur, f.r ); + } + + return cur; + }, + find: function( t, context ) { + // Quickly handle non-string expressions + if ( typeof t != "string" ) + return [ t ]; + + // Make sure that the context is a DOM Element + if ( context && !context.nodeType ) + context = null; + + // Set the correct context (if none is provided) + context = context || document; + + // DEPRECATED + // Handle the common XPath // expression + if ( !t.indexOf("//") ) { + //context = context.documentElement; + t = t.substr(2,t.length); + + // DEPRECATED + // And the / root expression + } else if ( !t.indexOf("/") && !context.ownerDocument ) { + context = context.documentElement; + t = t.substr(1,t.length); + if ( t.indexOf("/") >= 1 ) + t = t.substr(t.indexOf("/"),t.length); + } + + // Initialize the search + var ret = [context], done = [], last; + + // Continue while a selector expression exists, and while + // we're no longer looping upon ourselves + while ( t && last != t ) { + var r = []; + last = t; + + // DEPRECATED + t = jQuery.trim(t).replace( /^\/\//, "" ); + + var foundToken = false; + + // An attempt at speeding up child selectors that + // point to a specific element tag + var re = quickChild; + var m = re.exec(t); + + if ( m ) { + var nodeName = m[1].toUpperCase(); + + // Perform our own iteration and filter + for ( var i = 0; ret[i]; i++ ) + for ( var c = ret[i].firstChild; c; c = c.nextSibling ) + if ( c.nodeType == 1 && (nodeName == "*" || c.nodeName.toUpperCase() == nodeName.toUpperCase()) ) + r.push( c ); + + ret = r; + t = t.replace( re, "" ); + if ( t.indexOf(" ") == 0 ) continue; + foundToken = true; + } else { + // (.. and /) DEPRECATED + re = /^((\/?\.\.)|([>\/+~]))\s*(\w*)/i; + + if ( (m = re.exec(t)) != null ) { + r = []; + + var nodeName = m[4], mergeNum = jQuery.mergeNum++; + m = m[1]; + + for ( var j = 0, rl = ret.length; j < rl; j++ ) + if ( m.indexOf("..") < 0 ) { + var n = m == "~" || m == "+" ? ret[j].nextSibling : ret[j].firstChild; + for ( ; n; n = n.nextSibling ) + if ( n.nodeType == 1 ) { + if ( m == "~" && n.mergeNum == mergeNum ) break; + + if (!nodeName || n.nodeName.toUpperCase() == nodeName.toUpperCase() ) { + if ( m == "~" ) n.mergeNum = mergeNum; + r.push( n ); + } + + if ( m == "+" ) break; + } + // DEPRECATED + } else + r.push( ret[j].parentNode ); + + ret = r; + + // And remove the token + t = jQuery.trim( t.replace( re, "" ) ); + foundToken = true; + } + } + + // See if there's still an expression, and that we haven't already + // matched a token + if ( t && !foundToken ) { + // Handle multiple expressions + if ( !t.indexOf(",") ) { + // Clean the result set + if ( context == ret[0] ) ret.shift(); + + // Merge the result sets + done = jQuery.merge( done, ret ); + + // Reset the context + r = ret = [context]; + + // Touch up the selector string + t = " " + t.substr(1,t.length); + + } else { + // Optimize for the case nodeName#idName + var re2 = quickID; + var m = re2.exec(t); + + // Re-organize the results, so that they're consistent + if ( m ) { + m = [ 0, m[2], m[3], m[1] ]; + + } else { + // Otherwise, do a traditional filter check for + // ID, class, and element selectors + re2 = quickClass; + m = re2.exec(t); + } + + m[2] = m[2].replace(/\\/g, ""); + + var elem = ret[ret.length-1]; + + // Try to do a global search by ID, where we can + if ( m[1] == "#" && elem && elem.getElementById && !jQuery.isXMLDoc(elem) ) { + // Optimization for HTML document case + var oid = elem.getElementById(m[2]); + + // Do a quick check for the existence of the actual ID attribute + // to avoid selecting by the name attribute in IE + // also check to insure id is a string to avoid selecting an element with the name of 'id' inside a form + if ( (jQuery.browser.msie||jQuery.browser.opera) && oid && typeof oid.id == "string" && oid.id != m[2] ) + oid = jQuery('[@id="'+m[2]+'"]', elem)[0]; + + // Do a quick check for node name (where applicable) so + // that div#foo searches will be really fast + ret = r = oid && (!m[3] || jQuery.nodeName(oid, m[3])) ? [oid] : []; + } else { + // We need to find all descendant elements + for ( var i = 0; ret[i]; i++ ) { + // Grab the tag name being searched for + var tag = m[1] != "" || m[0] == "" ? "*" : m[2]; + + // Handle IE7 being really dumb about <object>s + if ( tag == "*" && ret[i].nodeName.toLowerCase() == "object" ) + tag = "param"; + + r = jQuery.merge( r, ret[i].getElementsByTagName( tag )); + } + + // It's faster to filter by class and be done with it + if ( m[1] == "." ) + r = jQuery.classFilter( r, m[2] ); + + // Same with ID filtering + if ( m[1] == "#" ) { + var tmp = []; + + // Try to find the element with the ID + for ( var i = 0; r[i]; i++ ) + if ( r[i].getAttribute("id") == m[2] ) { + tmp = [ r[i] ]; + break; + } + + r = tmp; + } + + ret = r; + } + + t = t.replace( re2, "" ); + } + + } + + // If a selector string still exists + if ( t ) { + // Attempt to filter it + var val = jQuery.filter(t,r); + ret = r = val.r; + t = jQuery.trim(val.t); + } + } + + // An error occurred with the selector; + // just return an empty set instead + if ( t ) + ret = []; + + // Remove the root context + if ( ret && context == ret[0] ) + ret.shift(); + + // And combine the results + done = jQuery.merge( done, ret ); + + return done; + }, + + classFilter: function(r,m,not){ + m = " " + m + " "; + var tmp = []; + for ( var i = 0; r[i]; i++ ) { + var pass = (" " + r[i].className + " ").indexOf( m ) >= 0; + if ( !not && pass || not && !pass ) + tmp.push( r[i] ); + } + return tmp; + }, + + filter: function(t,r,not) { + var last; + + // Look for common filter expressions + while ( t && t != last ) { + last = t; + + var p = jQuery.parse, m; + + for ( var i = 0; p[i]; i++ ) { + m = p[i].exec( t ); + + if ( m ) { + // Remove what we just matched + t = t.substring( m[0].length ); + + m[2] = m[2].replace(/\\/g, ""); + break; + } + } + + if ( !m ) + break; + + // :not() is a special case that can be optimized by + // keeping it out of the expression list + if ( m[1] == ":" && m[2] == "not" ) + r = jQuery.filter(m[3], r, true).r; + + // We can get a big speed boost by filtering by class here + else if ( m[1] == "." ) + r = jQuery.classFilter(r, m[2], not); + + else if ( m[1] == "@" ) { + var tmp = [], type = m[3]; + + for ( var i = 0, rl = r.length; i < rl; i++ ) { + var a = r[i], z = a[ jQuery.props[m[2]] || m[2] ]; + + if ( z == null || /href|src|selected/.test(m[2]) ) + z = jQuery.attr(a,m[2]) || ''; + + if ( (type == "" && !!z || + type == "=" && z == m[5] || + type == "!=" && z != m[5] || + type == "^=" && z && !z.indexOf(m[5]) || + type == "$=" && z.substr(z.length - m[5].length) == m[5] || + (type == "*=" || type == "~=") && z.indexOf(m[5]) >= 0) ^ not ) + tmp.push( a ); + } + + r = tmp; + + // We can get a speed boost by handling nth-child here + } else if ( m[1] == ":" && m[2] == "nth-child" ) { + var num = jQuery.mergeNum++, tmp = [], + test = /(\d*)n\+?(\d*)/.exec( + m[3] == "even" && "2n" || m[3] == "odd" && "2n+1" || + !/\D/.test(m[3]) && "n+" + m[3] || m[3]), + first = (test[1] || 1) - 0, last = test[2] - 0; + + for ( var i = 0, rl = r.length; i < rl; i++ ) { + var node = r[i], parentNode = node.parentNode; + + if ( num != parentNode.mergeNum ) { + var c = 1; + + for ( var n = parentNode.firstChild; n; n = n.nextSibling ) + if ( n.nodeType == 1 ) + n.nodeIndex = c++; + + parentNode.mergeNum = num; + } + + var add = false; + + if ( first == 1 ) { + if ( last == 0 || node.nodeIndex == last ) + add = true; + } else if ( (node.nodeIndex + last) % first == 0 ) + add = true; + + if ( add ^ not ) + tmp.push( node ); + } + + r = tmp; + + // Otherwise, find the expression to execute + } else { + var f = jQuery.expr[m[1]]; + if ( typeof f != "string" ) + f = jQuery.expr[m[1]][m[2]]; + + // Build a custom macro to enclose it + f = eval("false||function(a,i){return " + f + "}"); + + // Execute it against the current filter + r = jQuery.grep( r, f, not ); + } + } + + // Return an array of filtered elements (r) + // and the modified expression string (t) + return { r: r, t: t }; + }, + parents: function( elem ){ + var matched = []; + var cur = elem.parentNode; + while ( cur && cur != document ) { + matched.push( cur ); + cur = cur.parentNode; + } + return matched; + }, + nth: function(cur,result,dir,elem){ + result = result || 1; + var num = 0; + + for ( ; cur; cur = cur[dir] ) + if ( cur.nodeType == 1 && ++num == result ) + break; + + return cur; + }, + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType == 1 && (!elem || n != elem) ) + r.push( n ); + } + + return r; + } +}); +/* + * A number of helper functions used for managing events. + * Many of the ideas behind this code orignated from + * Dean Edwards' addEvent library. + */ +jQuery.event = { + + // Bind an event to an element + // Original by Dean Edwards + add: function(element, type, handler, data) { + // For whatever reason, IE has trouble passing the window object + // around, causing it to be cloned in the process + if ( jQuery.browser.msie && element.setInterval != undefined ) + element = window; + + // Make sure that the function being executed has a unique ID + if ( !handler.guid ) + handler.guid = this.guid++; + + // if data is passed, bind to handler + if( data != undefined ) { + // Create temporary function pointer to original handler + var fn = handler; + + // Create unique handler function, wrapped around original handler + handler = function() { + // Pass arguments and context to original handler + return fn.apply(this, arguments); + }; + + // Store data in unique handler + handler.data = data; + + // Set the guid of unique handler to the same of original handler, so it can be removed + handler.guid = fn.guid; + } + + // Init the element's event structure + if (!element.$events) + element.$events = {}; + + if (!element.$handle) + element.$handle = function() { + // returned undefined or false + var val; + + // Handle the second event of a trigger and when + // an event is called after a page has unloaded + if ( typeof jQuery == "undefined" || jQuery.event.triggered ) + return val; + + val = jQuery.event.handle.apply(element, arguments); + + return val; + }; + + // Get the current list of functions bound to this event + var handlers = element.$events[type]; + + // Init the event handler queue + if (!handlers) { + handlers = element.$events[type] = {}; + + // And bind the global event handler to the element + if (element.addEventListener) + element.addEventListener(type, element.$handle, false); + else + element.attachEvent("on" + type, element.$handle); + } + + // Add the function to the element's handler list + handlers[handler.guid] = handler; + + // Keep track of which events have been used, for global triggering + this.global[type] = true; + }, + + guid: 1, + global: {}, + + // Detach an event or set of events from an element + remove: function(element, type, handler) { + var events = element.$events, ret, index; + + if ( events ) { + // type is actually an event object here + if ( type && type.type ) { + handler = type.handler; + type = type.type; + } + + if ( !type ) { + for ( type in events ) + this.remove( element, type ); + + } else if ( events[type] ) { + // remove the given handler for the given type + if ( handler ) + delete events[type][handler.guid]; + + // remove all handlers for the given type + else + for ( handler in element.$events[type] ) + delete events[type][handler]; + + // remove generic event handler if no more handlers exist + for ( ret in events[type] ) break; + if ( !ret ) { + if (element.removeEventListener) + element.removeEventListener(type, element.$handle, false); + else + element.detachEvent("on" + type, element.$handle); + ret = null; + delete events[type]; + } + } + + // Remove the expando if it's no longer used + for ( ret in events ) break; + if ( !ret ) + element.$handle = element.$events = null; + } + }, + + trigger: function(type, data, element) { + // Clone the incoming data, if any + data = jQuery.makeArray(data || []); + + // Handle a global trigger + if ( !element ) { + // Only trigger if we've ever bound an event for it + if ( this.global[type] ) + jQuery("*").add([window, document]).trigger(type, data); + + // Handle triggering a single element + } else { + var val, ret, fn = jQuery.isFunction( element[ type ] || null ); + + // Pass along a fake event + data.unshift( this.fix({ type: type, target: element }) ); + + // Trigger the event + if ( jQuery.isFunction( element.$handle ) ) + val = element.$handle.apply( element, data ); + if ( !fn && element["on"+type] && element["on"+type].apply( element, data ) === false ) + val = false; + + if ( fn && val !== false && !(jQuery.nodeName(element, 'a') && type == "click") ) { + this.triggered = true; + element[ type ](); + } + + this.triggered = false; + } + }, + + handle: function(event) { + // returned undefined or false + var val; + + // Empty object is for triggered events with no data + event = jQuery.event.fix( event || window.event || {} ); + + var c = this.$events && this.$events[event.type], args = Array.prototype.slice.call( arguments, 1 ); + args.unshift( event ); + + for ( var j in c ) { + // Pass in a reference to the handler function itself + // So that we can later remove it + args[0].handler = c[j]; + args[0].data = c[j].data; + + if ( c[j].apply( this, args ) === false ) { + event.preventDefault(); + event.stopPropagation(); + val = false; + } + } + + // Clean up added properties in IE to prevent memory leak + if (jQuery.browser.msie) + event.target = event.preventDefault = event.stopPropagation = + event.handler = event.data = null; + + return val; + }, + + fix: function(event) { + // store a copy of the original event object + // and clone to set read-only properties + var originalEvent = event; + event = jQuery.extend({}, originalEvent); + + // add preventDefault and stopPropagation since + // they will not work on the clone + event.preventDefault = function() { + // if preventDefault exists run it on the original event + if (originalEvent.preventDefault) + originalEvent.preventDefault(); + // otherwise set the returnValue property of the original event to false (IE) + originalEvent.returnValue = false; + }; + event.stopPropagation = function() { + // if stopPropagation exists run it on the original event + if (originalEvent.stopPropagation) + originalEvent.stopPropagation(); + // otherwise set the cancelBubble property of the original event to true (IE) + originalEvent.cancelBubble = true; + }; + + // Fix target property, if necessary + if ( !event.target && event.srcElement ) + event.target = event.srcElement; + + // check if target is a textnode (safari) + if (jQuery.browser.safari && event.target.nodeType == 3) + event.target = originalEvent.target.parentNode; + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && event.fromElement ) + event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && event.clientX != null ) { + var e = document.documentElement, b = document.body; + event.pageX = event.clientX + (e && e.scrollLeft || b.scrollLeft || 0); + event.pageY = event.clientY + (e && e.scrollTop || b.scrollTop || 0); + } + + // Add which for key events + if ( !event.which && (event.charCode || event.keyCode) ) + event.which = event.charCode || event.keyCode; + + // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs) + if ( !event.metaKey && event.ctrlKey ) + event.metaKey = event.ctrlKey; + + // Add which for click: 1 == left; 2 == middle; 3 == right + // Note: button is not normalized, so don't use it + if ( !event.which && event.button ) + event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) )); + + return event; + } +}; + +jQuery.fn.extend({ + bind: function( type, data, fn ) { + return type == "unload" ? this.one(type, data, fn) : this.each(function(){ + jQuery.event.add( this, type, fn || data, fn && data ); + }); + }, + one: function( type, data, fn ) { + return this.each(function(){ + jQuery.event.add( this, type, function(event) { + jQuery(this).unbind(event); + return (fn || data).apply( this, arguments); + }, fn && data); + }); + }, + unbind: function( type, fn ) { + return this.each(function(){ + jQuery.event.remove( this, type, fn ); + }); + }, + trigger: function( type, data ) { + return this.each(function(){ + jQuery.event.trigger( type, data, this ); + }); + }, + toggle: function() { + // Save reference to arguments for access in closure + var a = arguments; + + return this.click(function(e) { + // Figure out which function to execute + this.lastToggle = 0 == this.lastToggle ? 1 : 0; + + // Make sure that clicks stop + e.preventDefault(); + + // and execute the function + return a[this.lastToggle].apply( this, [e] ) || false; + }); + }, + hover: function(f,g) { + + // A private function for handling mouse 'hovering' + function handleHover(e) { + // Check if mouse(over|out) are still within the same parent element + var p = e.relatedTarget; + + // Traverse up the tree + while ( p && p != this ) try { p = p.parentNode; } catch(e) { p = this; }; + + // If we actually just moused on to a sub-element, ignore it + if ( p == this ) return false; + + // Execute the right function + return (e.type == "mouseover" ? f : g).apply(this, [e]); + } + + // Bind the function to the two event listeners + return this.mouseover(handleHover).mouseout(handleHover); + }, + ready: function(f) { + // Attach the listeners + bindReady(); + + // If the DOM is already ready + if ( jQuery.isReady ) + // Execute the function immediately + f.apply( document, [jQuery] ); + + // Otherwise, remember the function for later + else + // Add the function to the wait list + jQuery.readyList.push( function() { return f.apply(this, [jQuery]); } ); + + return this; + } +}); + +jQuery.extend({ + /* + * All the code that makes DOM Ready work nicely. + */ + isReady: false, + readyList: [], + + // Handle when the DOM is ready + ready: function() { + // Make sure that the DOM is not already loaded + if ( !jQuery.isReady ) { + // Remember that the DOM is ready + jQuery.isReady = true; + + // If there are functions bound, to execute + if ( jQuery.readyList ) { + // Execute all of them + jQuery.each( jQuery.readyList, function(){ + this.apply( document ); + }); + + // Reset the list of functions + jQuery.readyList = null; + } + // Remove event listener to avoid memory leak + if ( jQuery.browser.mozilla || jQuery.browser.opera ) + document.removeEventListener( "DOMContentLoaded", jQuery.ready, false ); + + // Remove script element used by IE hack + if( !window.frames.length ) // don't remove if frames are present (#1187) + jQuery(window).load(function(){ jQuery("#__ie_init").remove(); }); + } + } +}); + + jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," + + "mousedown,mouseup,mousemove,mouseover,mouseout,change,select," + + "submit,keydown,keypress,keyup,error").split(","), function(i,o){ + + // Handle event binding + jQuery.fn[o] = function(f){ + return f ? this.bind(o, f) : this.trigger(o); + }; + + }); + +var readyBound = false; + +function bindReady(){ + if ( readyBound ) return; + readyBound = true; + + // If Mozilla is used + if ( jQuery.browser.mozilla || jQuery.browser.opera ) + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", jQuery.ready, false ); + + // If IE is used, use the excellent hack by Matthias Miller + // http://www.outofhanwell.com/blog/index.php?title=the_window_onload_problem_revisited + else if ( jQuery.browser.msie ) { + + // Only works if you document.write() it + document.write("<scr" + "ipt id=__ie_init defer=true " + + "src=//:><\/script>"); + + // Use the defer script hack + var script = document.getElementById("__ie_init"); + + // script does not exist if jQuery is loaded dynamically + if ( script ) + script.onreadystatechange = function() { + if ( document.readyState != "complete" ) return; + jQuery.ready(); + }; + + // Clear from memory + script = null; + + // If Safari is used + } else if ( jQuery.browser.safari ) + // Continually check to see if the document.readyState is valid + jQuery.safariTimer = setInterval(function(){ + // loaded and complete are both valid states + if ( document.readyState == "loaded" || + document.readyState == "complete" ) { + + // If either one are found, remove the timer + clearInterval( jQuery.safariTimer ); + jQuery.safariTimer = null; + + // and execute any waiting functions + jQuery.ready(); + } + }, 10); + + // A fallback to window.onload, that will always work + jQuery.event.add( window, "load", jQuery.ready ); +} +jQuery.fn.extend({ + // DEPRECATED + loadIfModified: function( url, params, callback ) { + this.load( url, params, callback, 1 ); + }, + load: function( url, params, callback, ifModified ) { + if ( jQuery.isFunction( url ) ) + return this.bind("load", url); + + callback = callback || function(){}; + + // Default to a GET request + var type = "GET"; + + // If the second parameter was provided + if ( params ) + // If it's a function + if ( jQuery.isFunction( params ) ) { + // We assume that it's the callback + callback = params; + params = null; + + // Otherwise, build a param string + } else { + params = jQuery.param( params ); + type = "POST"; + } + + var self = this; + + // Request the remote document + jQuery.ajax({ + url: url, + type: type, + data: params, + ifModified: ifModified, + complete: function(res, status){ + // If successful, inject the HTML into all the matched elements + if ( status == "success" || !ifModified && status == "notmodified" ) + self.html(res.responseText); + + // Add delay to account for Safari's delay in globalEval + setTimeout(function(){ + self.each( callback, [res.responseText, status, res] ); + }, 13); + } + }); + return this; + }, + serialize: function() { + return jQuery.param( this ); + }, + + // DEPRECATED + // This method no longer does anything - all script evaluation is + // taken care of within the HTML injection methods. + evalScripts: function(){} + +}); + +// Attach a bunch of functions for handling common AJAX events + +jQuery.each( "ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","), function(i,o){ + jQuery.fn[o] = function(f){ + return this.bind(o, f); + }; +}); + +jQuery.extend({ + get: function( url, data, callback, type, ifModified ) { + // shift arguments if data argument was ommited + if ( jQuery.isFunction( data ) ) { + callback = data; + data = null; + } + + return jQuery.ajax({ + type: "GET", + url: url, + data: data, + success: callback, + dataType: type, + ifModified: ifModified + }); + }, + // DEPRECATED + getIfModified: function( url, data, callback, type ) { + return jQuery.get(url, data, callback, type, 1); + }, + getScript: function( url, callback ) { + return jQuery.get(url, null, callback, "script"); + }, + getJSON: function( url, data, callback ) { + return jQuery.get(url, data, callback, "json"); + }, + post: function( url, data, callback, type ) { + if ( jQuery.isFunction( data ) ) { + callback = data; + data = {}; + } + + return jQuery.ajax({ + type: "POST", + url: url, + data: data, + success: callback, + dataType: type + }); + }, + // DEPRECATED + ajaxTimeout: function( timeout ) { + jQuery.ajaxSettings.timeout = timeout; + }, + ajaxSetup: function( settings ) { + jQuery.extend( jQuery.ajaxSettings, settings ); + }, + + ajaxSettings: { + global: true, + type: "GET", + timeout: 0, + contentType: "application/x-www-form-urlencoded", + processData: true, + async: true, + data: null + }, + + // Last-Modified header cache for next request + lastModified: {}, + ajax: function( s ) { + // Extend the settings, but re-extend 's' so that it can be + // checked again later (in the test suite, specifically) + s = jQuery.extend(true, s, jQuery.extend(true, {}, jQuery.ajaxSettings, s)); + + // if data available + if ( s.data ) { + // convert data if not already a string + if ( s.processData && typeof s.data != "string" ) + s.data = jQuery.param(s.data); + + // append data to url for get requests + if ( s.type.toLowerCase() == "get" ) { + // "?" + data or "&" + data (in case there are already params) + s.url += (s.url.indexOf("?") > -1 ? "&" : "?") + s.data; + + // IE likes to send both get and post data, prevent this + s.data = null; + } + } + + // Watch for a new set of requests + if ( s.global && ! jQuery.active++ ) + jQuery.event.trigger( "ajaxStart" ); + + var requestDone = false; + + // Create the request object; Microsoft failed to properly + // implement the XMLHttpRequest in IE7, so we use the ActiveXObject when it is available + var xml = window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : new XMLHttpRequest(); + + // Open the socket + xml.open(s.type, s.url, s.async); + + // Set the correct header, if data is being sent + if ( s.data ) + xml.setRequestHeader("Content-Type", s.contentType); + + // Set the If-Modified-Since header, if ifModified mode. + if ( s.ifModified ) + xml.setRequestHeader("If-Modified-Since", + jQuery.lastModified[s.url] || "Thu, 01 Jan 1970 00:00:00 GMT" ); + + // Set header so the called script knows that it's an XMLHttpRequest + xml.setRequestHeader("X-Requested-With", "XMLHttpRequest"); + + // Allow custom headers/mimetypes + if( s.beforeSend ) + s.beforeSend(xml); + + if ( s.global ) + jQuery.event.trigger("ajaxSend", [xml, s]); + + // Wait for a response to come back + var onreadystatechange = function(isTimeout){ + // The transfer is complete and the data is available, or the request timed out + if ( !requestDone && xml && (xml.readyState == 4 || isTimeout == "timeout") ) { + requestDone = true; + + // clear poll interval + if (ival) { + clearInterval(ival); + ival = null; + } + + var status = isTimeout == "timeout" && "timeout" || + !jQuery.httpSuccess( xml ) && "error" || + s.ifModified && jQuery.httpNotModified( xml, s.url ) && "notmodified" || + "success"; + + if ( status == "success" ) { + // Watch for, and catch, XML document parse errors + try { + // process the data (runs the xml through httpData regardless of callback) + var data = jQuery.httpData( xml, s.dataType ); + } catch(e) { + status = "parsererror"; + } + } + + // Make sure that the request was successful or notmodified + if ( status == "success" ) { + // Cache Last-Modified header, if ifModified mode. + var modRes; + try { + modRes = xml.getResponseHeader("Last-Modified"); + } catch(e) {} // swallow exception thrown by FF if header is not available + + if ( s.ifModified && modRes ) + jQuery.lastModified[s.url] = modRes; + + // If a local callback was specified, fire it and pass it the data + if ( s.success ) + s.success( data, status ); + + // Fire the global callback + if ( s.global ) + jQuery.event.trigger( "ajaxSuccess", [xml, s] ); + } else + jQuery.handleError(s, xml, status); + + // The request was completed + if( s.global ) + jQuery.event.trigger( "ajaxComplete", [xml, s] ); + + // Handle the global AJAX counter + if ( s.global && ! --jQuery.active ) + jQuery.event.trigger( "ajaxStop" ); + + // Process result + if ( s.complete ) + s.complete(xml, status); + + // Stop memory leaks + if(s.async) + xml = null; + } + }; + + if ( s.async ) { + // don't attach the handler to the request, just poll it instead + var ival = setInterval(onreadystatechange, 13); + + // Timeout checker + if ( s.timeout > 0 ) + setTimeout(function(){ + // Check to see if the request is still happening + if ( xml ) { + // Cancel the request + xml.abort(); + + if( !requestDone ) + onreadystatechange( "timeout" ); + } + }, s.timeout); + } + + // Send the data + try { + xml.send(s.data); + } catch(e) { + jQuery.handleError(s, xml, null, e); + } + + // firefox 1.5 doesn't fire statechange for sync requests + if ( !s.async ) + onreadystatechange(); + + // return XMLHttpRequest to allow aborting the request etc. + return xml; + }, + + handleError: function( s, xml, status, e ) { + // If a local callback was specified, fire it + if ( s.error ) s.error( xml, status, e ); + + // Fire the global callback + if ( s.global ) + jQuery.event.trigger( "ajaxError", [xml, s, e] ); + }, + + // Counter for holding the number of active queries + active: 0, + + // Determines if an XMLHttpRequest was successful or not + httpSuccess: function( r ) { + try { + return !r.status && location.protocol == "file:" || + ( r.status >= 200 && r.status < 300 ) || r.status == 304 || + jQuery.browser.safari && r.status == undefined; + } catch(e){} + return false; + }, + + // Determines if an XMLHttpRequest returns NotModified + httpNotModified: function( xml, url ) { + try { + var xmlRes = xml.getResponseHeader("Last-Modified"); + + // Firefox always returns 200. check Last-Modified date + return xml.status == 304 || xmlRes == jQuery.lastModified[url] || + jQuery.browser.safari && xml.status == undefined; + } catch(e){} + return false; + }, + + /* Get the data out of an XMLHttpRequest. + * Return parsed XML if content-type header is "xml" and type is "xml" or omitted, + * otherwise return plain text. + * (String) data - The type of data that you're expecting back, + * (e.g. "xml", "html", "script") + */ + httpData: function( r, type ) { + var ct = r.getResponseHeader("content-type"); + var xml = type == "xml" || !type && ct && ct.indexOf("xml") >= 0; + data = xml ? r.responseXML : r.responseText; + + if ( xml && data.documentElement.tagName == "parsererror" ) + throw "parsererror"; + + // If the type is "script", eval it in global context + if ( type == "script" ) + jQuery.globalEval( data ); + + // Get the JavaScript object, if JSON is used. + if ( type == "json" ) + data = eval("(" + data + ")"); + + return data; + }, + + // Serialize an array of form elements or a set of + // key/values into a query string + param: function( a ) { + var s = []; + + // If an array was passed in, assume that it is an array + // of form elements + if ( a.constructor == Array || a.jquery ) + // Serialize the form elements + jQuery.each( a, function(){ + s.push( encodeURIComponent(this.name) + "=" + encodeURIComponent( this.value ) ); + }); + + // Otherwise, assume that it's an object of key/value pairs + else + // Serialize the key/values + for ( var j in a ) + // If the value is an array then the key names need to be repeated + if ( a[j] && a[j].constructor == Array ) + jQuery.each( a[j], function(){ + s.push( encodeURIComponent(j) + "=" + encodeURIComponent( this ) ); + }); + else + s.push( encodeURIComponent(j) + "=" + encodeURIComponent( a[j] ) ); + + // Return the resulting serialization + return s.join("&"); + } + +}); +jQuery.fn.extend({ + + show: function(speed,callback){ + return speed ? + this.animate({ + height: "show", width: "show", opacity: "show" + }, speed, callback) : + + this.filter(":hidden").each(function(){ + this.style.display = this.oldblock ? this.oldblock : ""; + if ( jQuery.css(this,"display") == "none" ) + this.style.display = "block"; + }).end(); + }, + + hide: function(speed,callback){ + return speed ? + this.animate({ + height: "hide", width: "hide", opacity: "hide" + }, speed, callback) : + + this.filter(":visible").each(function(){ + this.oldblock = this.oldblock || jQuery.css(this,"display"); + if ( this.oldblock == "none" ) + this.oldblock = "block"; + this.style.display = "none"; + }).end(); + }, + + // Save the old toggle function + _toggle: jQuery.fn.toggle, + toggle: function( fn, fn2 ){ + return jQuery.isFunction(fn) && jQuery.isFunction(fn2) ? + this._toggle( fn, fn2 ) : + fn ? + this.animate({ + height: "toggle", width: "toggle", opacity: "toggle" + }, fn, fn2) : + this.each(function(){ + jQuery(this)[ jQuery(this).is(":hidden") ? "show" : "hide" ](); + }); + }, + slideDown: function(speed,callback){ + return this.animate({height: "show"}, speed, callback); + }, + slideUp: function(speed,callback){ + return this.animate({height: "hide"}, speed, callback); + }, + slideToggle: function(speed, callback){ + return this.animate({height: "toggle"}, speed, callback); + }, + fadeIn: function(speed, callback){ + return this.animate({opacity: "show"}, speed, callback); + }, + fadeOut: function(speed, callback){ + return this.animate({opacity: "hide"}, speed, callback); + }, + fadeTo: function(speed,to,callback){ + return this.animate({opacity: to}, speed, callback); + }, + animate: function( prop, speed, easing, callback ) { + return this.queue(function(){ + var hidden = jQuery(this).is(":hidden"), + opt = jQuery.speed(speed, easing, callback), + self = this; + + for ( var p in prop ) { + if ( prop[p] == "hide" && hidden || prop[p] == "show" && !hidden ) + return jQuery.isFunction(opt.complete) && opt.complete.apply(this); + + if ( p == "height" || p == "width" ) { + // Store display property + opt.display = jQuery.css(this, "display"); + + // Make sure that nothing sneaks out + opt.overflow = this.style.overflow; + } + } + + if ( opt.overflow != null ) + this.style.overflow = "hidden"; + + this.curAnim = jQuery.extend({}, prop); + + jQuery.each( prop, function(name, val){ + var e = new jQuery.fx( self, opt, name ); + if ( val.constructor == Number ) + e.custom( e.cur() || 0, val ); + else + e[ val == "toggle" ? hidden ? "show" : "hide" : val ]( prop ); + }); + + // For JS strict compliance + return true; + }); + }, + queue: function(type,fn){ + if ( !fn ) { + fn = type; + type = "fx"; + } + + return this.each(function(){ + if ( !this.queue ) + this.queue = {}; + + if ( !this.queue[type] ) + this.queue[type] = []; + + this.queue[type].push( fn ); + + if ( this.queue[type].length == 1 ) + fn.apply(this); + }); + } + +}); + +jQuery.extend({ + + speed: function(speed, easing, fn) { + var opt = speed && speed.constructor == Object ? speed : { + complete: fn || !fn && easing || + jQuery.isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && easing.constructor != Function && easing + }; + + opt.duration = (opt.duration && opt.duration.constructor == Number ? + opt.duration : + { slow: 600, fast: 200 }[opt.duration]) || 400; + + // Queueing + opt.old = opt.complete; + opt.complete = function(){ + jQuery.dequeue(this, "fx"); + if ( jQuery.isFunction( opt.old ) ) + opt.old.apply( this ); + }; + + return opt; + }, + + easing: { + linear: function( p, n, firstNum, diff ) { + return firstNum + diff * p; + }, + swing: function( p, n, firstNum, diff ) { + return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum; + } + }, + + queue: {}, + + dequeue: function(elem,type){ + type = type || "fx"; + + if ( elem.queue && elem.queue[type] ) { + // Remove self + elem.queue[type].shift(); + + // Get next function + var f = elem.queue[type][0]; + + if ( f ) f.apply( elem ); + } + }, + + timers: [], + + /* + * I originally wrote fx() as a clone of moo.fx and in the process + * of making it small in size the code became illegible to sane + * people. You've been warned. + */ + + fx: function( elem, options, prop ){ + + var z = this; + + // The styles + var y = elem.style; + + // Simple function for setting a style value + z.a = function(){ + if ( options.step ) + options.step.apply( elem, [ z.now ] ); + + if ( prop == "opacity" ) + jQuery.attr(y, "opacity", z.now); // Let attr handle opacity + else { + y[prop] = parseInt(z.now) + "px"; + + // Set display property to block for height/width animations + if ( prop == "height" || prop == "width" ) + y.display = "block"; + } + }; + + // Figure out the maximum number to run to + z.max = function(){ + return parseFloat( jQuery.css(elem,prop) ); + }; + + // Get the current size + z.cur = function(){ + var r = parseFloat( jQuery.curCSS(elem, prop) ); + return r && r > -10000 ? r : z.max(); + }; + + // Start an animation from one number to another + z.custom = function(from,to){ + z.startTime = (new Date()).getTime(); + z.now = from; + z.a(); + + jQuery.timers.push(function(){ + return z.step(from, to); + }); + + if ( jQuery.timers.length == 1 ) { + var timer = setInterval(function(){ + var timers = jQuery.timers; + + for ( var i = 0; i < timers.length; i++ ) + if ( !timers[i]() ) + timers.splice(i--, 1); + + if ( !timers.length ) + clearInterval( timer ); + }, 13); + } + }; + + // Simple 'show' function + z.show = function(){ + if ( !elem.orig ) elem.orig = {}; + + // Remember where we started, so that we can go back to it later + elem.orig[prop] = jQuery.attr( elem.style, prop ); + + options.show = true; + + // Begin the animation + z.custom(0, this.cur()); + + // Make sure that we start at a small width/height to avoid any + // flash of content + if ( prop != "opacity" ) + y[prop] = "1px"; + + // Start by showing the element + jQuery(elem).show(); + }; + + // Simple 'hide' function + z.hide = function(){ + if ( !elem.orig ) elem.orig = {}; + + // Remember where we started, so that we can go back to it later + elem.orig[prop] = jQuery.attr( elem.style, prop ); + + options.hide = true; + + // Begin the animation + z.custom(this.cur(), 0); + }; + + // Each step of an animation + z.step = function(firstNum, lastNum){ + var t = (new Date()).getTime(); + + if (t > options.duration + z.startTime) { + z.now = lastNum; + z.a(); + + if (elem.curAnim) elem.curAnim[ prop ] = true; + + var done = true; + for ( var i in elem.curAnim ) + if ( elem.curAnim[i] !== true ) + done = false; + + if ( done ) { + if ( options.display != null ) { + // Reset the overflow + y.overflow = options.overflow; + + // Reset the display + y.display = options.display; + if ( jQuery.css(elem, "display") == "none" ) + y.display = "block"; + } + + // Hide the element if the "hide" operation was done + if ( options.hide ) + y.display = "none"; + + // Reset the properties, if the item has been hidden or shown + if ( options.hide || options.show ) + for ( var p in elem.curAnim ) + jQuery.attr(y, p, elem.orig[p]); + } + + // If a callback was provided, execute it + if ( done && jQuery.isFunction( options.complete ) ) + // Execute the complete function + options.complete.apply( elem ); + + return false; + } else { + var n = t - this.startTime; + // Figure out where in the animation we are and set the number + var p = n / options.duration; + + // Perform the easing function, defaults to swing + z.now = jQuery.easing[options.easing || (jQuery.easing.swing ? "swing" : "linear")](p, n, firstNum, (lastNum-firstNum), options.duration); + + // Perform the next step of the animation + z.a(); + } + + return true; + }; + + } +}); +})(); diff --git a/libs/jquery/loadingAnimation.gif b/libs/jquery/loadingAnimation.gif Binary files differnew file mode 100644 index 0000000000..82290f4833 --- /dev/null +++ b/libs/jquery/loadingAnimation.gif diff --git a/libs/jquery/macFFBgHack.png b/libs/jquery/macFFBgHack.png Binary files differnew file mode 100644 index 0000000000..c6473b324e --- /dev/null +++ b/libs/jquery/macFFBgHack.png diff --git a/libs/jquery/thickbox.css b/libs/jquery/thickbox.css new file mode 100644 index 0000000000..9973d26f83 --- /dev/null +++ b/libs/jquery/thickbox.css @@ -0,0 +1,163 @@ +/* ----------------------------------------------------------------------------------------------------------------*/ +/* ---------->>> global settings needed for thickbox <<<-----------------------------------------------------------*/ +/* ----------------------------------------------------------------------------------------------------------------*/ + + +/* ----------------------------------------------------------------------------------------------------------------*/ +/* ---------->>> thickbox specific link and font settings <<<------------------------------------------------------*/ +/* ----------------------------------------------------------------------------------------------------------------*/ +#TB_window { + font: 12px Arial, Helvetica, sans-serif; + color: #333333; +} + +#TB_secondLine { + font: 10px Arial, Helvetica, sans-serif; + color:#666666; +} + +#TB_window a:link {color: #666666;} +#TB_window a:visited {color: #666666;} +#TB_window a:hover {color: #000;} +#TB_window a:active {color: #666666;} +#TB_window a:focus{color: #666666;} + +/* ----------------------------------------------------------------------------------------------------------------*/ +/* ---------->>> thickbox settings <<<-----------------------------------------------------------------------------*/ +/* ----------------------------------------------------------------------------------------------------------------*/ +#TB_overlay { + position: fixed; + z-index:100; + top: 0px; + left: 0px; + height:100%; + width:100%; +} + +.TB_overlayMacFFBGHack {background: url(libs/jquery/macFFBgHack.png) repeat;} +.TB_overlayBG { + background-color:#000; + filter:alpha(opacity=75); + -moz-opacity: 0.75; + opacity: 0.75; +} + +* html #TB_overlay { /* ie6 hack */ + position: absolute; + height: expression(document.body.scrollHeight > document.body.offsetHeight ? document.body.scrollHeight : document.body.offsetHeight + 'px'); +} + +#TB_window { + position: fixed; + background: #ffffff; + z-index: 102; + color:#000000; + display:none; + border: 4px solid #525252; + text-align:left; + top:50%; + left:50%; +} + +* html #TB_window { /* ie6 hack */ +position: absolute; +margin-top: expression(0 - parseInt(this.offsetHeight / 2) + (TBWindowMargin = document.documentElement && document.documentElement.scrollTop || document.body.scrollTop) + 'px'); +} + +#TB_window img#TB_Image { + display:block; + margin: 15px 0 0 15px; + border-right: 1px solid #ccc; + border-bottom: 1px solid #ccc; + border-top: 1px solid #666; + border-left: 1px solid #666; +} + +#TB_caption{ + height:25px; + padding:7px 30px 10px 25px; + float:left; +} + +#TB_closeWindow{ + height:25px; + padding:11px 25px 10px 0; + float:right; +} + +#TB_closeAjaxWindow{ + padding:7px 10px 5px 0; + margin-bottom:1px; + text-align:right; + float:right; +} + +#TB_ajaxWindowTitle{ + float:left; + padding:7px 0 5px 10px; + margin-bottom:1px; +} + +#TB_title{ + background-color:#e8e8e8; + height:27px; +} + +#TB_ajaxContent{ + clear:both; + padding:2px 15px 15px 15px; + overflow:auto; + text-align:left; + line-height:1.4em; +} + +#TB_ajaxContent.TB_modal{ + padding:15px; +} + +#TB_ajaxContent p{ + padding:5px 0px 5px 0px; +} + +#TB_load{ + position: fixed; + display:none; + height:13px; + width:208px; + z-index:103; + top: 50%; + left: 50%; + margin: -6px 0 0 -104px; /* -height/2 0 0 -width/2 */ +} + +* html #TB_load { /* ie6 hack */ +position: absolute; +margin-top: expression(0 - parseInt(this.offsetHeight / 2) + (TBWindowMargin = document.documentElement && document.documentElement.scrollTop || document.body.scrollTop) + 'px'); +} + +#TB_HideSelect{ + z-index:99; + position:fixed; + top: 0; + left: 0; + background-color:#fff; + border:none; + filter:alpha(opacity=0); + -moz-opacity: 0; + opacity: 0; + height:100%; + width:100%; +} + +* html #TB_HideSelect { /* ie6 hack */ + position: absolute; + height: expression(document.body.scrollHeight > document.body.offsetHeight ? document.body.scrollHeight : document.body.offsetHeight + 'px'); +} + +#TB_iframeContent{ + clear:both; + border:none; + margin-bottom:-1px; + margin-top:1px; + _margin-bottom:1px; +} diff --git a/libs/jquery/thickbox.js b/libs/jquery/thickbox.js new file mode 100644 index 0000000000..588a4da9ee --- /dev/null +++ b/libs/jquery/thickbox.js @@ -0,0 +1,10 @@ +/* + * Thickbox 3 - One Box To Rule Them All. + * By Cody Lindley (http://www.codylindley.com) + * Copyright (c) 2007 cody lindley + * Licensed under the MIT License: http://www.opensource.org/licenses/mit-license.php +*/ + +var tb_pathToImage = "libs/jquery/loadingAnimation.gif"; + +eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('$(o).2S(9(){1u(\'a.18, 3n.18, 3i.18\');1w=1p 1t();1w.L=2H});9 1u(b){$(b).s(9(){6 t=X.Q||X.1v||M;6 a=X.u||X.23;6 g=X.1N||P;19(t,a,g);X.2E();H P})}9 19(d,f,g){3m{3(2t o.v.J.2i==="2g"){$("v","11").r({A:"28%",z:"28%"});$("11").r("22","2Z");3(o.1Y("1F")===M){$("v").q("<U 5=\'1F\'></U><4 5=\'B\'></4><4 5=\'8\'></4>");$("#B").s(G)}}n{3(o.1Y("B")===M){$("v").q("<4 5=\'B\'></4><4 5=\'8\'></4>");$("#B").s(G)}}3(1K()){$("#B").1J("2B")}n{$("#B").1J("2z")}3(d===M){d=""}$("v").q("<4 5=\'K\'><1I L=\'"+1w.L+"\' /></4>");$(\'#K\').2y();6 h;3(f.O("?")!==-1){h=f.3l(0,f.O("?"))}n{h=f}6 i=/\\.2s$|\\.2q$|\\.2m$|\\.2l$|\\.2k$/;6 j=h.1C().2h(i);3(j==\'.2s\'||j==\'.2q\'||j==\'.2m\'||j==\'.2l\'||j==\'.2k\'){1D="";1G="";14="";1z="";1x="";R="";1n="";1r=P;3(g){E=$("a[@1N="+g+"]").36();25(D=0;((D<E.1c)&&(R===""));D++){6 k=E[D].u.1C().2h(i);3(!(E[D].u==f)){3(1r){1z=E[D].Q;1x=E[D].u;R="<1e 5=\'1X\'>&1d;&1d;<a u=\'#\'>2T &2R;</a></1e>"}n{1D=E[D].Q;1G=E[D].u;14="<1e 5=\'1U\'>&1d;&1d;<a u=\'#\'>&2O; 2N</a></1e>"}}n{1r=1b;1n="1t "+(D+1)+" 2L "+(E.1c)}}}S=1p 1t();S.1g=9(){S.1g=M;6 a=2x();6 x=a[0]-1M;6 y=a[1]-1M;6 b=S.z;6 c=S.A;3(b>x){c=c*(x/b);b=x;3(c>y){b=b*(y/c);c=y}}n 3(c>y){b=b*(y/c);c=y;3(b>x){c=c*(x/b);b=x}}13=b+30;1a=c+2G;$("#8").q("<a u=\'\' 5=\'1L\' Q=\'1o\'><1I 5=\'2F\' L=\'"+f+"\' z=\'"+b+"\' A=\'"+c+"\' 23=\'"+d+"\'/></a>"+"<4 5=\'2D\'>"+d+"<4 5=\'2C\'>"+1n+14+R+"</4></4><4 5=\'2A\'><a u=\'#\' 5=\'Z\' Q=\'1o\'>1l</a> 1k 1j 1s</4>");$("#Z").s(G);3(!(14==="")){9 12(){3($(o).N("s",12)){$(o).N("s",12)}$("#8").C();$("v").q("<4 5=\'8\'></4>");19(1D,1G,g);H P}$("#1U").s(12)}3(!(R==="")){9 1i(){$("#8").C();$("v").q("<4 5=\'8\'></4>");19(1z,1x,g);H P}$("#1X").s(1i)}o.1h=9(e){3(e==M){I=2w.2v}n{I=e.2u}3(I==27){G()}n 3(I==3k){3(!(R=="")){o.1h="";1i()}}n 3(I==3j){3(!(14=="")){o.1h="";12()}}};16();$("#K").C();$("#1L").s(G);$("#8").r({Y:"T"})};S.L=f}n{6 l=f.2r(/^[^\\?]+\\??/,\'\');6 m=2p(l);13=(m[\'z\']*1)+30||3h;1a=(m[\'A\']*1)+3g||3f;W=13-30;V=1a-3e;3(f.O(\'2j\')!=-1){1E=f.1B(\'3d\');$("#15").C();3(m[\'1A\']!="1b"){$("#8").q("<4 5=\'2f\'><4 5=\'1H\'>"+d+"</4><4 5=\'2e\'><a u=\'#\' 5=\'Z\' Q=\'1o\'>1l</a> 1k 1j 1s</4></4><U 1W=\'0\' 2d=\'0\' L=\'"+1E[0]+"\' 5=\'15\' 1v=\'15"+1f.2c(1f.1y()*2b)+"\' 1g=\'1m()\' J=\'z:"+(W+29)+"p;A:"+(V+17)+"p;\' > </U>")}n{$("#B").N();$("#8").q("<U 1W=\'0\' 2d=\'0\' L=\'"+1E[0]+"\' 5=\'15\' 1v=\'15"+1f.2c(1f.1y()*2b)+"\' 1g=\'1m()\' J=\'z:"+(W+29)+"p;A:"+(V+17)+"p;\'> </U>")}}n{3($("#8").r("Y")!="T"){3(m[\'1A\']!="1b"){$("#8").q("<4 5=\'2f\'><4 5=\'1H\'>"+d+"</4><4 5=\'2e\'><a u=\'#\' 5=\'Z\'>1l</a> 1k 1j 1s</4></4><4 5=\'F\' J=\'z:"+W+"p;A:"+V+"p\'></4>")}n{$("#B").N();$("#8").q("<4 5=\'F\' 3c=\'3b\' J=\'z:"+W+"p;A:"+V+"p;\'></4>")}}n{$("#F")[0].J.z=W+"p";$("#F")[0].J.A=V+"p";$("#F")[0].3a=0;$("#1H").11(d)}}$("#Z").s(G);3(f.O(\'37\')!=-1){$("#F").q($(\'#\'+m[\'26\']).1T());$("#8").24(9(){$(\'#\'+m[\'26\']).q($("#F").1T())});16();$("#K").C();$("#8").r({Y:"T"})}n 3(f.O(\'2j\')!=-1){16();3($.1q.35){$("#K").C();$("#8").r({Y:"T"})}}n{$("#F").34(f+="&1y="+(1p 33().32()),9(){16();$("#K").C();1u("#F a.18");$("#8").r({Y:"T"})})}}3(!m[\'1A\']){o.21=9(e){3(e==M){I=2w.2v}n{I=e.2u}3(I==27){G()}}}}31(e){}}9 1m(){$("#K").C();$("#8").r({Y:"T"})}9 G(){$("#2Y").N("s");$("#Z").N("s");$("#8").2X("2W",9(){$(\'#8,#B,#1F\').2V("24").N().C()});$("#K").C();3(2t o.v.J.2i=="2g"){$("v","11").r({A:"1Z",z:"1Z"});$("11").r("22","")}o.1h="";o.21="";H P}9 16(){$("#8").r({2U:\'-\'+20((13/2),10)+\'p\',z:13+\'p\'});3(!(1V.1q.2Q&&1V.1q.2P<7)){$("#8").r({38:\'-\'+20((1a/2),10)+\'p\'})}}9 2p(a){6 b={};3(!a){H b}6 c=a.1B(/[;&]/);25(6 i=0;i<c.1c;i++){6 d=c[i].1B(\'=\');3(!d||d.1c!=2){39}6 e=2a(d[0]);6 f=2a(d[1]);f=f.2r(/\\+/g,\' \');b[e]=f}H b}9 2x(){6 a=o.2M;6 w=1S.2o||1R.2o||(a&&a.1Q)||o.v.1Q;6 h=1S.1P||1R.1P||(a&&a.2n)||o.v.2n;1O=[w,h];H 1O}9 1K(){6 a=2K.2J.1C();3(a.O(\'2I\')!=-1&&a.O(\'3o\')!=-1){H 1b}}',62,211,'|||if|div|id|var||TB_window|function||||||||||||||else|document|px|append|css|click||href|body||||width|height|TB_overlay|remove|TB_Counter|TB_TempArray|TB_ajaxContent|tb_remove|return|keycode|style|TB_load|src|null|unbind|indexOf|false|title|TB_NextHTML|imgPreloader|block|iframe|ajaxContentH|ajaxContentW|this|display|TB_closeWindowButton||html|goPrev|TB_WIDTH|TB_PrevHTML|TB_iframeContent|tb_position||thickbox|tb_show|TB_HEIGHT|true|length|nbsp|span|Math|onload|onkeydown|goNext|Esc|or|close|tb_showIframe|TB_imageCount|Close|new|browser|TB_FoundURL|Key|Image|tb_init|name|imgLoader|TB_NextURL|random|TB_NextCaption|modal|split|toLowerCase|TB_PrevCaption|urlNoQuery|TB_HideSelect|TB_PrevURL|TB_ajaxWindowTitle|img|addClass|tb_detectMacXFF|TB_ImageOff|150|rel|arrayPageSize|innerHeight|clientWidth|self|window|children|TB_prev|jQuery|frameborder|TB_next|getElementById|auto|parseInt|onkeyup|overflow|alt|unload|for|inlineId||100||unescape|1000|round|hspace|TB_closeAjaxWindow|TB_title|undefined|match|maxHeight|TB_iframe|bmp|gif|png|clientHeight|innerWidth|tb_parseQuery|jpeg|replace|jpg|typeof|which|keyCode|event|tb_getPageSize|show|TB_overlayBG|TB_closeWindow|TB_overlayMacFFBGHack|TB_secondLine|TB_caption|blur|TB_Image|60|tb_pathToImage|mac|userAgent|navigator|of|documentElement|Prev|lt|version|msie|gt|ready|Next|marginLeft|trigger|fast|fadeOut|TB_imageOff|hidden||catch|getTime|Date|load|safari|get|TB_inline|marginTop|continue|scrollTop|TB_modal|class|TB_|45|440|40|630|input|188|190|substr|try|area|firefox'.split('|'),0,{}))
\ No newline at end of file diff --git a/libs/json/json.js b/libs/json/json.js new file mode 100644 index 0000000000..ccb1d05c1c --- /dev/null +++ b/libs/json/json.js @@ -0,0 +1,317 @@ +/* + json.js + 2007-08-19 + + Public Domain + + This file adds these methods to JavaScript: + + array.toJSONString(whitelist) + boolean.toJSONString() + date.toJSONString() + number.toJSONString() + object.toJSONString(whitelist) + string.toJSONString() + These methods produce a JSON text from a JavaScript value. + It must not contain any cyclical references. Illegal values + will be excluded. + + The default conversion for dates is to an ISO string. You can + add a toJSONString method to any date object to get a different + representation. + + The object and array methods can take an optional whitelist + argument. A whitelist is an array of strings. If it is provided, + keys in objects not found in the whitelist are excluded. + + string.parseJSON(filter) + This method parses a JSON text to produce an object or + array. It can throw a SyntaxError exception. + + The optional filter parameter is a function which can filter and + transform the results. It receives each of the keys and values, and + its return value is used instead of the original value. If it + returns what it received, then structure is not modified. If it + returns undefined then the member is deleted. + + Example: + + // Parse the text. If a key contains the string 'date' then + // convert the value to a date. + + myData = text.parseJSON(function (key, value) { + return key.indexOf('date') >= 0 ? new Date(value) : value; + }); + + It is expected that these methods will formally become part of the + JavaScript Programming Language in the Fourth Edition of the + ECMAScript standard in 2008. + + This file will break programs with improper for..in loops. See + http://yuiblog.com/blog/2006/09/26/for-in-intrigue/ + + This is a reference implementation. You are free to copy, modify, or + redistribute. + + Use your own copy. It is extremely unwise to load untrusted third party + code into your pages. +*/ + +/*jslint evil: true */ + +// Augment the basic prototypes if they have not already been augmented. + +if (!Object.prototype.toJSONString) { + + Array.prototype.toJSONString = function (w) { + var a = [], // The array holding the partial texts. + i, // Loop counter. + l = this.length, + v; // The value to be stringified. + +// For each value in this array... + + for (i = 0; i < l; i += 1) { + v = this[i]; + switch (typeof v) { + case 'object': + +// Serialize a JavaScript object value. Ignore objects thats lack the +// toJSONString method. Due to a specification error in ECMAScript, +// typeof null is 'object', so watch out for that case. + + if (v) { + if (typeof v.toJSONString === 'function') { + a.push(v.toJSONString(w)); + } + } else { + a.push('null'); + } + break; + + case 'string': + case 'number': + case 'boolean': + a.push(v.toJSONString()); + +// Values without a JSON representation are ignored. + + } + } + +// Join all of the member texts together and wrap them in brackets. + + return '[' + a.join(',') + ']'; + }; + + + Boolean.prototype.toJSONString = function () { + return String(this); + }; + + + Date.prototype.toJSONString = function () { + +// Eventually, this method will be based on the date.toISOString method. + + function f(n) { + +// Format integers to have at least two digits. + + return n < 10 ? '0' + n : n; + } + + return '"' + this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z"'; + }; + + + Number.prototype.toJSONString = function () { + +// JSON numbers must be finite. Encode non-finite numbers as null. + + return isFinite(this) ? String(this) : 'null'; + }; + + + Object.prototype.toJSONString = function (w) { + var a = [], // The array holding the partial texts. + k, // The current key. + i, // The loop counter. + v; // The current value. + +// If a whitelist (array of keys) is provided, use it assemble the components +// of the object. + + if (w) { + for (i = 0; i < w.length; i += 1) { + k = w[i]; + if (typeof k === 'string') { + v = this[k]; + switch (typeof v) { + case 'object': + +// Serialize a JavaScript object value. Ignore objects that lack the +// toJSONString method. Due to a specification error in ECMAScript, +// typeof null is 'object', so watch out for that case. + + if (v) { + if (typeof v.toJSONString === 'function') { + a.push(k.toJSONString() + ':' + + v.toJSONString(w)); + } + } else { + a.push(k.toJSONString() + ':null'); + } + break; + + case 'string': + case 'number': + case 'boolean': + a.push(k.toJSONString() + ':' + v.toJSONString()); + +// Values without a JSON representation are ignored. + + } + } + } + } else { + +// Iterate through all of the keys in the object, ignoring the proto chain +// and keys that are not strings. + + for (k in this) { + if (typeof k === 'string' && + Object.prototype.hasOwnProperty.apply(this, [k])) { + v = this[k]; + switch (typeof v) { + case 'object': + +// Serialize a JavaScript object value. Ignore objects that lack the +// toJSONString method. Due to a specification error in ECMAScript, +// typeof null is 'object', so watch out for that case. + + if (v) { + if (typeof v.toJSONString === 'function') { + a.push(k.toJSONString() + ':' + + v.toJSONString()); + } + } else { + a.push(k.toJSONString() + ':null'); + } + break; + + case 'string': + case 'number': + case 'boolean': + a.push(k.toJSONString() + ':' + v.toJSONString()); + +// Values without a JSON representation are ignored. + + } + } + } + } + +// Join all of the member texts together and wrap them in braces. + + return '{' + a.join(',') + '}'; + }; + + + (function (s) { + +// Augment String.prototype. We do this in an immediate anonymous function to +// avoid defining global variables. + +// m is a table of character substitutions. + + var m = { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }; + + + s.parseJSON = function (filter) { + var j; + + function walk(k, v) { + var i; + if (v && typeof v === 'object') { + for (i in v) { + if (Object.prototype.hasOwnProperty.apply(v, [i])) { + v[i] = walk(i, v[i]); + } + } + } + return filter(k, v); + } + + +// Parsing happens in three stages. In the first stage, we run the text against +// a regular expression which looks for non-JSON characters. We are especially +// concerned with '()' and 'new' because they can cause invocation, and '=' +// because it can cause mutation. But just to be safe, we will reject all +// unexpected characters. + +// We split the first stage into 3 regexp operations in order to work around +// crippling deficiencies in Safari's regexp engine. First we replace all +// backslash pairs with '@' (a non-JSON character). Second we delete all of +// the string literals. Third, we look to see if only JSON characters +// remain. If so, then the text is safe for eval. + + if (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/.test(this. + replace(/\\./g, '@'). + replace(/"[^"\\\n\r]*"/g, ''))) { + +// In the second stage we use the eval function to compile the text into a +// JavaScript structure. The '{' operator is subject to a syntactic ambiguity +// in JavaScript: it can begin a block or an object literal. We wrap the text +// in parens to eliminate the ambiguity. + + j = eval('(' + this + ')'); + +// In the optional third stage, we recursively walk the new structure, passing +// each name/value pair to a filter function for possible transformation. + + return typeof filter === 'function' ? walk('', j) : j; + } + +// If the text is not JSON parseable, then a SyntaxError is thrown. + + throw new SyntaxError('parseJSON'); + }; + + + s.toJSONString = function () { + +// If the string contains no control characters, no quote characters, and no +// backslash characters, then we can simply slap some quotes around it. +// Otherwise we must also replace the offending characters with safe +// sequences. + + if (/["\\\x00-\x1f]/.test(this)) { + return '"' + this.replace(/[\x00-\x1f\\"]/g, function (a) { + var c = m[a]; + if (c) { + return c; + } + c = a.charCodeAt(); + return '\\u00' + + Math.floor(c / 16).toString(16) + + (c % 16).toString(16); + }) + '"'; + } + return '"' + this + '"'; + }; + })(String.prototype); +}
\ No newline at end of file diff --git a/misc/generateVisits.php b/misc/generateVisits.php index 15890729a9..5539926a95 100644 --- a/misc/generateVisits.php +++ b/misc/generateVisits.php @@ -20,7 +20,7 @@ require_once "PluginsManager.php"; require_once "LogStats.php"; require_once "LogStats/Config.php"; require_once "LogStats/Action.php"; -require_once "LogStats/Cookie.php"; +require_once "Cookie.php"; require_once "LogStats/Db.php"; require_once "LogStats/Visit.php"; diff --git a/modules/API/APIable.php b/modules/API/APIable.php index 9a9feea6f7..e7987f0df4 100755 --- a/modules/API/APIable.php +++ b/modules/API/APIable.php @@ -5,6 +5,8 @@ * * @package Piwik_API */ +require_once "Archive.php"; + class Piwik_Apiable { static public $methodsNotToPublish = array(); diff --git a/modules/API/Proxy.php b/modules/API/Proxy.php index 5107c82e25..87eda0113e 100755 --- a/modules/API/Proxy.php +++ b/modules/API/Proxy.php @@ -200,11 +200,6 @@ class Piwik_API_Proxy throw new Exception("The number of parameters provided ($nbParamsGiven) is less than the number of required parameters ($nbParamsRequired) for this method. Please check the method API."); } - elseif($nbParamsGiven > $nbParamsRequired) - { - throw new Exception("The number of parameters provided ($nbParamsGiven) is greater than the number of required parameters ($nbParamsRequired) for this method. - Please check the method API."); - } } /** diff --git a/modules/API/Request.php b/modules/API/Request.php index 164a793905..e13be15ae9 100644 --- a/modules/API/Request.php +++ b/modules/API/Request.php @@ -109,7 +109,7 @@ class Piwik_API_Request $requestValue = Piwik_Common::getRequestVar($name, null, null, $this->requestToUse); } } catch(Exception $e) { - Piwik::error("The required variable '$name' is not correct or has not been found in the API Request. <br>\n ".var_export($this->requestToUse, true)); + throw new Exception("The required variable '$name' is not correct or has not been found in the API Request."); } $finalParameters[] = $requestValue; } @@ -130,12 +130,77 @@ class Piwik_API_Request $toReturn = $this->getRenderedDataTable($dataTable); } + + if(empty($toReturn)) + { + $format = Piwik_Common::getRequestVar('format', 'xml', 'string', $this->requestToUse); + $toReturn = $this->getStandardSuccessOutput($format); + } + } catch(Exception $e ) { - $toReturn = 'XML ERROR TEMPLATE TODO', $e; + $format = Piwik_Common::getRequestVar('format', 'xml', 'string', $this->requestToUse); + $toReturn = $this->getExceptionOutput( $e->getMessage(), $format); } + return $toReturn; } + + function getStandardSuccessOutput($format) + { + $return = 'TO OVERWRITE! getStandardSuccessOutput()'; + switch($format) + { + case 'xml': + header('Content-type: text/xml'); + $return = + '<?xml version="1.0" encoding="utf-8" ?>'. + '<result>'. + ' <success message="ok" />'. + '</result>'; + break; + case 'json': + header( "Content-type: application/json" ); + $return = '{"result":"success", "message":"ok"}'; + break; + case 'php': + $return = serialize(array('result' => 'success', 'message' => 'ok')); + break; + default: + $return = 'Success:ok'; + break; + } + + return $return; + } + function getExceptionOutput($message, $format) + { + $return = 'TO OVERWRITE! getExceptionOutput()'; + switch($format) + { + case 'xml': + header('Content-type: text/xml'); + $return = + '<?xml version="1.0" encoding="utf-8" ?>'. + '<result>'. + ' <error message="'.htmlentities($message).'" />'. + '</result>'; + break; + case 'json': + header( "Content-type: application/json" ); + $return = '{"result":"error", "message":"'.htmlentities($message).'"}'; + break; + case 'php': + $return = serialize(array('result' => 'error', 'message' => $message)); + break; + default: + $return = 'Error:'.$message; + break; + } + + return $return; + } + /** * Apply the specified renderer to the DataTable * @return Piwik_DataTable diff --git a/modules/Access.php b/modules/Access.php index ffa3284824..1646fbb8df 100755 --- a/modules/Access.php +++ b/modules/Access.php @@ -20,15 +20,19 @@ * * @package Piwik */ -require_once 'SitesManager.php'; +require_once 'SitesManager/API.php'; + class Piwik_Access { - private $acl = null; private $accesssByIdsite = null; private $idsitesByAccess = null; private $identity = null; //login private $isSuperUser = false; + public function isSuperUser() + { + return $this->isSuperUser; + } static private $availableAccess = array('noaccess', 'view', 'admin', 'superuser'); @@ -170,11 +174,17 @@ class Piwik_Access */ public function checkUserHasSomeAdminAccess() { - $idSitesAccessible = $this->getSitesIdWithAdminAccess(); - if(count($idSitesAccessible) == 0) - { - throw new Piwik_Access_NoAccessException("You can't access this resource as it requires an 'admin' access for at least one website."); - } + //commented because bug when super user method called with unknown websites +// if($this->isSuperUser) +// { +// return; +// } + + $idSitesAccessible = $this->getSitesIdWithAdminAccess(); + if(count($idSitesAccessible) == 0) + { + throw new Piwik_Access_NoAccessException("You can't access this resource as it requires an 'admin' access for at least one website."); + } } /** @@ -185,18 +195,24 @@ class Piwik_Access */ public function checkUserHasAdminAccess( $idSites ) { + //commented because bug when super user method called with unknown websites +// if($this->isSuperUser) +// { +// return; +// } + if(!is_array($idSites)) { $idSites = array($idSites); } - $idSitesAccessible = $this->getSitesIdWithAdminAccess(); - foreach($idSites as $idsite) + $idSitesAccessible = $this->getSitesIdWithAdminAccess(); + foreach($idSites as $idsite) + { + if(!in_array($idsite, $idSitesAccessible)) { - if(!in_array($idsite, $idSitesAccessible)) - { - throw new Piwik_Access_NoAccessException("You can't access this resource as it requires an 'admin' access for the website id = $idsite."); - } + throw new Piwik_Access_NoAccessException("You can't access this resource as it requires an 'admin' access for the website id = $idsite."); } + } } @@ -208,18 +224,24 @@ class Piwik_Access */ public function checkUserHasViewAccess( $idSites ) { + //commented because bug when super user method called with unknown websites +// if($this->isSuperUser) +// { +// return; +// } + if(!is_array($idSites)) { $idSites = array($idSites); } - $idSitesAccessible = $this->getSitesIdWithAtLeastViewAccess(); - foreach($idSites as $idsite) + $idSitesAccessible = $this->getSitesIdWithAtLeastViewAccess(); + foreach($idSites as $idsite) + { + if(!in_array($idsite, $idSitesAccessible)) { - if(!in_array($idsite, $idSitesAccessible)) - { - throw new Piwik_Access_NoAccessException("You can't access this resource as it requires a 'view' access for the website id = $idsite."); - } + throw new Piwik_Access_NoAccessException("You can't access this resource as it requires a 'view' access for the website id = $idsite."); } + } } } diff --git a/modules/Archive.php b/modules/Archive.php index b527e3ea41..1c10a3b6cb 100644 --- a/modules/Archive.php +++ b/modules/Archive.php @@ -28,6 +28,7 @@ */ require_once 'Period.php'; +require_once 'Date.php'; require_once 'ArchiveProcessing.php'; class Piwik_Archive diff --git a/modules/Auth.php b/modules/Auth.php index 94769e0824..0e6c2adef8 100644 --- a/modules/Auth.php +++ b/modules/Auth.php @@ -18,13 +18,13 @@ class Piwik_Auth extends Zend_Auth_Adapter_DbTable // we first try if the user is the super user $login = $this->_identity; - $token = $this->_credential; + $this->token = $this->_credential; $rootLogin = Zend_Registry::get('config')->superuser->login; $rootPassword = Zend_Registry::get('config')->superuser->password; $rootToken = Piwik_UsersManager_API::getTokenAuth($rootLogin,$rootPassword); if($login == $rootLogin - && $token == $rootToken) + && $this->token == $rootToken) { return new Piwik_Auth_Result(Piwik_Auth::SUCCESS_SUPERUSER_AUTH_CODE, $login, @@ -36,6 +36,10 @@ class Piwik_Auth extends Zend_Auth_Adapter_DbTable return parent::authenticate(); } + public function getTokenAuth() + { + return $this->token; + } } diff --git a/modules/LogStats/Cookie.php b/modules/Cookie.php index ff479442f2..bfa2639476 100644 --- a/modules/LogStats/Cookie.php +++ b/modules/Cookie.php @@ -12,7 +12,7 @@ * * @package Piwik_LogStats */ -class Piwik_LogStats_Cookie +class Piwik_Cookie { /** * The name of the cookie @@ -117,7 +117,7 @@ class Piwik_LogStats_Cookie /** * Delete the cookie */ - public function deleteCookie() + public function delete() { $this->setP3PHeader(); setcookie($this->name, false, time() - 86400); @@ -155,7 +155,7 @@ class Piwik_LogStats_Cookie if(!is_numeric($varValue)) { $varValue = base64_decode($varValue); - + // some of the values may be serialized array so we try to unserialize it if( ($arrayValue = @unserialize($varValue)) !== false // we set the unserialized version only for arrays as you can have set a serialized string on purpose @@ -253,7 +253,7 @@ class Piwik_LogStats_Cookie } -//$c = new Piwik_LogStats_Cookie( 'piwik_logstats', 86400); +//$c = new Piwik_Cookie( 'piwik_logstats', 86400); //echo $c; //$c->set(1,1); //$c->set('test',1); @@ -262,12 +262,10 @@ class Piwik_LogStats_Cookie //$c->set('test4',array(array(0=>1),1=>'test')); //echo $c; //echo "<br>"; -//echo $c->generateContentString(); -//echo "<br>"; //$v=$c->get('more!'); //if(empty($v)) $c->set('more!',1); //$c->set('more!', array($c->get('more!'))); //$c->save(); -//$c->deleteCookie(); +//$c->delete(); diff --git a/modules/DataTable.php b/modules/DataTable.php index da835fd200..3f61adb598 100644 --- a/modules/DataTable.php +++ b/modules/DataTable.php @@ -150,14 +150,12 @@ class Piwik_DataTable { foreach($this->queuedFilters as $filter) { - // make a reflection object $reflectionObj = new ReflectionClass($filter['className']); // the first parameter of a filter is the DataTable // we add the current datatable as the parameter $filter['parameters'] = array_merge(array($this), $filter['parameters']); - // use Reflection to create a new instance, using the $args $filter = $reflectionObj->newInstanceArgs($filter['parameters']); } $this->queuedFilters = array(); @@ -402,7 +400,7 @@ class Piwik_DataTable $aSerializedDataTable = $aSerializedDataTable + $serialized; } } - + //TODO COmment $forcedId = $this->getId(); if($depth==0) { diff --git a/modules/DataTable/Renderer.php b/modules/DataTable/Renderer.php index 4e921d2386..446e341466 100644 --- a/modules/DataTable/Renderer.php +++ b/modules/DataTable/Renderer.php @@ -46,6 +46,7 @@ abstract class Piwik_DataTable_Renderer * Returns the DataTable associated to the output format $name * @exception If the renderer is unknown */ + //TODO make a generic code here static public function factory( $name ) { $name = strtolower($name); diff --git a/modules/DataTable/Renderer/Console.php b/modules/DataTable/Renderer/Console.php index e6a4b82980..766c63b35e 100644 --- a/modules/DataTable/Renderer/Console.php +++ b/modules/DataTable/Renderer/Console.php @@ -49,15 +49,20 @@ class Piwik_DataTable_Renderer_Console extends Piwik_DataTable_Renderer $details[] = "'$detail' => $value"; } $details = implode(", ", $details); - $output.= str_repeat($this->prefixRows, $depth) . "- $i [".$columns."] [".$details."] [idsubtable = ".$row->getIdSubDataTable()."]<br>\n"; + $output.= str_repeat($this->prefixRows, $depth) + . "- $i [".$columns."] [".$details."] [idsubtable = " + . $row->getIdSubDataTable()."]<br>\n"; if($row->getIdSubDataTable() !== null) { $depth++; try{ - $output.= $this->renderTable( Piwik_DataTable_Manager::getInstance()->getTable($row->getIdSubDataTable())); - } catch(Exception $e) - { + $output.= $this->renderTable( + Piwik_DataTable_Manager::getInstance()->getTable( + $row->getIdSubDataTable() + ) + ); + } catch(Exception $e) { $output.= "-- Sub DataTable not loaded<br>\n"; } $depth--; diff --git a/modules/Form.php b/modules/Form.php index 134c6447e1..edc89416bc 100644 --- a/modules/Form.php +++ b/modules/Form.php @@ -3,11 +3,67 @@ require_once "HTML/QuickForm.php"; require_once "HTML/QuickForm/Renderer/ArraySmarty.php"; -class Piwik_Form extends HTML_QuickForm +abstract class Piwik_Form extends HTML_QuickForm { + private $a_formElements = array(); function __construct( $action = '' ) { + if(empty($action)) + { + $action = Piwik_Url::getCurrentUrl(); + } parent::HTML_QuickForm('form', 'POST', $action); + + $this->init(); + } + + abstract function init(); + + function getElementList() + { + $listElements=array(); + foreach($this->a_formElements as $title => $a_parameters) + { + foreach($a_parameters as $parameters) + { + if($parameters[1] != 'headertext' + && $parameters[1] != 'submit') + { + // case radio : there are two labels but only record once, unique name + if( !isset($listElements[$title]) + || !in_array($parameters[1], $listElements[$title])) + { + $listElements[$title][] = $parameters[1]; + } + } + } + } + return $listElements; + } + + function addElements( $a_formElements, $sectionTitle = '' ) + { + foreach($a_formElements as $parameters) + { + call_user_func_array(array(&$this , "addElement"), $parameters ); + } + + $this->a_formElements = + array_merge( + $this->a_formElements, + array( + $sectionTitle => $a_formElements + ) + ); + } + + function addRules( $a_formRules) + { + foreach($a_formRules as $parameters) + { + call_user_func_array(array(&$this , "addRule"), $parameters ); + } + } } diff --git a/modules/FrontController.php b/modules/FrontController.php new file mode 100644 index 0000000000..6bacad7ac0 --- /dev/null +++ b/modules/FrontController.php @@ -0,0 +1,107 @@ +<?php + +class Piwik_FrontController +{ + function dispatch() + { + $defaultModule = 'Home'; + + // load the module requested + $module = Piwik_Common::getRequestVar('module', $defaultModule, 'string'); + + if(ctype_alnum($module)) + { + $moduleController = PIWIK_PLUGINS_PATH . "/" . $module . "/Controller.php"; + if(is_readable($moduleController)) + { + require_once $moduleController; + + $controllerClassName = "Piwik_".$module."_Controller"; + + $controller = new $controllerClassName; + + $defaultAction = $controller->getDefaultAction(); + $action = Piwik_Common::getRequestVar('action', $defaultAction, 'string'); + + try{ + $controller->$action(); + } catch(Piwik_Access_NoAccessException $e) { + Piwik::log("NO ACCESS EXCEPTION =>"); + Piwik_PostEvent('FrontController.NoAccessException', $e); + } + } + else + { + throw new Exception("Module controller $moduleController not found!"); + } + } + else + { + throw new Exception("Invalid module name"); + } + + } + + function end() + { + + Piwik::printZendProfiler(); + Piwik::printMemoryUsage(); + Piwik::printQueryCount(); +// Piwik::uninstall(); + + Piwik::log($this->timer); + + } + + function init() + { + $this->timer = new Piwik_Timer; + + //move into a init() method + Piwik::createConfigObject(); + + // database object + Piwik::createDatabaseObject(); + + // Create the log objects + Piwik::createLogObject(); + + Piwik::printMemoryUsage('Start program'); + //TODO move all DB related methods in a DB static class + + //Piwik::createDatabase(); + //Piwik::createDatabaseObject(); + + $doNotDrop = array( + Piwik::prefixTable('log_visit'), + Piwik::prefixTable('access'), + Piwik::prefixTable('user'), + Piwik::prefixTable('site'), + Piwik::prefixTable('log_link_visit_action'), + Piwik::prefixTable('log_action'), + Piwik::prefixTable('log_profiling'), + Piwik::prefixTable('archive'), + ); + + Piwik::dropTables($doNotDrop); + Piwik::createTables(); + + // load plugins + Piwik_PluginsManager::getInstance()->setInstallPlugins(); + //TODO plugins install to handle in a better way + Piwik::loadPlugins(); + + // Create auth object + Zend_Registry::set('auth', $authAdapter = new Piwik_Auth()); + + // Setup the auth object + Piwik_PostEvent('FrontController.authSetCredentials'); + + // Perform the authentication query, saving the result + $access = new Piwik_Access($authAdapter); + Zend_Registry::set('access', $access); + Zend_Registry::get('access')->loadAccess(); + } +} + diff --git a/modules/Log/Error.php b/modules/Log/Error.php index 015695c3b2..208bfb5ae5 100644 --- a/modules/Log/Error.php +++ b/modules/Log/Error.php @@ -63,7 +63,11 @@ class Piwik_Log_Formatter_Error_ScreenFormatter implements Zend_Log_Formatter_In $strReturned = ''; $errno = $errno & error_reporting(); - //if($errno == 0) return ''; + + // problem when using error_reporting with the @ silent fail operator + // it gives an errno 0, and in this case the objective is to NOT display anything on the screen! + // is there any other case where the errno is zero at this point? + if($errno == 0) return ''; if(!defined('E_STRICT')) define('E_STRICT', 2048); if(!defined('E_RECOVERABLE_ERROR')) define('E_RECOVERABLE_ERROR', 4096); if(!defined('E_EXCEPTION')) define('E_EXCEPTION', 8192); @@ -83,7 +87,7 @@ class Piwik_Log_Formatter_Error_ScreenFormatter implements Zend_Log_Formatter_In case E_USER_NOTICE: $strReturned .= "User Notice"; break; case E_STRICT: $strReturned .= "Strict Notice"; break; case E_RECOVERABLE_ERROR: $strReturned .= "Recoverable Error"; break; - case E_EXCEPTION: $strReturned .= "Exception"; break; + case E_EXCEPTION: $strReturned .= "Exception"; break; default: $strReturned .= "Unknown error ($errno)"; break; } $strReturned .= ":</b> <i>$errstr</i> in <b>$errfile</b> on line <b>$errline</b>\n"; diff --git a/modules/LogStats/Generator.php b/modules/LogStats/Generator.php index 36090821d0..2bbdd3242c 100644 --- a/modules/LogStats/Generator.php +++ b/modules/LogStats/Generator.php @@ -27,13 +27,17 @@ class Piwik_LogStats_Generator { - private $currentget=array(); - private $allget=array(); - public $profiling; + protected $currentget = array(); + protected $allget = array(); + protected $maximumUrlDepth = 1; + protected $timestampToUse; + + public $profiling = true; public $reinitProfilingAtEveryRequest = true; - private $maximumUrlDepth = 1; + + //TODO also make this variable dynamic so that a visitor can make hit on several hosts and + // only the good ones are kept public $host = 'http://localhost'; - protected $timestampToUse; public function __construct() { @@ -44,29 +48,57 @@ class Piwik_LogStats_Generator require_once "Piwik.php"; Piwik::createConfigObject('../config/config.ini.php'); + // setup database Piwik::createDatabaseObject(); - $this->profiling = true; Piwik_LogStats_Db::enableProfiling(); $this->timestampToUse = time(); } + /** + * Sets the depth level of the generated URLs + * value = 1 => path OR path/page1 + * value = 2 => path OR path/pageRand OR path/dir1/pageRand + * + * @param int Depth + */ public function setMaximumUrlDepth($value) { $this->maximumUrlDepth = (int)$value; } + /** + * Set the timestamp to use as the starting time for the visitors times + * To be set with every day value + * + * @param int Unix timestamp + */ public function setTimestampToUse($timestamp) { $this->timestampToUse = $timestamp; } + + /** + * Returns the timestamp to be used as the visitor timestamp + * + * @return int + */ public function getTimestampToUse() { return $this->timestampToUse; } - public function addParam( $name, $aValue) + + /** + * Add a parameter to the GET global array + * We set an array value to the GET global array when we want to random select + * a value for a given name. + * + * @param string Name of the parameter _GET[$name] + * @param array|mixed Value of the parameter + */ + protected function addParam( $name, $aValue) { if(is_array($aValue)) { @@ -79,7 +111,9 @@ class Piwik_LogStats_Generator } } - + /** + * TRUNCATE all logs related tables to start a fresh logging database + */ public function emptyAllLogTables() { $db = Zend_Registry::get('db'); @@ -88,14 +122,19 @@ class Piwik_LogStats_Generator $db->query('TRUNCATE TABLE '.Piwik::prefixTable('log_link_visit_action')); } - + /** + * Call this method to disable the SQL query profiler + */ public function disableProfiler() { $this->profiling = false; Piwik_LogStats_Db::disableProfiling(); } - + /** + * This marks the end of the Generator script + * and calls the Profiler output if the profiler is enabled + */ public function end() { if($this->profiling) @@ -104,6 +143,14 @@ class Piwik_LogStats_Generator } } + /** + * Init the Generator script: + * - init the SQL profiler + * - init the random generator + * - setup the different possible values for parameters such as 'resolution', + * 'color', 'hour', 'minute', etc. + * - load and setup values for the other parameters + */ public function init() { if($this->profiling) @@ -131,33 +178,40 @@ class Piwik_LogStats_Generator 's' => range(0,59), ); - foreach($common as $label => $values) { $this->addParam($label,$values); } + // we get the name of the Download/outlink variables $downloadOrOutlink = array( Piwik_LogStats_Config::getInstance()->LogStats['download_url_var_name'], Piwik_LogStats_Config::getInstance()->LogStats['outlink_url_var_name'], ); + // we have a 20% chance to add a download or outlink variable to the URL $this->addParam('piwik_downloadOrOutlink', $downloadOrOutlink); $this->addParam('piwik_downloadOrOutlink', array_fill(0,8,'')); + // we get the variables name for the campaign parameters $campaigns = array( Piwik_LogStats_Config::getInstance()->LogStats['campaign_var_name'], Piwik_LogStats_Config::getInstance()->LogStats['newsletter_var_name'], Piwik_LogStats_Config::getInstance()->LogStats['partner_var_name'], ); + // we generate a campaign in the URL in 3/18 % of the generated URls $this->addParam('piwik_vars_campaign', $campaigns); - $this->addParam('piwik_vars_campaign', array_fill(0,5,'')); + $this->addParam('piwik_vars_campaign', array_fill(0,15,'')); + // we load some real referers to be used by the generator $referers = array(); require_once "misc/generateVisitsData/Referers.php"; - + $this->addParam('urlref',$referers); + + // and we add 2000 empty referers so that some visitors don't come using a referer (direct entry) $this->addParam('urlref',array_fill(0,2000,'')); + // load some user agent and accept language $userAgent = $acceptLanguages = array(); require_once "misc/generateVisitsData/UserAgent.php"; require_once "misc/generateVisitsData/AcceptLanguage.php"; @@ -165,12 +219,18 @@ class Piwik_LogStats_Generator $this->acceptLanguage=$acceptLanguages; } + /** + * Launches the process and generates an exact number of nbVisits + * For each visit, we setup the timestamp to the common timestamp + * Then we generate between 1 and nbActionsMaxPerVisit actions for this visit + * + * @return int The number of total actions generated + */ public function generate( $nbVisits, $nbActionsMaxPerVisit ) { $nbActionsTotal = 0; for($i = 0; $i < $nbVisits; $i++) { -// print("$i "); $nbActions = mt_rand(1, $nbActionsMaxPerVisit); Piwik_LogStats_Generator_Visit::setTimestampToUse($this->getTimestampToUse()); @@ -190,6 +250,12 @@ class Piwik_LogStats_Generator return $nbActionsTotal; } + /** + * Generate a new visit. Load a random value for + * all the parameters that are read by the piwik logging engine. + * + * We even set the _SERVER values + */ private function generateNewVisit() { $this->setCurrentRequest( 'urlref' , $this->getRandom('urlref')); @@ -213,29 +279,37 @@ class Piwik_LogStats_Generator $_SERVER['HTTP_ACCEPT_LANGUAGE'] = $this->acceptLanguage[mt_rand(0,count($this->acceptLanguage)-1)]; } - + /** + * Generates a new action for the current visitor. + * We random generate some campaigns, action names, + * download or outlink clicks, etc. + * + */ private function generateActionVisit() { - // we don't keep the previous action values // reinit them to empty string + // we don't keep the previous action values + // reinit them to empty string $this->setCurrentRequest( Piwik_LogStats_Config::getInstance()->LogStats['download_outlink_name_var'],''); $this->setCurrentRequest( Piwik_LogStats_Config::getInstance()->LogStats['download_url_var_name'],''); $this->setCurrentRequest( Piwik_LogStats_Config::getInstance()->LogStats['outlink_url_var_name'],''); $this->setCurrentRequest( 'action_name', ''); // generate new url referer ; case the visitor stays more than 30min - // we set it as a new visit and the referer will then be used + // (when the visit is known this value will simply be ignored) $this->setCurrentRequest( 'urlref' , $this->getRandom('urlref')); + // generates the current URL $url = $this->getRandomUrlFromHost($this->host); // we generate a campaign (partner or newsletter or campaign) $urlVars = $this->getRandom('piwik_vars_campaign'); - // campaign name - $urlValue = $this->getRandomString(5,3,'lower'); // if we actually generated a campaign if(!empty($urlVars)) { + // campaign name + $urlValue = $this->getRandomString(5,3,'lower'); + // add the parameter to the url $url .= '?'. $urlVars . '=' . $urlValue; @@ -260,27 +334,32 @@ class Piwik_LogStats_Generator // add the parameter to the url $this->setCurrentRequest( $GETParamToAdd , $urlValue); + // in 50% we give a special name to the download/outlink if(mt_rand(0,1)==0) { $this->setCurrentRequest( Piwik_LogStats_Config::getInstance()->LogStats['download_outlink_name_var'] , $this->getRandomString(6,3,'ALL')); } } - else + + // if we didn't set any campaign NOR any download click + // then we sometimes set a special action name to the current action + elseif(rand(0,2)==1) { - if(rand(0,2)==1) - { - $this->setCurrentRequest( 'action_name' , $this->getRandomString(1,1)); - } + $this->setCurrentRequest( 'action_name' , $this->getRandomString(1,1)); } } -// print($url . "<br>"); $this->setCurrentRequest( 'url' ,$url); + // setup the title of the page $this->setCurrentRequest( 'title',$this->getRandomString(15,5)); } + /** + * Returns a random URL using the $host as the URL host. + * Depth level depends on @see setMaximumUrlDepth() + */ private function getRandomUrlFromHost( $host ) { $url = $host; @@ -295,7 +374,18 @@ class Piwik_LogStats_Generator return $url; } - // from php.net and edited + /** + * Generates a random string from minLength to maxLenght + * using a specified set of characters + * + * From php.net and then badly hacked by myself + * + * @param int Maximum length + * @param int Minimum length + * @param string Characters set to use, ALL or lower or upper or numeric or ALPHA or ALNUM + * + * @return string The generated random string + */ private function getRandomString($maxLength = 15, $minLength = 5, $type = 'ALL') { $len = mt_rand($minLength, $maxLength); @@ -358,16 +448,32 @@ class Piwik_LogStats_Generator return join("", $key); } + /** + * Set the _GET and _REQUEST superglobal to the current generated array of values + */ private function setFakeRequest() { $_REQUEST = $_GET = $this->currentget; } + /** + * Set a value in the current request + * + * @param string Name of the parameter to set + * @param string Value of the parameter + */ private function setCurrentRequest($name,$value) { $this->currentget[$name] = $value; } + /** + * Returns a value for the given parameter $name + * + * @throws Exception if the parameter asked for has never been set + * + * @return mixed Random value for the parameter named $name + */ private function getRandom( $name ) { if(!isset($this->allget[$name])) @@ -381,13 +487,21 @@ class Piwik_LogStats_Generator return $value; } } - + + /** + * Returns either 0 or 1 + * @return int + */ private function getRandom01() { return mt_rand(0,1); } - + /** + * Saves the visit + * - set the fake request + * - load the LogStats class and call the method to launch the recording + */ private function saveVisit() { $this->setFakeRequest(); @@ -397,6 +511,10 @@ class Piwik_LogStats_Generator } +/** + * Fake Piwik_LogStats that simply overwrite the sendHeader method + * so that no headers are sent + */ class Piwik_LogStats_Generator_Main extends Piwik_LogStats { protected function sendHeader($header) @@ -405,6 +523,10 @@ class Piwik_LogStats_Generator_Main extends Piwik_LogStats } } +/** + * Fake Piwik_LogStats_Visit class that overwrite all the Time related method to be able + * to setup a given timestamp for the generated visitor and actions. + */ class Piwik_LogStats_Generator_Visit extends Piwik_LogStats_Visit { static protected $timestampToUse; diff --git a/modules/LogStats/Visit.php b/modules/LogStats/Visit.php index e98d156fa9..c163321972 100644 --- a/modules/LogStats/Visit.php +++ b/modules/LogStats/Visit.php @@ -86,7 +86,7 @@ class Piwik_LogStats_Visit */ private function getCookieName() { - return Piwik_LogStats_Config::getInstance()->LogStats['cookie_name'] . $this->idsite; + return Piwik_Config::getInstance()->LogStats['cookie_name'] . $this->idsite; } @@ -123,7 +123,7 @@ class Piwik_LogStats_Visit { $this->visitorKnown = false; - $this->cookieLog = new Piwik_LogStats_Cookie( $this->getCookieName() ); + $this->cookieLog = new Piwik_Cookie( $this->getCookieName() ); /* * Case the visitor has the piwik cookie. * We make sure all the data that should saved in the cookie is available. @@ -777,7 +777,7 @@ class Piwik_LogStats_Visit /** * Returns either * - "-1" for a known visitor - * - a unique 32 char identifier + * - a unique 32 char identifier @see Piwik_Common::generateUniqId() */ private function getVisitorUniqueId() { diff --git a/modules/Piwik.php b/modules/Piwik.php index 0931d2d6ae..8237a12508 100755 --- a/modules/Piwik.php +++ b/modules/Piwik.php @@ -26,28 +26,6 @@ class Piwik Zend_Registry::get('logger_message')->log( "<br>" . PHP_EOL); } - static function displayZendProfiler() - { - $profiler = Zend_Registry::get('db')->getProfiler(); - - $totalTime = $profiler->getTotalElapsedSecs(); - $queryCount = $profiler->getTotalNumQueries(); - $longestTime = 0; - $longestQuery = null; - - foreach ($profiler->getQueryProfiles() as $query) { - if ($query->getElapsedSecs() > $longestTime) { - $longestTime = $query->getElapsedSecs(); - $longestQuery = $query->getQuery(); - } - } - - echo '<br>Executed ' . $queryCount . ' queries in ' . $totalTime . ' seconds' . "\n"; - echo '<br>Average query length: ' . $totalTime / $queryCount . ' seconds' . "\n"; - echo '<br>Queries per second: ' . $queryCount / $totalTime . "\n"; - echo '<br>Longest query length: ' . $longestTime . "\n"; - echo '<br>Longest query: <br>' . $longestQuery . "\n"; - } static public function error($message = '') { @@ -100,9 +78,34 @@ class Piwik <br>"; } - print($str); + Piwik::log($str); } + static function printZendProfiler() + { + $profiler = Zend_Registry::get('db')->getProfiler(); + + $totalTime = $profiler->getTotalElapsedSecs(); + $queryCount = $profiler->getTotalNumQueries(); + $longestTime = 0; + $longestQuery = null; + + foreach ($profiler->getQueryProfiles() as $query) { + if ($query->getElapsedSecs() > $longestTime) { + $longestTime = $query->getElapsedSecs(); + $longestQuery = $query->getQuery(); + } + } + $str = ''; + $str .= '<br>Executed ' . $queryCount . ' queries in ' . $totalTime . ' seconds' . "\n"; + $str .= '<br>Average query length: ' . $totalTime / $queryCount . ' seconds' . "\n"; + $str .= '<br>Queries per second: ' . $queryCount / $totalTime . "\n"; + $str .= '<br>Longest query length: ' . $longestTime . "\n"; + $str .= '<br>Longest query: <br>' . $longestQuery . "\n"; + + Piwik::log($str); + } + static public function printMemoryUsage( $prefixString = null ) { if(function_exists('xdebug_memory_usage')) @@ -115,12 +118,16 @@ class Piwik } $usage = round( $memory / 1024 / 1024, 2); - if(!is_null($prefixString)) + + if(false) { - Piwik::log($prefixString); + if(!is_null($prefixString)) + { + Piwik::log($prefixString); + } + Piwik::log("Memory usage = $usage Mb"); + Piwik::log(); } - Piwik::log("Memory usage = $usage Mb"); - Piwik::log(); } static public function isNumeric($value) @@ -453,10 +460,10 @@ class Piwik static public function createLogObject() { - require_once PIWIK_INCLUDE_PATH . "/modules/Log/APICall.php"; - require_once PIWIK_INCLUDE_PATH . "/modules/Log/Exception.php"; - require_once PIWIK_INCLUDE_PATH . "/modules/Log/Error.php"; - require_once PIWIK_INCLUDE_PATH . "/modules/Log/Message.php"; + require_once "Log/APICall.php"; + require_once "Log/Exception.php"; + require_once "Log/Error.php"; + require_once "Log/Message.php"; $configAPI = Zend_Registry::get('config')->log; @@ -527,7 +534,6 @@ class Piwik && !ereg($doNotDeletePattern,$tableName) ) { - print("drop $tableName "); $db->query("DROP TABLE $tableName"); } } diff --git a/modules/Plugin.php b/modules/Plugin.php index f1535acc18..58a678e68a 100644 --- a/modules/Plugin.php +++ b/modules/Plugin.php @@ -57,7 +57,10 @@ abstract class Piwik_Plugin /** * Returns the list of hooks registered with the methods names */ - abstract function getListHooksRegistered(); + function getListHooksRegistered() + { + return array(); + } /** * Returns the names of the required plugins diff --git a/modules/Url.php b/modules/Url.php index aa7806e0d5..29f289aefc 100644 --- a/modules/Url.php +++ b/modules/Url.php @@ -1,7 +1,21 @@ <?php class Piwik_Url { + static public function redirectToUrl( $url ) + { + header("Location: $url"); + exit; + } + static public function getReferer() + { + if(!empty($_SERVER['HTTP_REFERER'])) + { + return $_SERVER['HTTP_REFERER']; + } + return false; + } + static public function getCurrentUrl() { return self::getCurrentHost() @@ -9,6 +23,12 @@ class Piwik_Url . self::getCurrentQueryString(); } + static public function getCurrentUrlWithoutQueryString() + { + + return self::getCurrentHost() + . self::getCurrentScriptName() ; + } static public function getCurrentScriptName() { $url = ''; diff --git a/modules/View.php b/modules/View.php index 2e530dad4f..e8b34ca3c9 100644 --- a/modules/View.php +++ b/modules/View.php @@ -3,6 +3,7 @@ require_once 'Smarty/Smarty.class.php'; class Piwik_View { + private $template = ''; private $smarty = false; private $variables = array(); @@ -11,10 +12,15 @@ class Piwik_View $this->template = $templateFile; $this->smarty = new Smarty(); + if(count($smConf) == 0) + { + $smConf = Zend_Registry::get('config')->smarty; + } foreach($smConf as $key => $value) { $this->smarty->$key = $value; } + $this->smarty->template_dir = $smConf->template_dir->toArray(); } /** @@ -26,7 +32,7 @@ class Piwik_View */ public function __set($key, $val) { - $this->variables[$key] = $val; + $this->smarty->assign($key, $val); } /** @@ -37,11 +43,7 @@ class Piwik_View */ public function __get($key) { - if(!isset($this->variables[$key])) - { - throw new Exception("Variable $key not known!"); - } - return $this->variables[$key]; + return $this->smarty->get_template_vars($key); } public function render() @@ -59,7 +61,8 @@ class Piwik_View $form->accept($renderer); // assign array with form data - $this->smarty->assign('form', $renderer->toArray()); + $this->smarty->assign('form_data', $renderer->toArray()); + $this->smarty->assign('element_list', $form->getElementList());//$renderer->toArray()); } public function assign($var, $value=null) @@ -38,7 +38,7 @@ require_once "PluginsManager.php"; require_once "LogStats.php"; require_once "LogStats/Config.php"; require_once "LogStats/Action.php"; -require_once "LogStats/Cookie.php"; +require_once "Cookie.php"; require_once "LogStats/Db.php"; require_once "LogStats/Visit.php"; diff --git a/plugins/API/Controller.php b/plugins/API/Controller.php new file mode 100644 index 0000000000..144ec683c9 --- /dev/null +++ b/plugins/API/Controller.php @@ -0,0 +1,13 @@ +<?php +require_once "API/Request.php"; + +class Piwik_API_Controller extends Piwik_Controller +{ + function index() + { +// sleep(1); + $request = new Piwik_API_Request(); + echo $request->process(); + } +} + diff --git a/plugins/Home/Controller.php b/plugins/Home/Controller.php index f292e35f7f..9d79081f93 100644 --- a/plugins/Home/Controller.php +++ b/plugins/Home/Controller.php @@ -9,7 +9,6 @@ class Piwik_Home_Controller extends Piwik_Controller function homepage() { - print("HOMEPAGE!"); main(); } } @@ -26,15 +25,16 @@ function main() Piwik::log("Start process..."); $api = Piwik_API_Proxy::getInstance(); - - $api->SitesManager->getSiteUrlsFromId(1); - - $api->SitesManager->addSite("test name site", array("http://localhost", "http://test.com")); - - Zend_Registry::get('access')->loadAccess(); - - $api->UsersManager->deleteUser("login"); - $api->UsersManager->addUser("login", "password", "email@geage.com"); +// $api->SitesManager->addSite("t2site2", array("http://localhost44", "http://test123.com")); +// $api->SitesManager->addSite("2e site33", array("http://localhost52", "http://test123.com")); +// $api->SitesManager->addSite("te2 site44", array("http://localhost31231", "http://test123.com")); +// +// $api->SitesManager->addSite("test name site", array("http://localhost", "http://test.com")); +// +// $api->SitesManager->getSiteUrlsFromId(1); + + //$api->UsersManager->deleteUser("login"); +// $api->UsersManager->addUser("login", "password", "email@geage.com"); require_once "API/Request.php"; diff --git a/plugins/Login.php b/plugins/Login.php index bbcc3ddf92..9a50e3457a 100644 --- a/plugins/Login.php +++ b/plugins/Login.php @@ -1,6 +1,6 @@ <?php require "Login/Controller.php"; -require "LogStats/Cookie.php"; +require "Cookie.php"; class Piwik_Login extends Piwik_Plugin { @@ -41,10 +41,13 @@ class Piwik_Login extends Piwik_Plugin return $hooks; } - function noAccess() + function noAccess( $notification ) { + $exception = $notification->getNotificationObject(); + $exceptionMessage = $exception->getMessage(); + $controller = new Piwik_Login_Controller; - $controller->login(); + $controller->login($exceptionMessage); } function authSetCredentials($notification) @@ -54,14 +57,14 @@ class Piwik_Login extends Piwik_Plugin $authCookieName = 'piwik-auth'; $authCookieExpiry = time() + 3600; - $authCookie = new Piwik_LogStats_Cookie($authCookieName, $authCookieExpiry); + $authCookie = new Piwik_Cookie($authCookieName, $authCookieExpiry); $login = $tokenAuth = 'abc'; if($authCookie->isCookieFound()) { $login = $authCookie->get('login'); - $tokenAuth = $authCookie->get('auth'); + $tokenAuth = $authCookie->get('token'); } $this->prepareAuthObject( $login, $tokenAuth); diff --git a/plugins/Login/Controller.php b/plugins/Login/Controller.php index 03d1fe92a7..d227b16878 100644 --- a/plugins/Login/Controller.php +++ b/plugins/Login/Controller.php @@ -1,6 +1,7 @@ <?php -require_once "UsersManager.php"; -require_once "Form.php"; +require_once "UsersManager/API.php"; +require_once "Login/Form.php"; +require_once "View.php"; class Piwik_Login_Controller extends Piwik_Controller { function getDefaultAction() @@ -8,25 +9,21 @@ class Piwik_Login_Controller extends Piwik_Controller return 'login'; } - function login() - { - print("<br>-----------<br>--LOGIN PAGE<br>-----------"); - + function login( $messageNoAccess = null ) + { $form = new Piwik_Login_Form; + $AccessErrorString = false; if($form->validate()) - { - $authCookieName = 'piwik-auth'; - $authCookieExpiry = time() + 3600; - + { // value submitted in form - $login = 'root'; - $password = 'nintendo'; + $login = $form->getSubmitValue('form_login'); + $password = $form->getSubmitValue('form_password'); + + $baseUrl = Piwik_Url::getCurrentUrlWithoutQueryString(); + $currentUrl = Piwik_Url::getCurrentUrl(); + $urlToRedirect = Piwik_Common::getRequestVar('form_url', $currentUrl, 'string', $_POST); - $baseUrl = Piwik_Url::getCurrentHost() . Piwik_Url::getCurrentScriptName(); - - $urlToRedirect = Piwik_Common::getRequestVar('url', $baseUrl, 'string', $_POST); - $currentUrl = Piwik_Url::getCurrentUrl(); $tokenAuth = Piwik_UsersManager_API::getTokenAuth($login,$password); @@ -36,24 +33,40 @@ class Piwik_Login_Controller extends Piwik_Controller if($auth->authenticate()->isValid()) { - print("Authenticated, redirecting to $urlToRedirect"); - +// Piwik::log("Authenticated ::: "); if($currentUrl === $urlToRedirect) { - print("We redirect to the homepage! $baseUrl"); +// Piwik::log("We redirect to the homepage! "); +// $urlToRedirect = $baseUrl; } + +// Piwik::log("setup cookie"); + $authCookieName = 'piwik-auth'; + $authCookieExpiry = time() + 3600; + $cookie = new Piwik_Cookie($authCookieName, $authCookieExpiry); + $cookie->set('login', $login); + $tokenAuth = $auth->getTokenAuth(); + $cookie->set('token', $tokenAuth); + $cookie->save(); + +// Piwik_Login::prepareAuthObject($login,$tokenAuth); + +// Piwik::log("redirecting to $urlToRedirect ....."); + + Piwik_Url::redirectToUrl($urlToRedirect); } else { - print("Error authenticated"); - + $messageNoAccess = 'login & password not correct'; } } - $view = new Piwik_View('home.tpl'); + $view = new Piwik_View('login.tpl'); + $view->AccessErrorString = $messageNoAccess; $view->addForm( $form ); - $view->render(); + $view->subTemplate = 'genericForm.tpl'; + echo $view->render(); } } ?> diff --git a/plugins/Login/Form.php b/plugins/Login/Form.php index 998a55897a..194b172c69 100644 --- a/plugins/Login/Form.php +++ b/plugins/Login/Form.php @@ -7,5 +7,33 @@ class Piwik_Login_Form extends Piwik_Form parent::__construct(); } + function init() + { + $urlToGoAfter = Piwik_Url::getReferer(); + if($urlToGoAfter == false) + { + + $urlToGoAfter = Piwik_Url::getCurrentUrl(); + } + + $formElements = array( + array('text', 'form_login', 'login:'), + array('password', 'form_password', 'pass:'), + array('hidden', 'form_url', $urlToGoAfter), + ); + $this->addElements( $formElements ); + + $formRules = array( + array('form_login', sprintf('%s required', 'login'), 'required'), + array('form_password', sprintf('%s required', 'password'), 'required'), + ); + $this->addRules( $formRules ); + + $this->addElement('submit', 'submit', 'Go!'); + $this->addElement('submit', 'back', 'Cancel'); + + } + + } ?> diff --git a/plugins/Logout/Controller.php b/plugins/Logout/Controller.php new file mode 100644 index 0000000000..b8dc78e8fe --- /dev/null +++ b/plugins/Logout/Controller.php @@ -0,0 +1,15 @@ +<?php +class Piwik_Logout_Controller extends Piwik_Controller +{ + function index() + { + $authCookieName = 'piwik-auth'; + $cookie = new Piwik_Cookie($authCookieName); + $cookie->delete(); + + $baseUrl = Piwik_Url::getCurrentUrlWithoutQueryString(); + + Piwik_Url::redirectToUrl($baseUrl); + } +} +?> diff --git a/plugins/SitesManager.php b/plugins/SitesManager.php new file mode 100644 index 0000000000..da777cbc5e --- /dev/null +++ b/plugins/SitesManager.php @@ -0,0 +1,26 @@ +<?php + +class Piwik_SitesManager extends Piwik_Plugin +{ + public function __construct() + { + parent::__construct(); + } + + public function getInformation() + { + $info = array( + // name must be the className prefix! + 'name' => 'SitesManager', + 'description' => 'Description', + 'author' => 'Piwik', + 'homepage' => 'http://piwik.org/', + 'version' => '0.1', + 'translationAvailable' => false, + ); + + return $info; + } + +} + diff --git a/modules/SitesManager.php b/plugins/SitesManager/API.php index a481729c47..96d70c0ff1 100755 --- a/modules/SitesManager.php +++ b/plugins/SitesManager/API.php @@ -3,8 +3,6 @@ * * @package Piwik */ -require_once "API/APIable.php"; - class Piwik_SitesManager_API extends Piwik_Apiable { static private $instance = null; @@ -169,10 +167,10 @@ class Piwik_SitesManager_API extends Piwik_Apiable */ static private function getSitesFromIds( $idSites ) { - assert(is_array($idSites)); +// assert(is_array($idSites)); foreach($idSites as $idsite) { - assert(is_int($idsite)); +// assert(is_int($idsite)); } if(count($idSites) === 0) { @@ -181,7 +179,8 @@ class Piwik_SitesManager_API extends Piwik_Apiable $db = Zend_Registry::get('db'); $sites = $db->fetchAll("SELECT * FROM ".Piwik::prefixTable("site")." - WHERE idsite IN (".implode(", ", $idSites).")"); + WHERE idsite IN (".implode(", ", $idSites).") + ORDER BY idsite ASC"); return $sites; } @@ -220,9 +219,27 @@ class Piwik_SitesManager_API extends Piwik_Apiable self::insertSiteUrls($idSite, $aUrls); + // we reload the access list which doesn't yet take in consideration this new website + Zend_Registry::get('access')->loadAccess(); + return (int)$idSite; } + //TODO comment + static public function deleteSite( $idSite ) + { + Piwik::checkUserIsSuperUser(); + + $db = Zend_Registry::get('db'); + + $db->query("DELETE FROM ".Piwik::prefixTable("site")." + WHERE idsite = ?", $idSite); + + $db->query("DELETE FROM ".Piwik::prefixTable("site_url")." + WHERE idsite = ?", $idSite); + } + + /** * Checks that the array has at least one element * diff --git a/plugins/SitesManager/Controller.php b/plugins/SitesManager/Controller.php new file mode 100644 index 0000000000..b1d224ff45 --- /dev/null +++ b/plugins/SitesManager/Controller.php @@ -0,0 +1,18 @@ +<?php +class Piwik_SitesManager_Controller extends Piwik_Controller +{ + function index() + { + $view = new Piwik_View('SitesManager/templates/SitesManager.tpl'); + + $sites = Piwik_SitesManager_API::getSitesWithAdminAccess(); + foreach($sites as &$site) + { + $site['alias_urls'] = Piwik_SitesManager_API::getSiteUrlsFromId($site['idsite']); + } +// var_dump($sites);exit; + $view->sites = $sites; + echo $view->render(); + } +} +?> diff --git a/plugins/SitesManager/templates/SitesManager.js b/plugins/SitesManager/templates/SitesManager.js new file mode 100644 index 0000000000..5441680e88 --- /dev/null +++ b/plugins/SitesManager/templates/SitesManager.js @@ -0,0 +1,101 @@ + +$('#addRowSite').click( function() { + ajaxHideError(); + $(this).toggle(); + + var numberOfRows = $('table#editSites')[0].rows.length; + var newRowIdNumeric = numberOfRows + 1; + var newRowId = 'row' + newRowIdNumeric; + + $(' <tr id="'+newRowId+'">\ + <td>'+newRowIdNumeric+'</td>\ + <td><input id="siteadd_name" value="Name" size=10></td>\ + <td><textarea cols=30 rows=3 id="siteadd_urls">http://siteUrl.com/\nhttp://siteUrl2.com/</textarea></td>\ + <td><img src="plugins/UsersManager/images/ok.png" id="addsite" href="#"></td>\ + <td><img src="plugins/UsersManager/images/remove.png" id="cancel"></td>\ + </tr>') + .appendTo('#editSites') + ; + $('#'+newRowId).keypress( submitSiteOnEnter ); + $('#addsite').click( function(){ $.ajax( getAddSiteAJAX($('tr#'+newRowId)) ); } ); + $('#cancel').click(function() { ajaxHideError(); $(this).parents('tr').remove(); $('#addRowSite').toggle(); }); + + } ); + +// when click on deleteuser, the we ask for confirmation and then delete the user +$('.deleteSite').click( function() { + ajaxHideError(); + var idRow = $(this).attr('id'); + var nameToDelete = $(this).parent().parent().find('#name').html(); + var idsiteToDelete = $(this).parent().parent().find('#idSite').html(); + if(confirm('Are you sure you want to delete the website "'+nameToDelete+'" (idSite = '+idsiteToDelete+')?')) + { + $.ajax( getDeleteSiteAJAX( idsiteToDelete ) ); + } + } +); + +var alreadyEdited = new Array; +// when click on edituser, the cells become editable +$('.editSite') + .click( function() { + ajaxHideError(); + var idRow = $(this).attr('id'); + if(alreadyEdited[idRow]==1) return; + alreadyEdited[idRow] = 1; + $('tr#'+idRow+' .editableSite').each( + // make the fields editable + // change the EDIT button to VALID button + function (i,n) { + var contentBefore = $(n).html(); + var idName = $(n).attr('id'); + if(idName == 'name') + { + var contentAfter = '<input id="'+idName+'" value="'+contentBefore+'" size="10">'; + $(n) + .html(contentAfter) + .keypress( submitSiteOnEnter ); + } + if(idName == 'urls') + { + var contentAfter = '<textarea cols=30 rows=3 id="aUrls">'+contentBefore.replace(/<br>/gi,"\n")+'</textarea>'; + $(n).html(contentAfter); + } + } + ); + + $(this) + .toggle() + .parent() + .prepend( $('<img src="plugins/UsersManager/images/ok.png" id="updateSite">') + .click( function(){ $.ajax( getUpdateSiteAJAX( $('tr#'+idRow) ) ); } ) + ); + + + + } +); + + +$('td.editableSite') + .hover( function() { + $(this).css({ cursor: "pointer"}); + }, + function() { + $(this).css({ cursor: "auto"}); + } + ) + .click( function(){ $(this).parent().find('.editSite').click(); } ) + ; + + +function submitSiteOnEnter(e) +{ + var key=e.keyCode || e.which; + if (key==13) + { + alert('ok'); + $(this).parent().find('#updateSite').click(); + $(this).find('#addsite').click(); + } +} diff --git a/plugins/SitesManager/templates/SitesManager.tpl b/plugins/SitesManager/templates/SitesManager.tpl new file mode 100644 index 0000000000..8e605078f5 --- /dev/null +++ b/plugins/SitesManager/templates/SitesManager.tpl @@ -0,0 +1,48 @@ +{literal} +<style> +* { +font-family:Trebuchet MS,arial,sans-serif; +} + +textarea{ + font-family: Trebuchet MS, Verdana; + font-size:0.85em; + +} + +#editSites{ + valign:top; +} +</style> +{/literal} +<script type="text/javascript" src="libs/jquery/jquery.js"></script> + +<h2>Sites</h2> +<div id="ajaxError" style="display:none"></div> +<div id="ajaxLoading" style="display:none">Loading... <img src="themes/default/loading.gif"></div> + +<table id="editSites" border=1 cellpadding="10"> + <tbody> + <tr> + <td>Id</td> + <td>Name</td> + <td>URLs</td> + </tr> + + {foreach from=$sites key=i item=site} + <tr id="row{$i}"> + <td id="idSite">{$site.idsite}</td> + <td id="name" class="editableSite">{$site.name}</td> + <td id="urls" class="editableSite">{foreach from=$site.alias_urls item=url}{$url}<br>{/foreach}</td> + <td><img src='plugins/UsersManager/images/edit.png' class="editSite" id="row{$i}" href='#'></td> + <td><img src='plugins/UsersManager/images/remove.png' class="deleteSite" id="row{$i}" value="Delete"></td> + + </tr> + {/foreach} + + </tbody> +</table> +<div id="addRowSite"><img src='plugins/UsersManager/images/add.png'> <a href="#">Add a new Site</a></div> + +<script type="text/javascript" src="plugins/UsersManager/templates/UsersManager.js"></script> +<script type="text/javascript" src="plugins/SitesManager/templates/SitesManager.js"></script> diff --git a/plugins/UsersManager.php b/plugins/UsersManager.php new file mode 100644 index 0000000000..dd56cdf89d --- /dev/null +++ b/plugins/UsersManager.php @@ -0,0 +1,26 @@ +<?php + +class Piwik_UsersManager extends Piwik_Plugin +{ + public function __construct() + { + parent::__construct(); + } + + public function getInformation() + { + $info = array( + // name must be the className prefix! + 'name' => 'UserManager', + 'description' => 'Description', + 'author' => 'Piwik', + 'homepage' => 'http://piwik.org/', + 'version' => '0.1', + 'translationAvailable' => false, + ); + + return $info; + } + +} + diff --git a/modules/UsersManager.php b/plugins/UsersManager/API.php index 78317bc3db..c23c7e2caf 100755 --- a/modules/UsersManager.php +++ b/plugins/UsersManager/API.php @@ -26,16 +26,30 @@ class Piwik_UsersManager_API extends Piwik_Apiable static public $methodsNotToPublish = array(); /** - * Returns the list of all the users login. + * Returns the list of all the users * - * @return array the list of all the login + * @return array the list of all the users */ static public function getUsers() { Piwik::checkUserIsSuperUser(); $db = Zend_Registry::get('db'); - $users = $db->fetchAll("SELECT login FROM ".Piwik::prefixTable("user")); + $users = $db->fetchAll("SELECT * FROM ".Piwik::prefixTable("user")." ORDER BY login ASC"); + return $users; + } + /** + * Returns the list of all the users login + * + * @return array the list of all the users login + */ + //TODO test this method + static public function getUsersLogin() + { + Piwik::checkUserHasSomeAdminAccess(); + + $db = Zend_Registry::get('db'); + $users = $db->fetchAll("SELECT login FROM ".Piwik::prefixTable("user")." ORDER BY login ASC"); $return = array(); foreach($users as $login) { @@ -176,7 +190,7 @@ class Piwik_UsersManager_API extends Piwik_Apiable if(!self::isValidLoginString($userLogin)) { - throw new Exception("The login must contain only letters, numbers, or the characters '_' or '-' or '.'."); + throw new Exception("The login must contain only letters, numbers, or the characters '_' or '-' or '.'"); } } @@ -184,7 +198,7 @@ class Piwik_UsersManager_API extends Piwik_Apiable { if(!self::isValidPasswordString($password)) { - throw new Exception("The password must contain at least 6 characters including at least one number."); + throw new Exception("The password length must be between 6 and 26 characters."); } } @@ -198,8 +212,7 @@ class Piwik_UsersManager_API extends Piwik_Apiable static private function getCleanAlias($alias,$userLogin) { - if(is_null($alias) - || empty($alias)) + if(empty($alias)) { $alias = $userLogin; } @@ -225,14 +238,14 @@ class Piwik_UsersManager_API extends Piwik_Apiable * * @exception in case of an invalid parameter */ - static public function addUser( $userLogin, $password, $email, $alias = null ) + static public function addUser( $userLogin, $password, $email, $alias = false ) { Piwik::checkUserIsSuperUser(); self::checkLogin($userLogin); self::checkPassword($password); self::checkEmail($email); - + $alias = self::getCleanAlias($alias,$userLogin); $token_auth = self::getTokenAuth($userLogin,$password); $passwordTransformed = self::getCleanPassword($password); @@ -248,6 +261,9 @@ class Piwik_UsersManager_API extends Piwik_Apiable ) ); + // we reload the access list which doesn't yet take in consideration this new user + Zend_Registry::get('access')->loadAccess(); + } /** @@ -258,33 +274,42 @@ class Piwik_UsersManager_API extends Piwik_Apiable * * @see addUser() for all the parameters */ - static public function updateUser( $userLogin, $password, $email = null, $alias = null ) + static public function updateUser( $userLogin, $password = false, $email = false, $alias = false ) { Piwik::checkUserIsSuperUserOrTheUser($userLogin); $userInfo = self::getUser($userLogin); - if(is_null($alias)) + if(empty($password)) + { + $password = $userInfo['password']; + } + else + { + self::checkPassword($password); + $password = self::getCleanPassword($password); + } + + if(empty($alias)) { $alias = $userInfo['alias']; } - if(is_null($email)) + + if(empty($email)) { $email = $userInfo['email']; } - self::checkPassword($password); self::checkEmail($email); $alias = self::getCleanAlias($alias,$userLogin); $token_auth = self::getTokenAuth($userLogin,$password); - $passwordTransformed = self::getCleanPassword($password); $db = Zend_Registry::get('db'); $db->update( Piwik::prefixTable("user"), array( - 'password' => $passwordTransformed, + 'password' => $password, 'alias' => $alias, 'email' => $email, 'token_auth' => $token_auth, @@ -344,14 +369,14 @@ class Piwik_UsersManager_API extends Piwik_Apiable * * @return bool true on success */ - static public function setUserAccess( $userLogin, $access, $idSites = null) + static public function setUserAccess( $userLogin, $access, $idSites = false) { self::checkAccessType( $access ); self::checkUserExists( $userLogin); // in case idSites is null we grant access to all the websites on which the current connected user // has an 'admin' access - if(is_null($idSites)) + if(empty($idSites)) { $idSites = Piwik_SitesManager_API::getSitesIdWithAdminAccess(); } @@ -383,6 +408,9 @@ class Piwik_UsersManager_API extends Piwik_Apiable ); } } + + // we reload the access list which doesn't yet take in consideration this new user access + Zend_Registry::get('access')->loadAccess(); } /** diff --git a/plugins/UsersManager/Controller.php b/plugins/UsersManager/Controller.php new file mode 100644 index 0000000000..0f6133545f --- /dev/null +++ b/plugins/UsersManager/Controller.php @@ -0,0 +1,53 @@ +<?php +class Piwik_UsersManager_Controller extends Piwik_Controller +{ + function index() + { + $view = new Piwik_View('UsersManager/templates/UsersManager.tpl'); + + $IdSitesAdmin = Piwik_SitesManager_API::getSitesIdWithAdminAccess(); + + $idSiteSelected = 1; + + if(count($IdSitesAdmin) > 0) + { + $defaultWebsiteId = $IdSitesAdmin[0]; + $idSiteSelected = Piwik_Common::getRequestVar('idsite', $defaultWebsiteId, 'integer'); + } + + if($idSiteSelected==-1) + { + $usersAccessByWebsite = array(); + } + else + { + $usersAccessByWebsite = Piwik_UsersManager_API::getUsersAccessFromSite( $idSiteSelected ); + } + + // requires super user access + $usersLogin = Piwik_UsersManager_API::getUsersLogin(); + + // we dont want to display the user currently logged so that the user can't change his settings from admin to view... + $currentlyLogged = Zend_Registry::get('access')->getIdentity(); + + foreach($usersLogin as $login) + { + if( $login != $currentlyLogged + && !isset($usersAccessByWebsite[$login])) + { + $usersAccessByWebsite[$login] = 'noaccess'; + } + } + $users = array(); + if(Zend_Registry::get('access')->isSuperUser()) + $users = Piwik_UsersManager_API::getUsers(); + + $view->idSiteSelected = $idSiteSelected; + $view->users = $users; + $view->usersAccessByWebsite = $usersAccessByWebsite; + $view->formUrl = Piwik_Url::getCurrentUrl(); + $view->websites = Piwik_SitesManager_API::getSitesWithAdminAccess(); + echo $view->render(); + } +} +?> diff --git a/plugins/UsersManager/images/add.png b/plugins/UsersManager/images/add.png Binary files differnew file mode 100644 index 0000000000..1aa7f095c6 --- /dev/null +++ b/plugins/UsersManager/images/add.png diff --git a/plugins/UsersManager/images/edit.png b/plugins/UsersManager/images/edit.png Binary files differnew file mode 100644 index 0000000000..188e1c12bd --- /dev/null +++ b/plugins/UsersManager/images/edit.png diff --git a/plugins/UsersManager/images/no-access.png b/plugins/UsersManager/images/no-access.png Binary files differnew file mode 100644 index 0000000000..2f66cdebbb --- /dev/null +++ b/plugins/UsersManager/images/no-access.png diff --git a/plugins/UsersManager/images/no.png b/plugins/UsersManager/images/no.png Binary files differnew file mode 100644 index 0000000000..ab6808fba5 --- /dev/null +++ b/plugins/UsersManager/images/no.png diff --git a/plugins/UsersManager/images/ok.png b/plugins/UsersManager/images/ok.png Binary files differnew file mode 100644 index 0000000000..0582c35707 --- /dev/null +++ b/plugins/UsersManager/images/ok.png diff --git a/plugins/UsersManager/images/remove.png b/plugins/UsersManager/images/remove.png Binary files differnew file mode 100644 index 0000000000..ab6808fba5 --- /dev/null +++ b/plugins/UsersManager/images/remove.png diff --git a/plugins/UsersManager/images/valid.png b/plugins/UsersManager/images/valid.png Binary files differnew file mode 100644 index 0000000000..6ef8de76e0 --- /dev/null +++ b/plugins/UsersManager/images/valid.png diff --git a/plugins/UsersManager/templates/UsersManager.js b/plugins/UsersManager/templates/UsersManager.js new file mode 100644 index 0000000000..98304a9c30 --- /dev/null +++ b/plugins/UsersManager/templates/UsersManager.js @@ -0,0 +1,326 @@ +function ajaxHandleError() +{ + alert('Transfer error, please reload the page or try again later.'); +} + +function ajaxShowError( string ) +{ + $('#ajaxError').html(string).show(); +} +function ajaxHideError() +{ + $('#ajaxError').hide(); +} + +function ajaxToggleLoading() +{ + $('#ajaxLoading').toggle(); +} +function ajaxHandleResponse(response) +{ + if(response.result == "error") + { + ajaxShowError(response.message); + } + else + { + window.location.reload(); + } + ajaxToggleLoading(); +} + +function getStandardAjaxConf() +{ + var ajaxRequest = new Object; + + //prepare the ajax request + ajaxRequest.type = 'GET'; + ajaxRequest.url = 'index.php'; + ajaxRequest.dataType = 'json'; + ajaxRequest.error = ajaxHandleError; + ajaxRequest.success = ajaxHandleResponse; + + return ajaxRequest; +} +function toggleAjaxLoading() +{ + $('#ajaxLoading').toggle(); +} +function getUpdateUserAJAX( row ) +{ + var ajaxRequest = getStandardAjaxConf(); + toggleAjaxLoading(); + + // prepare the API parameters to update the user + var parameters = new Object; + parameters.module = 'API'; + parameters.format = 'json'; + parameters.method = 'UsersManager.updateUser'; + parameters.userLogin = $(row).children('#userLogin').html(); + var password = $(row).find('input[@id=password]').val(); + if(password != '-') parameters.password = password; + parameters.email = $(row).find('input[@id=email]').val(); + parameters.alias = $(row).find('input[@id=alias]').val(); + + ajaxRequest.data = parameters; + + return ajaxRequest; + +} + +function getDeleteUserAJAX( login ) +{ + var ajaxRequest = getStandardAjaxConf(); + toggleAjaxLoading(); + + // prepare the API parameters to update the user + var parameters = new Object; + parameters.module = 'API'; + parameters.format = 'json'; + parameters.method = 'UsersManager.deleteUser'; + parameters.userLogin = login; + + ajaxRequest.data = parameters; + + return ajaxRequest; +} + +function getAddUserAJAX( row ) +{ + var ajaxRequest = getStandardAjaxConf(); + toggleAjaxLoading(); + + // prepare the API parameters to add the user + var parameters = new Object; + parameters.module = 'API'; + parameters.format = 'json'; + parameters.method = 'UsersManager.addUser'; + parameters.userLogin = $(row).find('input[@id=useradd_login]').val(); + parameters.password = $(row).find('input[@id=useradd_password]').val(); + parameters.email = $(row).find('input[@id=useradd_email]').val(); + parameters.alias = $(row).find('input[@id=useradd_alias]').val(); + + ajaxRequest.data = parameters; + + return ajaxRequest; +} + +function getUpdateUserAccess(login, access) +{ + var ajaxRequest = getStandardAjaxConf(); + + ajaxRequest.success = function (response){ + if(response.result == "error") + { + ajaxShowError(response.message); + } + } + ajaxRequest.async = false; + + // prepare the API parameters to add the user + var parameters = new Object; + parameters.module = 'API'; + parameters.format = 'json'; + parameters.method = 'UsersManager.setUserAccess'; + parameters.userLogin = login; + parameters.access = access; + + var idSites = $('#selectIdsite option:selected').val(); + if(idSites != -1) + { + parameters.idSites = idSites; + } + + ajaxRequest.data = parameters; + + return ajaxRequest; +} + +function submitOnEnter(e) +{ + var key=e.keyCode || e.which; + if (key==13) + { + $(this).find('#adduser').click(); + $(this).find('#updateuser').click(); + } +} + +function bindUpdateAccess() +{ + $('#accessUpdated').hide(); + + //launching AJAX request + $.ajax( getUpdateUserAccess( + $(this).parent().parent().find('#login').html(), + $(this).parent().attr('id') + ) + ); + + //once successful + $(this).parent().parent().find('.accessGranted') + .attr("src","plugins/UsersManager/images/no-access.png" ) + .attr("class","updateAccess" ) + .click(bindUpdateAccess) + ; + $(this) + .attr('src',"plugins/UsersManager/images/ok.png" ) + .attr('class',"accessGranted" ) + ; + $('#accessUpdated').show(); + $('#accessUpdated').fadeOut(1500); +} + + +function getDeleteSiteAJAX( idsite ) +{ + var ajaxRequest = getStandardAjaxConf(); + toggleAjaxLoading(); + + // prepare the API parameters to update the user + var parameters = new Object; + parameters.module = 'API'; + parameters.format = 'json'; + parameters.method = 'SitesManager.deleteSite'; + parameters.idSite = idsite; + + ajaxRequest.data = parameters; + + return ajaxRequest; +} +String.prototype.trim = function() { + return this.replace(/^\s+|\s+$/g,""); +} + +function getAddSiteAJAX( row ) +{ + var ajaxRequest = getStandardAjaxConf(); + toggleAjaxLoading(); + + // prepare the API parameters to add the user + var parameters = new Object; + + var name = $(row).find('input[@id=siteadd_name]').val(); + var urls = $(row).find('textarea[@id=siteadd_urls]').val(); + var aUrls = urls.trim().split("\n"); + + var request = ''; + request += '&module=API'; + request += '&format=json'; + request += '&method=SitesManager.addSite'; + request += '&name='+escape(name); + + $.each(aUrls, function (key,value){ request+= '&aUrls[]='+escape(value);} ); + + ajaxRequest.data = request; + + return ajaxRequest; +} + +function getUpdateSiteAJAX( row ) +{ + var ajaxRequest = getStandardAjaxConf(); + toggleAjaxLoading(); + + var name = $(row).find('input[@id=name]').val(); + var idSite = $(row).children('#idSite').html(); + var aUrls = $(row).find('textarea[@id=aUrls]').val().trim().split("\n"); + + var request = ''; + request += '&module=API'; + request += '&format=json'; + request += '&method=SitesManager.updateSite'; + request += '&name='+escape(name); + request += '&idSite='+idSite; + $.each(aUrls, function (key,value){ request+= '&aUrls[]='+value;} ); + + ajaxRequest.data = request; + + return ajaxRequest; + +} + +var alreadyEdited = new Array; +// when click on edituser, the cells become editable +$('.edituser') + .click( function() { + ajaxHideError(); + var idRow = $(this).attr('id'); + if(alreadyEdited[idRow]==1) return; + alreadyEdited[idRow] = 1; + $('tr#'+idRow+' .editable').each( + // make the fields editable + // change the EDIT button to VALID button + function (i,n) { + var contentBefore = $(n).html(); + var idName = $(n).attr('id'); + if(idName != 'userLogin') + { + var contentAfter = '<input id="'+idName+'" value="'+contentBefore+'" size="10">'; + $(n).html(contentAfter); + } + } + ); + + $(this) + .toggle() + .parent() + .prepend( $('<img src="plugins/UsersManager/images/ok.png" id="updateuser">') + .click( function(){ $.ajax( getUpdateUserAJAX( $('tr#'+idRow) ) ); } ) + ); + + + + } +); +$('.editable').keypress( submitOnEnter ); + +$('td.editable') + .hover( function() { + $(this).css({ cursor: "pointer"}); + }, + function() { + $(this).css({ cursor: "auto"}); + } + ) + .click( function(){ $(this).parent().find('.edituser').click(); } ) + ; + +// when click on deleteuser, the we ask for confirmation and then delete the user +$('.deleteuser').click( function() { + ajaxHideError(); + var idRow = $(this).attr('id'); + var loginToDelete = $(this).parent().parent().find('#userLogin').html(); + if(confirm('Are you sure you want to delete the user "'+loginToDelete+'"?')) + { + $.ajax( getDeleteUserAJAX( loginToDelete ) ); + } + } +); + +$('#addrow').click( function() { + ajaxHideError(); + $(this).toggle(); + + var numberOfRows = $('table#users')[0].rows.length; + var newRowId = numberOfRows + 1; + var newRowId = 'row' + newRowId; + + $(' <tr id="'+newRowId+'">\ + <td><input id="useradd_login" value="login?" size=10></td>\ + <td><input id="useradd_password" value="password" size=10></td>\ + <td><input id="useradd_email" value="email@domain.com" size=15></td>\ + <td><input id="useradd_alias" value="alias" size=15></td>\ + <td><img src="plugins/UsersManager/images/ok.png" id="adduser" href="#"></td>\ + <td><img src="plugins/UsersManager/images/remove.png" id="cancel"></td>\ + </tr>') + .appendTo('#users') + ; + $('#'+newRowId).keypress( submitOnEnter ); + $('#adduser').click( function(){ $.ajax( getAddUserAJAX($('tr#'+newRowId)) ); } ); + $('#cancel').click(function() { ajaxHideError(); $(this).parents('tr').remove(); $('#addrow').toggle(); }); + + } ); +$('.updateAccess').click( bindUpdateAccess ); + + diff --git a/plugins/UsersManager/templates/UsersManager.tpl b/plugins/UsersManager/templates/UsersManager.tpl new file mode 100644 index 0000000000..f8b43eb71b --- /dev/null +++ b/plugins/UsersManager/templates/UsersManager.tpl @@ -0,0 +1,113 @@ +<script type="text/javascript" src="libs/jquery/jquery.js"></script> + +{literal} + +<style> +#access td, #users td +{ + spacing: 0px; + padding: 2px 5px 5px 4px; + border: 1px solid #660000; + width:100px; +} + + +#ajaxError{ + color:red; + text-align:center; + font-weight:bold; + width:550px; + border: 3px solid red; + margin: 10px; + padding: 10px; +} + +#addrow img { + vertical-align:middle; +} +#addrow a { + text-decoration:none; +} + +#accessUpdated{ + display:none; + border:2px solid green; + color:green; + width:100px; + text-align:center; +} + +</style> +{/literal} + +<h2>Access</h2> + +<div id="sites"> +<form method="get" action="{$formUrl}" id="accessSites"> + <input type="hidden" name="module" value="UsersManager"> + <p>Sites: <select id="selectIdsite" name="idsite" onchange="this.form.submit()"> + + <optgroup label="All websites"> + <option label="All websites" value="-1" {if $idSiteSelected==-1} selected="selected"{/if}>Apply to all websites</option> + </optgroup> + <optgroup label="Sites"> + {foreach from=$websites item=info} + <option value="{$info.idsite}" {if $idSiteSelected==$info.idsite} selected="selected"{/if}>{$info.name}</option> + {/foreach} + </optgroup> + + </select></p> +</form> +</div> + +<table id="access"> +<tr> + <td>User</td> + <td>No access</td> + <td>View</td> + <td>Admin</td> +</tr> +{foreach from=$usersAccessByWebsite key=login item=access} +{assign var=accesValid value="<img src='plugins/UsersManager/images/ok.png' class='accessGranted'>"} +{assign var=accesInvalid value="<img src='plugins/UsersManager/images/no-access.png' class='updateAccess'>"} +<tr> + <td id='login'>{$login}</td> + <td id='noaccess'>{if $access=='noaccess' and $idSiteSelected!=-1}{$accesValid}{else}{$accesInvalid}{/if} </td> + <td id='view'>{if $access=='view' and $idSiteSelected!=-1}{$accesValid}{else}{$accesInvalid}{/if} </td> + <td id='admin'>{if $access=='admin' and $idSiteSelected!=-1}{$accesValid}{else}{$accesInvalid}{/if} </td> +</tr> +{/foreach} +</table> + +<div id="accessUpdated">Done!</div> + + +<h2>Users</h2> + +<div id="ajaxError" style="display:none"></div> +<div id="ajaxLoading" style="display:none">Loading... <img src="themes/default/loading.gif"></div> +<table id="users"> + <tbody> + <tr> + <td>Login</td> + <td>Password</td> + <td>Email</td> + <td>Alias</td> + <td>Edit</td> + <td>Delete</td> + </tr> + {foreach from=$users item=user key=i} + <tr class="editable" id="row{$i}"> + <td id="userLogin" class="editable">{$user.login}</td> + <td id="password" class="editable">-</td> + <td id="email" class="editable">{$user.email}</td> + <td id="alias" class="editable">{$user.alias}</td> + <td><img src='plugins/UsersManager/images/edit.png' class="edituser" id="row{$i}" href='#'></td> + <td><img src='plugins/UsersManager/images/remove.png' class="deleteuser" id="row{$i}" value="Delete"></td> + </tr> + {/foreach} + </tbody> + +</table> +<div id="addrow"><img src='plugins/UsersManager/images/add.png'> <a href="#">Add a new user</a></div> +<script type="text/javascript" src="plugins/UsersManager/templates/UsersManager.js"></script> diff --git a/tests/config_test.php b/tests/config_test.php index afb60c06e1..7b8ebe40f4 100755 --- a/tests/config_test.php +++ b/tests/config_test.php @@ -25,13 +25,13 @@ set_include_path(PATH_TEST_TO_ROOT .'/' . PATH_SEPARATOR . getcwd() . '/../modules/' . PATH_SEPARATOR . getcwd() . '/../../tests/' . PATH_SEPARATOR . getcwd() . '/../tests/' - . PATH_SEPARATOR . PATH_TEST_TO_ROOT . '/core/' + . PATH_SEPARATOR . PATH_TEST_TO_ROOT . '/plugins/' . PATH_SEPARATOR . PATH_TEST_TO_ROOT . '/config/' . PATH_SEPARATOR . PATH_TEST_TO_ROOT . '/modules/' . PATH_SEPARATOR . PATH_TEST_TO_ROOT . '/tests/' . PATH_SEPARATOR . PATH_TEST_TO_ROOT2 . '/libs/' . PATH_SEPARATOR . PATH_TEST_TO_ROOT2 . '/config/' - . PATH_SEPARATOR . PATH_TEST_TO_ROOT2 . '/core/' + . PATH_SEPARATOR . PATH_TEST_TO_ROOT2 . '/plugins/' . PATH_SEPARATOR . PATH_TEST_TO_ROOT2 . '/modules/' . PATH_SEPARATOR . PATH_TEST_TO_ROOT2 . '/tests/' . PATH_SEPARATOR . get_include_path() diff --git a/tests/modules/DataTable.test.php b/tests/modules/DataTable.test.php index 086205804d..2214af4a83 100644 --- a/tests/modules/DataTable.test.php +++ b/tests/modules/DataTable.test.php @@ -340,7 +340,7 @@ class Test_Piwik_DataTable extends UnitTestCase $this->assertEqual($table->getRows(), $expectedtable->getRows()); } /** - * Test to filter a column with a offset, limit + * Test to filter a table with a offset, limit */ function test_filter_OffsetLimit() { diff --git a/tests/modules/Database.test.php b/tests/modules/Database.test.php index d161f1bb7f..c5f3c9b4c5 100755 --- a/tests/modules/Database.test.php +++ b/tests/modules/Database.test.php @@ -35,6 +35,8 @@ class FakeAccess throw new Exception("checkUserIsSuperUser Fake exception // string not to be tested"); } } + static public function loadAccess() + {} static public function checkUserHasAdminAccess( $idSites ) { if(!self::$superUser) diff --git a/tests/modules/SitesManager.test.php b/tests/modules/SitesManager.test.php index 10500806df..8da16cc92f 100755 --- a/tests/modules/SitesManager.test.php +++ b/tests/modules/SitesManager.test.php @@ -8,7 +8,7 @@ if(!defined('CONFIG_TEST_INCLUDED')) } require_once "Database.test.php"; -require_once 'SitesManager.php'; +require_once 'SitesManager/API.php'; class Test_Piwik_SitesManager extends Test_Database { diff --git a/tests/modules/UsersManager.test.php b/tests/modules/UsersManager.test.php index 09871614de..f3c489a097 100644 --- a/tests/modules/UsersManager.test.php +++ b/tests/modules/UsersManager.test.php @@ -9,7 +9,7 @@ if(!defined('CONFIG_TEST_INCLUDED')) require_once "Database.test.php"; -require 'UsersManager.php'; +require 'UsersManager/API.php'; class Test_Piwik_UsersManager extends Test_Database { @@ -432,10 +432,19 @@ class Test_Piwik_UsersManager extends Test_Database Piwik_UsersManager_API::addUser("geggeqge632ge56a4qag", "geqgegeagae", "tesggt@tesgt.com", "alias"); Piwik_UsersManager_API::addUser("geggeqgeqagqegg", "geqgeaggggae", "tesgggt@tesgt.com"); - - $this->assertEqual(Piwik_UsersManager_API::getUsers(), array("gegg4564eqgeqag", - "geggeqge632ge56a4qag", - "geggeqgeqagqegg")); + $users = Piwik_UsersManager_API::getUsers(); + foreach($users as &$user) + { + unset($user['token_auth']); + unset($user['date_registered']); + } + $this->assertEqual($users, + array( + array('login' => "gegg4564eqgeqag", 'password' => md5("geqgegagae"), 'alias' => "alias", 'email' => "tegst@tesgt.com"), + array('login' => "geggeqge632ge56a4qag", 'password' => md5("geqgegeagae"),'alias' => "alias", 'email' => "tesggt@tesgt.com"), + array('login' => "geggeqgeqagqegg", 'password' => md5("geqgeaggggae"), 'alias' => 'geggeqgeqagqegg','email' => "tesgggt@tesgt.com"), + ) + ); } diff --git a/themes/default/genericForm.tpl b/themes/default/genericForm.tpl new file mode 100755 index 0000000000..ea55ca82cc --- /dev/null +++ b/themes/default/genericForm.tpl @@ -0,0 +1,53 @@ + +{if $form_data.errors} + <div id="adminErrors"> + <strong>Errors</strong> + <ul> + {foreach from=$form_data.errors item=data} + <li>{$data}</li> + {/foreach} + </ul> + </div> +{/if} + +{* +{if isset($form_text)} +<p>{$form_text}</p> +{/if} +*} + +<form {$form_data.attributes}> +<!-- Output hidden fields --> + +<!-- Display the fields --> +{foreach from=$element_list key=title item=data} + <h3>{$title}</h3> + <div class="centrer"> + <table class="centrer"> + {foreach from=$data item=fieldname} + {* normal form *} + {if $form_data.$fieldname.label} + <tr> + <td>{$form_data.$fieldname.label}</td> + <td>{$form_data.$fieldname.html}</td> + </tr> + {elseif $form_data.$fieldname.type == 'hidden'} + <tr><td colspan=2>{$form_data.$fieldname.html}</td></tr> + {* radio form + {else} + {foreach from=$form_data.$fieldname key=key item=radio} + <tr> + <td>{$radio.label}</td> + <td>{$radio.html}</td> + </tr> + {/foreach}*} + {/if} + {/foreach} + </table> + </div> +{/foreach} +<div class="boutonsAction"> + +{$form_data.submit.html} +</div> +</form>
\ No newline at end of file diff --git a/themes/default/home.tpl b/themes/default/home.tpl index 92b6fd0093..76c15173f3 100644 --- a/themes/default/home.tpl +++ b/themes/default/home.tpl @@ -1,4 +1,6 @@ Home TEMPLATE SMARTY -{debug}
\ No newline at end of file +{debug} + +{include file=$subTemplate}
\ No newline at end of file diff --git a/themes/default/loading-small.gif b/themes/default/loading-small.gif Binary files differnew file mode 100644 index 0000000000..c953410523 --- /dev/null +++ b/themes/default/loading-small.gif diff --git a/themes/default/loading.gif b/themes/default/loading.gif Binary files differnew file mode 100644 index 0000000000..f0876b27e4 --- /dev/null +++ b/themes/default/loading.gif diff --git a/themes/default/login.tpl b/themes/default/login.tpl new file mode 100644 index 0000000000..3114127992 --- /dev/null +++ b/themes/default/login.tpl @@ -0,0 +1,5 @@ +{if $AccessErrorString} +<b>{$AccessErrorString}</b> +{/if} + +{include file=genericForm.tpl}
\ No newline at end of file diff --git a/tmp/templates_c/%%05^055^055FEB02%%UsersManager.tpl.php b/tmp/templates_c/%%05^055^055FEB02%%UsersManager.tpl.php new file mode 100644 index 0000000000..691ac242ca --- /dev/null +++ b/tmp/templates_c/%%05^055^055FEB02%%UsersManager.tpl.php @@ -0,0 +1,138 @@ +<?php /* Smarty version 2.6.18, created on 2007-09-03 13:57:39 + compiled from UsersManager/templates/UsersManager.tpl */ ?> +<script type="text/javascript" src="libs/jquery/jquery.js"></script> + +<?php echo ' + +<style> +#access td, #users td +{ + spacing: 0px; + padding: 2px 5px 5px 4px; + border: 1px solid #660000; + width:100px; +} + + +#ajaxError{ + color:red; + text-align:center; + font-weight:bold; + width:550px; + border: 3px solid red; + margin: 10px; + padding: 10px; +} + +#addrow img { + vertical-align:middle; +} +#addrow a { + text-decoration:none; +} + +#accessUpdated{ + display:none; + border:2px solid green; + color:green; + width:100px; + text-align:center; +} + +</style> +'; ?> + + +<h2>Access</h2> + +<div id="sites"> +<form method="get" action="<?php echo $this->_tpl_vars['formUrl']; ?> +" id="accessSites"> + <input type="hidden" name="module" value="UsersManager"> + <p>Sites: <select id="selectIdsite" name="idsite" onchange="this.form.submit()"> + + <optgroup label="All websites"> + <option label="All websites" value="-1" <?php if ($this->_tpl_vars['idSiteSelected'] == -1): ?> selected="selected"<?php endif; ?>>Apply to all websites</option> + </optgroup> + <optgroup label="Sites"> + <?php $_from = $this->_tpl_vars['websites']; if (!is_array($_from) && !is_object($_from)) { settype($_from, 'array'); }if (count($_from)): + foreach ($_from as $this->_tpl_vars['info']): +?> + <option value="<?php echo $this->_tpl_vars['info']['idsite']; ?> +" <?php if ($this->_tpl_vars['idSiteSelected'] == $this->_tpl_vars['info']['idsite']): ?> selected="selected"<?php endif; ?>><?php echo $this->_tpl_vars['info']['name']; ?> +</option> + <?php endforeach; endif; unset($_from); ?> + </optgroup> + + </select></p> +</form> +</div> + +<table id="access"> +<tr> + <td>User</td> + <td>No access</td> + <td>View</td> + <td>Admin</td> +</tr> +<?php $_from = $this->_tpl_vars['usersAccessByWebsite']; if (!is_array($_from) && !is_object($_from)) { settype($_from, 'array'); }if (count($_from)): + foreach ($_from as $this->_tpl_vars['login'] => $this->_tpl_vars['access']): +?> +<?php $this->assign('accesValid', "<img src='plugins/UsersManager/images/ok.png' class='accessGranted'>"); ?> +<?php $this->assign('accesInvalid', "<img src='plugins/UsersManager/images/no-access.png' class='updateAccess'>"); ?> +<tr> + <td id='login'><?php echo $this->_tpl_vars['login']; ?> +</td> + <td id='noaccess'><?php if ($this->_tpl_vars['access'] == 'noaccess' && $this->_tpl_vars['idSiteSelected'] != -1): ?><?php echo $this->_tpl_vars['accesValid']; ?> +<?php else: ?><?php echo $this->_tpl_vars['accesInvalid']; ?> +<?php endif; ?> </td> + <td id='view'><?php if ($this->_tpl_vars['access'] == 'view' && $this->_tpl_vars['idSiteSelected'] != -1): ?><?php echo $this->_tpl_vars['accesValid']; ?> +<?php else: ?><?php echo $this->_tpl_vars['accesInvalid']; ?> +<?php endif; ?> </td> + <td id='admin'><?php if ($this->_tpl_vars['access'] == 'admin' && $this->_tpl_vars['idSiteSelected'] != -1): ?><?php echo $this->_tpl_vars['accesValid']; ?> +<?php else: ?><?php echo $this->_tpl_vars['accesInvalid']; ?> +<?php endif; ?> </td> +</tr> +<?php endforeach; endif; unset($_from); ?> +</table> + +<div id="accessUpdated">Done!</div> + + +<h2>Users</h2> + +<div id="ajaxError" style="display:none"></div> +<div id="ajaxLoading" style="display:none">Loading... <img src="themes/default/loading.gif"></div> +<table id="users"> + <tbody> + <tr> + <td>Login</td> + <td>Password</td> + <td>Email</td> + <td>Alias</td> + <td>Edit</td> + <td>Delete</td> + </tr> + <?php $_from = $this->_tpl_vars['users']; if (!is_array($_from) && !is_object($_from)) { settype($_from, 'array'); }if (count($_from)): + foreach ($_from as $this->_tpl_vars['i'] => $this->_tpl_vars['user']): +?> + <tr class="editable" id="row<?php echo $this->_tpl_vars['i']; ?> +"> + <td id="userLogin" class="editable"><?php echo $this->_tpl_vars['user']['login']; ?> +</td> + <td id="password" class="editable">-</td> + <td id="email" class="editable"><?php echo $this->_tpl_vars['user']['email']; ?> +</td> + <td id="alias" class="editable"><?php echo $this->_tpl_vars['user']['alias']; ?> +</td> + <td><img src='plugins/UsersManager/images/edit.png' class="edituser" id="row<?php echo $this->_tpl_vars['i']; ?> +" href='#'></td> + <td><img src='plugins/UsersManager/images/remove.png' class="deleteuser" id="row<?php echo $this->_tpl_vars['i']; ?> +" value="Delete"></td> + </tr> + <?php endforeach; endif; unset($_from); ?> + </tbody> + +</table> +<div id="addrow"><img src='plugins/UsersManager/images/add.png'> <a href="#">Add a new user</a></div> +<script type="text/javascript" src="plugins/UsersManager/templates/UsersManager.js"></script>
\ No newline at end of file diff --git a/tmp/templates_c/%%0F^0FA^0FAD1594%%debug.tpl.php b/tmp/templates_c/%%0F^0FA^0FAD1594%%debug.tpl.php new file mode 100644 index 0000000000..32618b90f7 --- /dev/null +++ b/tmp/templates_c/%%0F^0FA^0FAD1594%%debug.tpl.php @@ -0,0 +1,263 @@ +<?php /* Smarty version 2.6.18, created on 2007-09-03 13:57:31 + compiled from file:/home/matt/dev/piwiktrunk/libs/Smarty/debug.tpl */ ?> +<?php require_once(SMARTY_CORE_DIR . 'core.load_plugins.php'); +smarty_core_load_plugins(array('plugins' => array(array('function', 'assign_debug_info', 'file:/home/matt/dev/piwiktrunk/libs/Smarty/debug.tpl', 3, false),array('function', 'cycle', 'file:/home/matt/dev/piwiktrunk/libs/Smarty/debug.tpl', 119, false),array('modifier', 'escape', 'file:/home/matt/dev/piwiktrunk/libs/Smarty/debug.tpl', 102, false),array('modifier', 'string_format', 'file:/home/matt/dev/piwiktrunk/libs/Smarty/debug.tpl', 105, false),array('modifier', 'debug_print_var', 'file:/home/matt/dev/piwiktrunk/libs/Smarty/debug.tpl', 121, false),)), $this); ?> +<?php echo smarty_function_assign_debug_info(array(), $this);?> + +<?php ob_start(); ?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> +<head> + <title>Smarty Debug Console</title> +<?php echo ' +<style type="text/css"> +/* <![CDATA[ */ +body, h1, h2, td, th, p { + font-family: sans-serif; + font-weight: normal; + font-size: 0.9em; + margin: 1px; + padding: 0; +} + +h1 { + margin: 0; + text-align: left; + padding: 2px; + background-color: #f0c040; + color: black; + font-weight: bold; + font-size: 1.2em; + } + +h2 { + background-color: #9B410E; + color: white; + text-align: left; + font-weight: bold; + padding: 2px; + border-top: 1px solid black; +} + +body { + background: black; +} + +p, table, div { + background: #f0ead8; +} + +p { + margin: 0; + font-style: italic; + text-align: center; +} + +table { + width: 100%; +} + +th, td { + font-family: monospace; + vertical-align: top; + text-align: left; + width: 50%; +} + +td { + color: green; +} + +.odd { + background-color: #eeeeee; +} + +.even { + background-color: #fafafa; +} + +.exectime { + font-size: 0.8em; + font-style: italic; +} + +#table_assigned_vars th { + color: blue; +} + +#table_config_vars th { + color: maroon; +} +/* ]]> */ +</style> +'; ?> + +</head> +<body> + +<h1>Smarty Debug Console</h1> + +<h2>included templates & config files (load time in seconds)</h2> + +<div> +<?php unset($this->_sections['templates']); +$this->_sections['templates']['name'] = 'templates'; +$this->_sections['templates']['loop'] = is_array($_loop=$this->_tpl_vars['_debug_tpls']) ? count($_loop) : max(0, (int)$_loop); unset($_loop); +$this->_sections['templates']['show'] = true; +$this->_sections['templates']['max'] = $this->_sections['templates']['loop']; +$this->_sections['templates']['step'] = 1; +$this->_sections['templates']['start'] = $this->_sections['templates']['step'] > 0 ? 0 : $this->_sections['templates']['loop']-1; +if ($this->_sections['templates']['show']) { + $this->_sections['templates']['total'] = $this->_sections['templates']['loop']; + if ($this->_sections['templates']['total'] == 0) + $this->_sections['templates']['show'] = false; +} else + $this->_sections['templates']['total'] = 0; +if ($this->_sections['templates']['show']): + + for ($this->_sections['templates']['index'] = $this->_sections['templates']['start'], $this->_sections['templates']['iteration'] = 1; + $this->_sections['templates']['iteration'] <= $this->_sections['templates']['total']; + $this->_sections['templates']['index'] += $this->_sections['templates']['step'], $this->_sections['templates']['iteration']++): +$this->_sections['templates']['rownum'] = $this->_sections['templates']['iteration']; +$this->_sections['templates']['index_prev'] = $this->_sections['templates']['index'] - $this->_sections['templates']['step']; +$this->_sections['templates']['index_next'] = $this->_sections['templates']['index'] + $this->_sections['templates']['step']; +$this->_sections['templates']['first'] = ($this->_sections['templates']['iteration'] == 1); +$this->_sections['templates']['last'] = ($this->_sections['templates']['iteration'] == $this->_sections['templates']['total']); +?> + <?php unset($this->_sections['indent']); +$this->_sections['indent']['name'] = 'indent'; +$this->_sections['indent']['loop'] = is_array($_loop=$this->_tpl_vars['_debug_tpls'][$this->_sections['templates']['index']]['depth']) ? count($_loop) : max(0, (int)$_loop); unset($_loop); +$this->_sections['indent']['show'] = true; +$this->_sections['indent']['max'] = $this->_sections['indent']['loop']; +$this->_sections['indent']['step'] = 1; +$this->_sections['indent']['start'] = $this->_sections['indent']['step'] > 0 ? 0 : $this->_sections['indent']['loop']-1; +if ($this->_sections['indent']['show']) { + $this->_sections['indent']['total'] = $this->_sections['indent']['loop']; + if ($this->_sections['indent']['total'] == 0) + $this->_sections['indent']['show'] = false; +} else + $this->_sections['indent']['total'] = 0; +if ($this->_sections['indent']['show']): + + for ($this->_sections['indent']['index'] = $this->_sections['indent']['start'], $this->_sections['indent']['iteration'] = 1; + $this->_sections['indent']['iteration'] <= $this->_sections['indent']['total']; + $this->_sections['indent']['index'] += $this->_sections['indent']['step'], $this->_sections['indent']['iteration']++): +$this->_sections['indent']['rownum'] = $this->_sections['indent']['iteration']; +$this->_sections['indent']['index_prev'] = $this->_sections['indent']['index'] - $this->_sections['indent']['step']; +$this->_sections['indent']['index_next'] = $this->_sections['indent']['index'] + $this->_sections['indent']['step']; +$this->_sections['indent']['first'] = ($this->_sections['indent']['iteration'] == 1); +$this->_sections['indent']['last'] = ($this->_sections['indent']['iteration'] == $this->_sections['indent']['total']); +?> <?php endfor; endif; ?> + <font color=<?php if ($this->_tpl_vars['_debug_tpls'][$this->_sections['templates']['index']]['type'] == 'template'): ?>brown<?php elseif ($this->_tpl_vars['_debug_tpls'][$this->_sections['templates']['index']]['type'] == 'insert'): ?>black<?php else: ?>green<?php endif; ?>> + <?php echo ((is_array($_tmp=$this->_tpl_vars['_debug_tpls'][$this->_sections['templates']['index']]['filename'])) ? $this->_run_mod_handler('escape', true, $_tmp, 'html') : smarty_modifier_escape($_tmp, 'html')); ?> +</font> + <?php if (isset ( $this->_tpl_vars['_debug_tpls'][$this->_sections['templates']['index']]['exec_time'] )): ?> + <span class="exectime"> + (<?php echo ((is_array($_tmp=$this->_tpl_vars['_debug_tpls'][$this->_sections['templates']['index']]['exec_time'])) ? $this->_run_mod_handler('string_format', true, $_tmp, "%.5f") : smarty_modifier_string_format($_tmp, "%.5f")); ?> +) + <?php if ($this->_sections['templates']['index'] == 0): ?>(total)<?php endif; ?> + </span> + <?php endif; ?> + <br /> +<?php endfor; else: ?> + <p>no templates included</p> +<?php endif; ?> +</div> + +<h2>assigned template variables</h2> + +<table id="table_assigned_vars"> + <?php unset($this->_sections['vars']); +$this->_sections['vars']['name'] = 'vars'; +$this->_sections['vars']['loop'] = is_array($_loop=$this->_tpl_vars['_debug_keys']) ? count($_loop) : max(0, (int)$_loop); unset($_loop); +$this->_sections['vars']['show'] = true; +$this->_sections['vars']['max'] = $this->_sections['vars']['loop']; +$this->_sections['vars']['step'] = 1; +$this->_sections['vars']['start'] = $this->_sections['vars']['step'] > 0 ? 0 : $this->_sections['vars']['loop']-1; +if ($this->_sections['vars']['show']) { + $this->_sections['vars']['total'] = $this->_sections['vars']['loop']; + if ($this->_sections['vars']['total'] == 0) + $this->_sections['vars']['show'] = false; +} else + $this->_sections['vars']['total'] = 0; +if ($this->_sections['vars']['show']): + + for ($this->_sections['vars']['index'] = $this->_sections['vars']['start'], $this->_sections['vars']['iteration'] = 1; + $this->_sections['vars']['iteration'] <= $this->_sections['vars']['total']; + $this->_sections['vars']['index'] += $this->_sections['vars']['step'], $this->_sections['vars']['iteration']++): +$this->_sections['vars']['rownum'] = $this->_sections['vars']['iteration']; +$this->_sections['vars']['index_prev'] = $this->_sections['vars']['index'] - $this->_sections['vars']['step']; +$this->_sections['vars']['index_next'] = $this->_sections['vars']['index'] + $this->_sections['vars']['step']; +$this->_sections['vars']['first'] = ($this->_sections['vars']['iteration'] == 1); +$this->_sections['vars']['last'] = ($this->_sections['vars']['iteration'] == $this->_sections['vars']['total']); +?> + <tr class="<?php echo smarty_function_cycle(array('values' => "odd,even"), $this);?> +"> + <th>{$<?php echo ((is_array($_tmp=$this->_tpl_vars['_debug_keys'][$this->_sections['vars']['index']])) ? $this->_run_mod_handler('escape', true, $_tmp, 'html') : smarty_modifier_escape($_tmp, 'html')); ?> +}</th> + <td><?php echo smarty_modifier_debug_print_var($this->_tpl_vars['_debug_vals'][$this->_sections['vars']['index']]); ?> +</td></tr> + <?php endfor; else: ?> + <tr><td><p>no template variables assigned</p></td></tr> + <?php endif; ?> +</table> + +<h2>assigned config file variables (outer template scope)</h2> + +<table id="table_config_vars"> + <?php unset($this->_sections['config_vars']); +$this->_sections['config_vars']['name'] = 'config_vars'; +$this->_sections['config_vars']['loop'] = is_array($_loop=$this->_tpl_vars['_debug_config_keys']) ? count($_loop) : max(0, (int)$_loop); unset($_loop); +$this->_sections['config_vars']['show'] = true; +$this->_sections['config_vars']['max'] = $this->_sections['config_vars']['loop']; +$this->_sections['config_vars']['step'] = 1; +$this->_sections['config_vars']['start'] = $this->_sections['config_vars']['step'] > 0 ? 0 : $this->_sections['config_vars']['loop']-1; +if ($this->_sections['config_vars']['show']) { + $this->_sections['config_vars']['total'] = $this->_sections['config_vars']['loop']; + if ($this->_sections['config_vars']['total'] == 0) + $this->_sections['config_vars']['show'] = false; +} else + $this->_sections['config_vars']['total'] = 0; +if ($this->_sections['config_vars']['show']): + + for ($this->_sections['config_vars']['index'] = $this->_sections['config_vars']['start'], $this->_sections['config_vars']['iteration'] = 1; + $this->_sections['config_vars']['iteration'] <= $this->_sections['config_vars']['total']; + $this->_sections['config_vars']['index'] += $this->_sections['config_vars']['step'], $this->_sections['config_vars']['iteration']++): +$this->_sections['config_vars']['rownum'] = $this->_sections['config_vars']['iteration']; +$this->_sections['config_vars']['index_prev'] = $this->_sections['config_vars']['index'] - $this->_sections['config_vars']['step']; +$this->_sections['config_vars']['index_next'] = $this->_sections['config_vars']['index'] + $this->_sections['config_vars']['step']; +$this->_sections['config_vars']['first'] = ($this->_sections['config_vars']['iteration'] == 1); +$this->_sections['config_vars']['last'] = ($this->_sections['config_vars']['iteration'] == $this->_sections['config_vars']['total']); +?> + <tr class="<?php echo smarty_function_cycle(array('values' => "odd,even"), $this);?> +"> + <th>{#<?php echo ((is_array($_tmp=$this->_tpl_vars['_debug_config_keys'][$this->_sections['config_vars']['index']])) ? $this->_run_mod_handler('escape', true, $_tmp, 'html') : smarty_modifier_escape($_tmp, 'html')); ?> +#}</th> + <td><?php echo smarty_modifier_debug_print_var($this->_tpl_vars['_debug_config_vals'][$this->_sections['config_vars']['index']]); ?> +</td></tr> + <?php endfor; else: ?> + <tr><td><p>no config vars assigned</p></td></tr> + <?php endif; ?> +</table> +</body> +</html> +<?php $this->_smarty_vars['capture']['default'] = ob_get_contents(); $this->assign('debug_output', ob_get_contents());ob_end_clean(); ?> +<?php if (isset ( $this->_tpl_vars['_smarty_debug_output'] ) && $this->_tpl_vars['_smarty_debug_output'] == 'html'): ?> + <?php echo $this->_tpl_vars['debug_output']; ?> + +<?php else: ?> +<script type="text/javascript"> +// <![CDATA[ + if ( self.name == '' ) { + var title = 'Console'; + } + else { + var title = 'Console_' + self.name; + } + _smarty_console = window.open("",title.value,"width=680,height=600,resizable,scrollbars=yes"); + _smarty_console.document.write('<?php echo ((is_array($_tmp=$this->_tpl_vars['debug_output'])) ? $this->_run_mod_handler('escape', true, $_tmp, 'javascript') : smarty_modifier_escape($_tmp, 'javascript')); ?> +'); + _smarty_console.document.close(); +// ]]> +</script> +<?php endif; ?>
\ No newline at end of file diff --git a/tmp/templates_c/%%51^513^513E196B%%form.tpl.php b/tmp/templates_c/%%51^513^513E196B%%form.tpl.php new file mode 100644 index 0000000000..6d7d738d67 --- /dev/null +++ b/tmp/templates_c/%%51^513^513E196B%%form.tpl.php @@ -0,0 +1,46 @@ +<?php /* Smarty version 2.6.18, created on 2007-08-30 11:23:07 + compiled from form.tpl */ ?> + +<form <?php echo $this->_tpl_vars['form_data']['attributes']; ?> +> +<!-- Output hidden fields --> +<?php echo $this->_tpl_vars['form_data']['hidden']; ?> + +<!-- Display the fields --> +<?php $_from = $this->_tpl_vars['list_elements']; if (!is_array($_from) && !is_object($_from)) { settype($_from, 'array'); }if (count($_from)): + foreach ($_from as $this->_tpl_vars['title'] => $this->_tpl_vars['data']): +?> + <h3><?php echo $this->_tpl_vars['title']; ?> +</h3> + <div class="centrer"> + <table class="centrer"> + <?php $_from = $this->_tpl_vars['data']; if (!is_array($_from) && !is_object($_from)) { settype($_from, 'array'); }if (count($_from)): + foreach ($_from as $this->_tpl_vars['fieldname']): +?> + <?php if ($this->_tpl_vars['form_data'][$this->_tpl_vars['fieldname']]['label']): ?> + <tr> + <td><?php echo $this->_tpl_vars['form_data'][$this->_tpl_vars['fieldname']]['label']; ?> +</td> + <td><?php echo $this->_tpl_vars['form_data'][$this->_tpl_vars['fieldname']]['html']; ?> +</td> + </tr> + <?php else: ?> + <?php $_from = $this->_tpl_vars['form_data'][$this->_tpl_vars['fieldname']]; if (!is_array($_from) && !is_object($_from)) { settype($_from, 'array'); }if (count($_from)): + foreach ($_from as $this->_tpl_vars['key'] => $this->_tpl_vars['radio']): +?> + <tr> + <td><?php echo $this->_tpl_vars['radio']['label']; ?> +</td> + <td><?php echo $this->_tpl_vars['radio']['html']; ?> +</td> + </tr> + <?php endforeach; endif; unset($_from); ?> + <?php endif; ?> + <?php endforeach; endif; unset($_from); ?> + </table> + </div> +<?php endforeach; endif; unset($_from); ?> +<div class="boutonsAction"> + +</div> +</form>
\ No newline at end of file diff --git a/tmp/templates_c/%%61^614^614EB811%%genericForm.tpl.php b/tmp/templates_c/%%61^614^614EB811%%genericForm.tpl.php new file mode 100644 index 0000000000..9ae4fecc4e --- /dev/null +++ b/tmp/templates_c/%%61^614^614EB811%%genericForm.tpl.php @@ -0,0 +1,54 @@ +<?php /* Smarty version 2.6.18, created on 2007-08-31 19:21:11 + compiled from genericForm.tpl */ ?> + +<?php if ($this->_tpl_vars['form_data']['errors']): ?> + <div id="adminErrors"> + <strong>Errors</strong> + <ul> + <?php $_from = $this->_tpl_vars['form_data']['errors']; if (!is_array($_from) && !is_object($_from)) { settype($_from, 'array'); }if (count($_from)): + foreach ($_from as $this->_tpl_vars['data']): +?> + <li><?php echo $this->_tpl_vars['data']; ?> +</li> + <?php endforeach; endif; unset($_from); ?> + </ul> + </div> +<?php endif; ?> + + +<form <?php echo $this->_tpl_vars['form_data']['attributes']; ?> +> +<!-- Output hidden fields --> + +<!-- Display the fields --> +<?php $_from = $this->_tpl_vars['element_list']; if (!is_array($_from) && !is_object($_from)) { settype($_from, 'array'); }if (count($_from)): + foreach ($_from as $this->_tpl_vars['title'] => $this->_tpl_vars['data']): +?> + <h3><?php echo $this->_tpl_vars['title']; ?> +</h3> + <div class="centrer"> + <table class="centrer"> + <?php $_from = $this->_tpl_vars['data']; if (!is_array($_from) && !is_object($_from)) { settype($_from, 'array'); }if (count($_from)): + foreach ($_from as $this->_tpl_vars['fieldname']): +?> + <?php if ($this->_tpl_vars['form_data'][$this->_tpl_vars['fieldname']]['label']): ?> + <tr> + <td><?php echo $this->_tpl_vars['form_data'][$this->_tpl_vars['fieldname']]['label']; ?> +</td> + <td><?php echo $this->_tpl_vars['form_data'][$this->_tpl_vars['fieldname']]['html']; ?> +</td> + </tr> + <?php elseif ($this->_tpl_vars['form_data'][$this->_tpl_vars['fieldname']]['type'] == 'hidden'): ?> + <tr><td colspan=2><?php echo $this->_tpl_vars['form_data'][$this->_tpl_vars['fieldname']]['html']; ?> +</td></tr> + <?php endif; ?> + <?php endforeach; endif; unset($_from); ?> + </table> + </div> +<?php endforeach; endif; unset($_from); ?> +<div class="boutonsAction"> + +<?php echo $this->_tpl_vars['form_data']['submit']['html']; ?> + +</div> +</form>
\ No newline at end of file diff --git a/tmp/templates_c/%%6A^6A5^6A537DD8%%login.tpl.php b/tmp/templates_c/%%6A^6A5^6A537DD8%%login.tpl.php new file mode 100644 index 0000000000..d39c9b6da7 --- /dev/null +++ b/tmp/templates_c/%%6A^6A5^6A537DD8%%login.tpl.php @@ -0,0 +1,12 @@ +<?php /* Smarty version 2.6.18, created on 2007-08-30 13:34:22 + compiled from login.tpl */ ?> +<?php if ($this->_tpl_vars['AccessErrorString']): ?> +<b><?php echo $this->_tpl_vars['AccessErrorString']; ?> +</b> +<?php endif; ?> + +<?php $_smarty_tpl_vars = $this->_tpl_vars; +$this->_smarty_include(array('smarty_include_tpl_file' => "genericForm.tpl", 'smarty_include_vars' => array())); +$this->_tpl_vars = $_smarty_tpl_vars; +unset($_smarty_tpl_vars); + ?>
\ No newline at end of file diff --git a/tmp/templates_c/%%86^864^8644F0D6%%home.tpl.php b/tmp/templates_c/%%86^864^8644F0D6%%home.tpl.php new file mode 100644 index 0000000000..83ac6bdf46 --- /dev/null +++ b/tmp/templates_c/%%86^864^8644F0D6%%home.tpl.php @@ -0,0 +1,15 @@ +<?php /* Smarty version 2.6.18, created on 2007-08-30 11:20:11 + compiled from home.tpl */ ?> +<?php require_once(SMARTY_CORE_DIR . 'core.load_plugins.php'); +smarty_core_load_plugins(array('plugins' => array(array('function', 'debug', 'home.tpl', 4, false),)), $this); ?> +Home TEMPLATE SMARTY + + +<?php echo smarty_function_debug(array(), $this);?> + + +<?php $_smarty_tpl_vars = $this->_tpl_vars; +$this->_smarty_include(array('smarty_include_tpl_file' => $this->_tpl_vars['subTemplate'], 'smarty_include_vars' => array())); +$this->_tpl_vars = $_smarty_tpl_vars; +unset($_smarty_tpl_vars); + ?>
\ No newline at end of file diff --git a/tmp/templates_c/%%A6^A6A^A6A3DC79%%SitesManager.tpl.php b/tmp/templates_c/%%A6^A6A^A6A3DC79%%SitesManager.tpl.php new file mode 100644 index 0000000000..15f1c776f7 --- /dev/null +++ b/tmp/templates_c/%%A6^A6A^A6A3DC79%%SitesManager.tpl.php @@ -0,0 +1,61 @@ +<?php /* Smarty version 2.6.18, created on 2007-09-03 13:48:31 + compiled from SitesManager/templates/SitesManager.tpl */ ?> +<?php echo ' +<style> +* { +font-family:Trebuchet MS,arial,sans-serif; +} + +textarea{ + font-family: Trebuchet MS, Verdana; + font-size:0.85em; + +} + +#editSites{ + valign:top; +} +</style> +'; ?> + +<script type="text/javascript" src="libs/jquery/jquery.js"></script> + +<h2>Sites</h2> +<div id="ajaxError" style="display:none"></div> +<div id="ajaxLoading" style="display:none">Loading... <img src="themes/default/loading.gif"></div> + +<table id="editSites" border=1 cellpadding="10"> + <tbody> + <tr> + <td>Id</td> + <td>Name</td> + <td>URLs</td> + </tr> + + <?php $_from = $this->_tpl_vars['sites']; if (!is_array($_from) && !is_object($_from)) { settype($_from, 'array'); }if (count($_from)): + foreach ($_from as $this->_tpl_vars['i'] => $this->_tpl_vars['site']): +?> + <tr id="row<?php echo $this->_tpl_vars['i']; ?> +"> + <td id="idSite"><?php echo $this->_tpl_vars['site']['idsite']; ?> +</td> + <td id="name" class="editableSite"><?php echo $this->_tpl_vars['site']['name']; ?> +</td> + <td id="urls" class="editableSite"><?php $_from = $this->_tpl_vars['site']['alias_urls']; if (!is_array($_from) && !is_object($_from)) { settype($_from, 'array'); }if (count($_from)): + foreach ($_from as $this->_tpl_vars['url']): +?><?php echo $this->_tpl_vars['url']; ?> +<br><?php endforeach; endif; unset($_from); ?></td> + <td><img src='plugins/UsersManager/images/edit.png' class="editSite" id="row<?php echo $this->_tpl_vars['i']; ?> +" href='#'></td> + <td><img src='plugins/UsersManager/images/remove.png' class="deleteSite" id="row<?php echo $this->_tpl_vars['i']; ?> +" value="Delete"></td> + + </tr> + <?php endforeach; endif; unset($_from); ?> + + </tbody> +</table> +<div id="addRowSite"><img src='plugins/UsersManager/images/add.png'> <a href="#">Add a new Site</a></div> + +<script type="text/javascript" src="plugins/UsersManager/templates/UsersManager.js"></script> +<script type="text/javascript" src="plugins/SitesManager/templates/SitesManager.js"></script>
\ No newline at end of file |