Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/matomo-org/matomo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--TODO4
-rwxr-xr-xconfig/config.ini.php11
-rwxr-xr-xindex.php105
-rwxr-xr-xlibs/PEAR.php2
-rwxr-xr-xlibs/Smarty/Smarty.class.php7
-rw-r--r--libs/ezcomponents/Url/CREDITS16
-rw-r--r--libs/ezcomponents/Url/ChangeLog41
-rw-r--r--libs/ezcomponents/Url/DESCRIPTION2
-rw-r--r--libs/ezcomponents/Url/design/class_diagram.pngbin0 -> 14029 bytes
-rw-r--r--libs/ezcomponents/Url/design/requirements.txt68
-rw-r--r--libs/ezcomponents/Url/docs/tutorial.txt273
-rw-r--r--libs/ezcomponents/Url/docs/tutorial/tutorial_autoload.php20
-rw-r--r--libs/ezcomponents/Url/docs/tutorial/tutorial_cfg_change.php50
-rw-r--r--libs/ezcomponents/Url/docs/tutorial/tutorial_cfg_create.php29
-rw-r--r--libs/ezcomponents/Url/docs/tutorial/tutorial_get_params.php36
-rw-r--r--libs/ezcomponents/Url/docs/tutorial/tutorial_get_query.php10
-rw-r--r--libs/ezcomponents/Url/docs/tutorial/tutorial_lazy_initialization.php39
-rw-r--r--libs/ezcomponents/Url/docs/tutorial/tutorial_set_params.php32
-rw-r--r--libs/ezcomponents/Url/docs/tutorial/tutorial_set_query.php26
-rw-r--r--libs/ezcomponents/Url/docs/tutorial/tutorial_url_creator.php16
-rw-r--r--libs/ezcomponents/Url/docs/tutorial/tutorial_url_creator_params.php12
-rw-r--r--libs/ezcomponents/Url/src/exceptions/url_exception.php30
-rw-r--r--libs/ezcomponents/Url/src/exceptions/url_invalid_parameter_exception.php30
-rw-r--r--libs/ezcomponents/Url/src/exceptions/url_no_configuration_exception.php30
-rw-r--r--libs/ezcomponents/Url/src/exceptions/url_not_registered_exception.php30
-rw-r--r--libs/ezcomponents/Url/src/url.php576
-rw-r--r--libs/ezcomponents/Url/src/url_configuration.php270
-rw-r--r--libs/ezcomponents/Url/src/url_creator.php124
-rw-r--r--libs/ezcomponents/Url/tests/suite.php39
-rw-r--r--libs/ezcomponents/Url/tests/url_configuration_test.php122
-rw-r--r--libs/ezcomponents/Url/tests/url_creator_test.php71
-rw-r--r--libs/ezcomponents/Url/tests/url_test.php572
-rw-r--r--libs/jquery/jquery.js2508
-rw-r--r--libs/jquery/loadingAnimation.gifbin0 -> 5886 bytes
-rw-r--r--libs/jquery/macFFBgHack.pngbin0 -> 207 bytes
-rw-r--r--libs/jquery/thickbox.css163
-rw-r--r--libs/jquery/thickbox.js10
-rw-r--r--libs/json/json.js317
-rw-r--r--misc/generateVisits.php2
-rwxr-xr-xmodules/API/APIable.php2
-rwxr-xr-xmodules/API/Proxy.php5
-rw-r--r--modules/API/Request.php69
-rwxr-xr-xmodules/Access.php60
-rw-r--r--modules/Archive.php1
-rw-r--r--modules/Auth.php8
-rw-r--r--modules/Cookie.php (renamed from modules/LogStats/Cookie.php)12
-rw-r--r--modules/DataTable.php4
-rw-r--r--modules/DataTable/Renderer.php1
-rw-r--r--modules/DataTable/Renderer/Console.php13
-rw-r--r--modules/Form.php58
-rw-r--r--modules/FrontController.php107
-rw-r--r--modules/Log/Error.php8
-rw-r--r--modules/LogStats/Generator.php178
-rw-r--r--modules/LogStats/Visit.php6
-rwxr-xr-xmodules/Piwik.php70
-rw-r--r--modules/Plugin.php5
-rw-r--r--modules/Url.php20
-rw-r--r--modules/View.php17
-rw-r--r--piwik.php2
-rw-r--r--plugins/API/Controller.php13
-rw-r--r--plugins/Home/Controller.php20
-rw-r--r--plugins/Login.php13
-rw-r--r--plugins/Login/Controller.php59
-rw-r--r--plugins/Login/Form.php28
-rw-r--r--plugins/Logout/Controller.php15
-rw-r--r--plugins/SitesManager.php26
-rwxr-xr-xplugins/SitesManager/API.php (renamed from modules/SitesManager.php)27
-rw-r--r--plugins/SitesManager/Controller.php18
-rw-r--r--plugins/SitesManager/templates/SitesManager.js101
-rw-r--r--plugins/SitesManager/templates/SitesManager.tpl48
-rw-r--r--plugins/UsersManager.php26
-rwxr-xr-xplugins/UsersManager/API.php (renamed from modules/UsersManager.php)62
-rw-r--r--plugins/UsersManager/Controller.php53
-rw-r--r--plugins/UsersManager/images/add.pngbin0 -> 323 bytes
-rw-r--r--plugins/UsersManager/images/edit.pngbin0 -> 574 bytes
-rw-r--r--plugins/UsersManager/images/no-access.pngbin0 -> 653 bytes
-rw-r--r--plugins/UsersManager/images/no.pngbin0 -> 820 bytes
-rw-r--r--plugins/UsersManager/images/ok.pngbin0 -> 851 bytes
-rw-r--r--plugins/UsersManager/images/remove.pngbin0 -> 820 bytes
-rw-r--r--plugins/UsersManager/images/valid.pngbin0 -> 676 bytes
-rw-r--r--plugins/UsersManager/templates/UsersManager.js326
-rw-r--r--plugins/UsersManager/templates/UsersManager.tpl113
-rwxr-xr-xtests/config_test.php4
-rw-r--r--tests/modules/DataTable.test.php2
-rwxr-xr-xtests/modules/Database.test.php2
-rwxr-xr-xtests/modules/SitesManager.test.php2
-rw-r--r--tests/modules/UsersManager.test.php19
-rwxr-xr-xthemes/default/genericForm.tpl53
-rw-r--r--themes/default/home.tpl4
-rw-r--r--themes/default/loading-small.gifbin0 -> 1456 bytes
-rw-r--r--themes/default/loading.gifbin0 -> 4176 bytes
-rw-r--r--themes/default/login.tpl5
-rw-r--r--tmp/templates_c/%%05^055^055FEB02%%UsersManager.tpl.php138
-rw-r--r--tmp/templates_c/%%0F^0FA^0FAD1594%%debug.tpl.php263
-rw-r--r--tmp/templates_c/%%51^513^513E196B%%form.tpl.php46
-rw-r--r--tmp/templates_c/%%61^614^614EB811%%genericForm.tpl.php54
-rw-r--r--tmp/templates_c/%%6A^6A5^6A537DD8%%login.tpl.php12
-rw-r--r--tmp/templates_c/%%86^864^8644F0D6%%home.tpl.php15
-rw-r--r--tmp/templates_c/%%A6^A6A^A6A3DC79%%SitesManager.tpl.php61
99 files changed, 7666 insertions, 301 deletions
diff --git a/TODO b/TODO
index b858f10dcf..104e4c3dbc 100644
--- a/TODO
+++ b/TODO
@@ -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
diff --git a/index.php b/index.php
index feff451cb5..a8c5bbf893 100755
--- a/index.php
+++ b/index.php
@@ -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
new file mode 100644
index 0000000000..27ab1cec84
--- /dev/null
+++ b/libs/ezcomponents/Url/design/class_diagram.png
Binary files differ
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
new file mode 100644
index 0000000000..82290f4833
--- /dev/null
+++ b/libs/jquery/loadingAnimation.gif
Binary files differ
diff --git a/libs/jquery/macFFBgHack.png b/libs/jquery/macFFBgHack.png
new file mode 100644
index 0000000000..c6473b324e
--- /dev/null
+++ b/libs/jquery/macFFBgHack.png
Binary files differ
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)
diff --git a/piwik.php b/piwik.php
index 7aadd64274..cb740b2a08 100644
--- a/piwik.php
+++ b/piwik.php
@@ -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
new file mode 100644
index 0000000000..1aa7f095c6
--- /dev/null
+++ b/plugins/UsersManager/images/add.png
Binary files differ
diff --git a/plugins/UsersManager/images/edit.png b/plugins/UsersManager/images/edit.png
new file mode 100644
index 0000000000..188e1c12bd
--- /dev/null
+++ b/plugins/UsersManager/images/edit.png
Binary files differ
diff --git a/plugins/UsersManager/images/no-access.png b/plugins/UsersManager/images/no-access.png
new file mode 100644
index 0000000000..2f66cdebbb
--- /dev/null
+++ b/plugins/UsersManager/images/no-access.png
Binary files differ
diff --git a/plugins/UsersManager/images/no.png b/plugins/UsersManager/images/no.png
new file mode 100644
index 0000000000..ab6808fba5
--- /dev/null
+++ b/plugins/UsersManager/images/no.png
Binary files differ
diff --git a/plugins/UsersManager/images/ok.png b/plugins/UsersManager/images/ok.png
new file mode 100644
index 0000000000..0582c35707
--- /dev/null
+++ b/plugins/UsersManager/images/ok.png
Binary files differ
diff --git a/plugins/UsersManager/images/remove.png b/plugins/UsersManager/images/remove.png
new file mode 100644
index 0000000000..ab6808fba5
--- /dev/null
+++ b/plugins/UsersManager/images/remove.png
Binary files differ
diff --git a/plugins/UsersManager/images/valid.png b/plugins/UsersManager/images/valid.png
new file mode 100644
index 0000000000..6ef8de76e0
--- /dev/null
+++ b/plugins/UsersManager/images/valid.png
Binary files differ
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}&nbsp;</td>
+ <td id='view'>{if $access=='view' and $idSiteSelected!=-1}{$accesValid}{else}{$accesInvalid}{/if}&nbsp;</td>
+ <td id='admin'>{if $access=='admin' and $idSiteSelected!=-1}{$accesValid}{else}{$accesInvalid}{/if}&nbsp;</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
new file mode 100644
index 0000000000..c953410523
--- /dev/null
+++ b/themes/default/loading-small.gif
Binary files differ
diff --git a/themes/default/loading.gif b/themes/default/loading.gif
new file mode 100644
index 0000000000..f0876b27e4
--- /dev/null
+++ b/themes/default/loading.gif
Binary files differ
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; ?>&nbsp;</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; ?>&nbsp;</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; ?>&nbsp;</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 &amp; 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']);
+?>&nbsp;&nbsp;&nbsp;<?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