From d397e6da631cb6d262ad14ec7b46b75d1b60fbcf Mon Sep 17 00:00:00 2001 From: Christopher Smith Date: Wed, 12 Mar 2014 15:25:18 +0000 Subject: Restore correct public interface of getUserData() for authldap plugin The outer/public getUserData() implemented as a wrapper for the previous fn which is now protected. --- lib/plugins/authldap/auth.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/plugins/authldap/auth.php b/lib/plugins/authldap/auth.php index 6c3637e15..9d03afd7f 100644 --- a/lib/plugins/authldap/auth.php +++ b/lib/plugins/authldap/auth.php @@ -103,7 +103,7 @@ class auth_plugin_authldap extends DokuWiki_Auth_Plugin { return true; } else { // See if we can find the user - $info = $this->getUserData($user, true); + $info = $this->_getUserData($user, true); if(empty($info['dn'])) { return false; } else { @@ -145,11 +145,19 @@ class auth_plugin_authldap extends DokuWiki_Auth_Plugin { * @author Stephane Chazelas * @author Steffen Schoch * + * @param string $user + * @return array containing user data or false + */ + public function getUserData($user) { + return $this->_getUserData($user); + } + + /** * @param string $user * @param bool $inbind authldap specific, true if in bind phase * @return array containing user data or false */ - public function getUserData($user, $inbind = false) { + protected function _getUserData($user, $inbind = false) { global $conf; if(!$this->_openLDAP()) return false; -- cgit v1.2.3 From 2046a6546c8ed62b9a7b33305b6201458f2f8291 Mon Sep 17 00:00:00 2001 From: Christopher Smith Date: Wed, 12 Mar 2014 15:38:28 +0000 Subject: Allow user info to be retrieved without groups Some parts of dokuwiki (e.g. recent changes, old revisions) can requests lots of user info (to provide editor names) without requiring any group information. This change also implements caching of user info by authmysql & authpgsql plugins to avoid repeated querying of the DB to retrieve the same user information. --- lib/plugins/auth.php | 6 +- lib/plugins/authad/auth.php | 3 +- lib/plugins/authldap/auth.php | 3 +- lib/plugins/authmysql/auth.php | 147 +++++++++++++++++++++++++++++++++++------ lib/plugins/authpgsql/auth.php | 8 ++- lib/plugins/authplain/auth.php | 3 +- 6 files changed, 143 insertions(+), 27 deletions(-) (limited to 'lib') diff --git a/lib/plugins/auth.php b/lib/plugins/auth.php index b04735639..f9e5cded9 100644 --- a/lib/plugins/auth.php +++ b/lib/plugins/auth.php @@ -15,6 +15,9 @@ if(!defined('DOKU_INC')) die(); class DokuWiki_Auth_Plugin extends DokuWiki_Plugin { public $success = true; + const IGNORE_GROUPS = false; + const REQUIRE_GROUPS = true; + /** * Possible things an auth backend module may be able to * do. The things a backend can do need to be set to true @@ -234,9 +237,10 @@ class DokuWiki_Auth_Plugin extends DokuWiki_Plugin { * * @author Andreas Gohr * @param string $user the user name + * @param bool $requireGroups whether or not the returned data must include groups * @return array containing user data or false */ - public function getUserData($user) { + public function getUserData($user, $requireGroups=true) { if(!$this->cando['external']) msg("no valid authorisation system in use", -1); return false; } diff --git a/lib/plugins/authad/auth.php b/lib/plugins/authad/auth.php index 0860e5756..a3119dda6 100644 --- a/lib/plugins/authad/auth.php +++ b/lib/plugins/authad/auth.php @@ -177,9 +177,10 @@ class auth_plugin_authad extends DokuWiki_Auth_Plugin { * * @author James Van Lommel * @param string $user + * @param bool $requireGroups (optional) - ignored, groups are always supplied by this plugin * @return array */ - public function getUserData($user) { + public function getUserData($user, $requireGroups=true) { global $conf; global $lang; global $ID; diff --git a/lib/plugins/authldap/auth.php b/lib/plugins/authldap/auth.php index 9d03afd7f..eaa2d160a 100644 --- a/lib/plugins/authldap/auth.php +++ b/lib/plugins/authldap/auth.php @@ -146,9 +146,10 @@ class auth_plugin_authldap extends DokuWiki_Auth_Plugin { * @author Steffen Schoch * * @param string $user + * @param bool $requireGroups (optional) - ignored, groups are always supplied by this plugin * @return array containing user data or false */ - public function getUserData($user) { + public function getUserData($user, $requireGroups=true) { return $this->_getUserData($user); } diff --git a/lib/plugins/authmysql/auth.php b/lib/plugins/authmysql/auth.php index 1e6e6a4a9..774fce01f 100644 --- a/lib/plugins/authmysql/auth.php +++ b/lib/plugins/authmysql/auth.php @@ -21,6 +21,9 @@ class auth_plugin_authmysql extends DokuWiki_Auth_Plugin { /** @var int database subrevision */ protected $dbsub = 0; + /** @var array cache to avoid re-reading user info data */ + protected $cacheUserInfo = array(); + /** * Constructor * @@ -174,12 +177,18 @@ class auth_plugin_authmysql extends DokuWiki_Auth_Plugin { * @author Matthias Grimm * * @param string $user user login to get data for + * @param bool $requireGroups when true, group membership information should be included in the returned array; + * when false, it maybe included, but is not required by the caller * @return array|bool */ - public function getUserData($user) { + public function getUserData($user, $requireGroups=true) { + if($this->_cacheExists($user, $requireGroups)) { + return $this->cacheUserInfo[$user]; + } + if($this->_openDB()) { $this->_lockTables("READ"); - $info = $this->_getUserInfo($user); + $info = $this->_getUserInfo($user, $requireGroups); $this->_unlockTables(); $this->_closeDB(); } else @@ -262,21 +271,23 @@ class auth_plugin_authmysql extends DokuWiki_Auth_Plugin { if($this->_openDB()) { $this->_lockTables("WRITE"); - if(($uid = $this->_getUserID($user))) { - $rc = $this->_updateUserInfo($changes, $uid); + $rc = $this->_updateUserInfo($user, $changes); - if($rc && isset($changes['grps']) && $this->cando['modGroups']) { - $groups = $this->_getGroups($user); - $grpadd = array_diff($changes['grps'], $groups); - $grpdel = array_diff($groups, $changes['grps']); + if($rc && isset($changes['grps']) && $this->cando['modGroups']) { + $groups = $this->_getGroups($user); + $grpadd = array_diff($changes['grps'], $groups); + $grpdel = array_diff($groups, $changes['grps']); - foreach($grpadd as $group) - if(($this->_addUserToGroup($user, $group, 1)) == false) - $rc = false; + foreach($grpadd as $group) { + if(($this->_addUserToGroup($user, $group, 1)) == false) { + $rc = false; + } + } - foreach($grpdel as $group) - if(($this->_delUserFromGroup($user, $group)) == false) - $rc = false; + foreach($grpdel as $group) { + if(($this->_delUserFromGroup($user, $group)) == false) { + $rc = false; + } } } @@ -466,7 +477,10 @@ class auth_plugin_authmysql extends DokuWiki_Auth_Plugin { $sql = str_replace('%{user}', $this->_escape($user), $sql); $sql = str_replace('%{gid}', $this->_escape($gid), $sql); $sql = str_replace('%{group}', $this->_escape($group), $sql); - if($this->_modifyDB($sql) !== false) return true; + if($this->_modifyDB($sql) !== false) { + $this->_flushUserInfoCache($user); + return true; + } if($newgroup) { // remove previously created group on error $sql = str_replace('%{gid}', $this->_escape($gid), $this->getConf('delGroup')); @@ -501,6 +515,10 @@ class auth_plugin_authmysql extends DokuWiki_Auth_Plugin { $sql = str_replace('%{gid}', $this->_escape($gid), $sql); $sql = str_replace('%{group}', $this->_escape($group), $sql); $rc = $this->_modifyDB($sql) == 0 ? true : false; + + if ($rc) { + $this->_flushUserInfoCache($user); + } } } return $rc; @@ -590,6 +608,7 @@ class auth_plugin_authmysql extends DokuWiki_Auth_Plugin { } if($gid !== false){ + $this->_flushUserInfoCache($user); return true; } else { /* remove the new user and all group relations if a group can't @@ -626,16 +645,96 @@ class auth_plugin_authmysql extends DokuWiki_Auth_Plugin { $sql = str_replace('%{uid}', $this->_escape($uid), $this->getConf('delUser')); $sql = str_replace('%{user}', $this->_escape($user), $sql); $this->_modifyDB($sql); + $this->_flushUserInfoCache($user); + return true; + } + } + return false; + } + + /** + * Flush cached user information + * + * @author Christopher Smith + * + * @param string $user username of the user whose data is to be removed from the cache + * if null, empty the whole cache + * @return none + */ + protected function _flushUserInfoCache($user=null) { + if (is_null($user)) { + $this->cacheUserInfo = array(); + } else { + unset($this->cacheUserInfo[$user]); + } + } + + /** + * Quick lookup to see if a user's information has been cached + * + * This test does not need a database connection or read lock + * + * @author Christopher Smith + * + * @param string $user username to be looked up in the cache + * @param bool $requireGroups true, if cached info should include group memberships + * + * @return bool existence of required user information in the cache + */ + protected function _cacheExists($user, $requireGroups=true) { + if (isset($this->cacheUserInfo[$user])) { + if (!is_array($this->cacheUserInfo[$user])) { + return true; // user doesn't exist + } + + if (!$requireGroups || isset($this->cacheUserInfo[$user]['grps'])) { return true; } } + return false; } /** - * getUserInfo + * Get a user's information + * + * The database connection must already be established for this function to work. + * + * @author Christopher Smith + * + * @param string $user username of the user whose information is being reterieved + * @param bool $requireGroups true if group memberships should be included + * @param bool $useCache true if ok to return cached data & to cache returned data + * + * @return mixed false|array false if the user doesn't exist + * array containing user information if user does exist + */ + protected function _getUserInfo($user, $requireGroups=true, $useCache=true) { + $info = null; + + if ($useCache && isset($this->cacheUserInfo[$user])) { + $info = $this->cacheUserInfo[$user]; + } + + if (is_null($info)) { + $info = $this->_retrieveUserInfo($user); + } + + if ($requireGroups && $info && !isset($info['grps'])) { + $info['grps'] = $this->_getGroups($user); + } + + if ($useCache) { + $this->cacheUserInfo[$user] = $info; + } + + return $info; + } + + /** + * retrieveUserInfo * - * Gets the data for a specific user The database connection + * Gets the data for a specific user. The database connection * must already be established for this function to work. * Otherwise it will return 'false'. * @@ -644,12 +743,11 @@ class auth_plugin_authmysql extends DokuWiki_Auth_Plugin { * @param string $user user's nick to get data for * @return bool|array false on error, user info on success */ - protected function _getUserInfo($user) { + protected function _retrieveUserInfo($user) { $sql = str_replace('%{user}', $this->_escape($user), $this->getConf('getUserInfo')); $result = $this->_queryDB($sql); if($result !== false && count($result)) { $info = $result[0]; - $info['grps'] = $this->_getGroups($user); return $info; } return false; @@ -666,20 +764,26 @@ class auth_plugin_authmysql extends DokuWiki_Auth_Plugin { * The database connection has already to be established for this * function to work. Otherwise it will return 'false'. * - * The password will be crypted if necessary. + * The password will be encrypted if necessary. * + * @param string $user user's nick being updated * @param array $changes array of items to change as pairs of item and value * @param mixed $uid user id of dataset to change, must be unique in DB * @return bool true on success or false on error * * @author Matthias Grimm */ - protected function _updateUserInfo($changes, $uid) { + protected function _updateUserInfo($user, $changes) { $sql = $this->getConf('updateUser')." "; $cnt = 0; $err = 0; if($this->dbcon) { + $uid = $this->_getUserID($user); + if ($uid === false) { + return false; + } + foreach($changes as $item => $value) { if($item == 'user') { if(($this->_getUserID($changes['user']))) { @@ -707,6 +811,7 @@ class auth_plugin_authmysql extends DokuWiki_Auth_Plugin { $sql .= " ".str_replace('%{uid}', $uid, $this->getConf('UpdateTarget')); if(get_class($this) == 'auth_mysql') $sql .= " LIMIT 1"; //some PgSQL inheritance comp. $this->_modifyDB($sql); + $this->_flushUserInfoCache($user); } return true; } diff --git a/lib/plugins/authpgsql/auth.php b/lib/plugins/authpgsql/auth.php index e51b39858..99f3ed443 100644 --- a/lib/plugins/authpgsql/auth.php +++ b/lib/plugins/authpgsql/auth.php @@ -160,7 +160,7 @@ class auth_plugin_authpgsql extends auth_plugin_authmysql { $result = $this->_queryDB($sql); foreach($result as $user) - if(($info = $this->_getUserInfo($user['user']))) + if(($info = $this->_getCachedUserInfo($user['user']))) $out[$user['user']] = $info; $this->_unlockTables(); @@ -212,7 +212,10 @@ class auth_plugin_authpgsql extends auth_plugin_authmysql { $sql = str_replace('%{user}', addslashes($user), $sql); $sql = str_replace('%{gid}', addslashes($gid), $sql); $sql = str_replace('%{group}', addslashes($group), $sql); - if($this->_modifyDB($sql) !== false) return true; + if($this->_modifyDB($sql) !== false) { + $this->_flushUserInfoCache($user); + return true; + } if($newgroup) { // remove previously created group on error $sql = str_replace('%{gid}', addslashes($gid), $this->conf['delGroup']); @@ -267,6 +270,7 @@ class auth_plugin_authpgsql extends auth_plugin_authmysql { } if($gid !== false){ + $this->_flushUserInfoCache($user); return true; } else { /* remove the new user and all group relations if a group can't diff --git a/lib/plugins/authplain/auth.php b/lib/plugins/authplain/auth.php index 8c4ce0dd9..16ecaa147 100644 --- a/lib/plugins/authplain/auth.php +++ b/lib/plugins/authplain/auth.php @@ -76,9 +76,10 @@ class auth_plugin_authplain extends DokuWiki_Auth_Plugin { * * @author Andreas Gohr * @param string $user + * @param bool $requireGroups (optional) ignored by this plugin, grps info always supplied * @return array|bool */ - public function getUserData($user) { + public function getUserData($user, $requireGroups=true) { if($this->users === null) $this->_loadUserData(); return isset($this->users[$user]) ? $this->users[$user] : false; } -- cgit v1.2.3 From 3fb3173756af152604d5d7bb3f1713a95eab5b52 Mon Sep 17 00:00:00 2001 From: Christopher Smith Date: Wed, 12 Mar 2014 17:56:55 +0000 Subject: code styling - add missing braces --- lib/plugins/authmysql/auth.php | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) (limited to 'lib') diff --git a/lib/plugins/authmysql/auth.php b/lib/plugins/authmysql/auth.php index 774fce01f..a5300c604 100644 --- a/lib/plugins/authmysql/auth.php +++ b/lib/plugins/authmysql/auth.php @@ -160,10 +160,11 @@ class auth_plugin_authmysql extends DokuWiki_Auth_Plugin { $result = $this->_queryDB($sql); if($result !== false && count($result) == 1) { - if($this->getConf('forwardClearPass') == 1) + if($this->getConf('forwardClearPass') == 1) { $rc = true; - else + } else { $rc = auth_verifyPassword($pass, $result[0]['pass']); + } } $this->_closeDB(); } @@ -191,8 +192,9 @@ class auth_plugin_authmysql extends DokuWiki_Auth_Plugin { $info = $this->_getUserInfo($user, $requireGroups); $this->_unlockTables(); $this->_closeDB(); - } else + } else { $info = false; + } return $info; } @@ -218,12 +220,14 @@ class auth_plugin_authmysql extends DokuWiki_Auth_Plugin { global $conf; if($this->_openDB()) { - if(($info = $this->_getUserInfo($user)) !== false) + if(($info = $this->_getUserInfo($user)) !== false) { return false; // user already exists + } // set defaultgroup if no groups were given - if($grps == null) + if($grps == null) { $grps = array($conf['defaultgroup']); + } $this->_lockTables("WRITE"); $pwd = $this->getConf('forwardClearPass') ? $pwd : auth_cryptPassword($pwd); @@ -265,8 +269,9 @@ class auth_plugin_authmysql extends DokuWiki_Auth_Plugin { public function modifyUser($user, $changes) { $rc = false; - if(!is_array($changes) || !count($changes)) + if(!is_array($changes) || !count($changes)) { return true; // nothing to change + } if($this->_openDB()) { $this->_lockTables("WRITE"); @@ -315,8 +320,9 @@ class auth_plugin_authmysql extends DokuWiki_Auth_Plugin { if(is_array($users) && count($users)) { $this->_lockTables("WRITE"); foreach($users as $user) { - if($this->_delUser($user)) + if($this->_delUser($user)) { $count++; + } } $this->_unlockTables(); } @@ -378,9 +384,11 @@ class auth_plugin_authmysql extends DokuWiki_Auth_Plugin { $result = $this->_queryDB($sql); if(!empty($result)) { - foreach($result as $user) - if(($info = $this->_getUserInfo($user['user']))) + foreach($result as $user) { + if(($info = $this->_getUserInfo($user['user']))) { $out[$user['user']] = $info; + } + } } $this->_unlockTables(); @@ -544,8 +552,9 @@ class auth_plugin_authmysql extends DokuWiki_Auth_Plugin { $result = $this->_queryDB($sql); if($result !== false && count($result)) { - foreach($result as $row) + foreach($result as $row) { $groups[] = $row['group']; + } } return $groups; } -- cgit v1.2.3 From 792883c4aaba64146ea38cd62287c96cb8121c1f Mon Sep 17 00:00:00 2001 From: Christopher Smith Date: Wed, 12 Mar 2014 17:57:34 +0000 Subject: fix comment errors, sp. & grammar --- lib/plugins/authmysql/auth.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'lib') diff --git a/lib/plugins/authmysql/auth.php b/lib/plugins/authmysql/auth.php index a5300c604..0ddbff99b 100644 --- a/lib/plugins/authmysql/auth.php +++ b/lib/plugins/authmysql/auth.php @@ -247,17 +247,17 @@ class auth_plugin_authmysql extends DokuWiki_Auth_Plugin { * The dataset update will be rejected if the user name should be changed * to an already existing one. * - * The password must be provides unencrypted. Pasword cryption is done + * The password must be provided unencrypted. Pasword encryption is done * automatically if configured. * - * If one or more groups could't be updated, an error would be set. In + * If one or more groups can't be updated, an error will be set. In * this case the dataset might already be changed and we can't rollback - * the changes. Transactions would be really usefull here. + * the changes. Transactions would be really useful here. * * modifyUser() may be called without SQL statements defined that are * needed to change group membership (for example if only the user profile - * should be modified). In this case we asure that we don't touch groups - * even $changes['grps'] is set by mistake. + * should be modified). In this case we assure that we don't touch groups + * even when $changes['grps'] is set by mistake. * * @author Chris Smith * @author Matthias Grimm @@ -642,7 +642,7 @@ class auth_plugin_authmysql extends DokuWiki_Auth_Plugin { * * @author Matthias Grimm * - * @param string $user user whose id is desired + * @param string $user username of the user to be deleted * @return bool */ protected function _delUser($user) { -- cgit v1.2.3 From 06e3e0c7b506a637df1ea27c6a8a439756e7139d Mon Sep 17 00:00:00 2001 From: Christopher Smith Date: Fri, 14 Mar 2014 17:58:53 +0000 Subject: use $requireGroups constants in auth classes; comments; code improvements --- lib/plugins/auth.php | 4 ++-- lib/plugins/authmysql/auth.php | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) (limited to 'lib') diff --git a/lib/plugins/auth.php b/lib/plugins/auth.php index f9e5cded9..9275611be 100644 --- a/lib/plugins/auth.php +++ b/lib/plugins/auth.php @@ -232,7 +232,7 @@ class DokuWiki_Auth_Plugin extends DokuWiki_Plugin { * at least these fields: * * name string full name of the user - * mail string email addres of the user + * mail string email address of the user * grps array list of groups the user is in * * @author Andreas Gohr @@ -240,7 +240,7 @@ class DokuWiki_Auth_Plugin extends DokuWiki_Plugin { * @param bool $requireGroups whether or not the returned data must include groups * @return array containing user data or false */ - public function getUserData($user, $requireGroups=true) { + public function getUserData($user, $requireGroups=self::REQUIRE_GROUPS) { if(!$this->cando['external']) msg("no valid authorisation system in use", -1); return false; } diff --git a/lib/plugins/authmysql/auth.php b/lib/plugins/authmysql/auth.php index 0ddbff99b..d3906759b 100644 --- a/lib/plugins/authmysql/auth.php +++ b/lib/plugins/authmysql/auth.php @@ -182,7 +182,7 @@ class auth_plugin_authmysql extends DokuWiki_Auth_Plugin { * when false, it maybe included, but is not required by the caller * @return array|bool */ - public function getUserData($user, $requireGroups=true) { + public function getUserData($user, $requireGroups=DokuWiki_Auth_Plugin::REQUIRE_GROUPS) { if($this->_cacheExists($user, $requireGroups)) { return $this->cacheUserInfo[$user]; } @@ -690,13 +690,13 @@ class auth_plugin_authmysql extends DokuWiki_Auth_Plugin { * * @return bool existence of required user information in the cache */ - protected function _cacheExists($user, $requireGroups=true) { + protected function _cacheExists($user, $requireGroups=DokuWiki_Auth_Plugin::REQUIRE_GROUPS) { if (isset($this->cacheUserInfo[$user])) { if (!is_array($this->cacheUserInfo[$user])) { return true; // user doesn't exist } - if (!$requireGroups || isset($this->cacheUserInfo[$user]['grps'])) { + if ($requireGroups == DokuWiki_Auth_Plugin::IGNORE_GROUPS || isset($this->cacheUserInfo[$user]['grps'])) { return true; } } @@ -718,7 +718,7 @@ class auth_plugin_authmysql extends DokuWiki_Auth_Plugin { * @return mixed false|array false if the user doesn't exist * array containing user information if user does exist */ - protected function _getUserInfo($user, $requireGroups=true, $useCache=true) { + protected function _getUserInfo($user, $requireGroups=DokuWiki_Auth_Plugin::REQUIRE_GROUPS, $useCache=true) { $info = null; if ($useCache && isset($this->cacheUserInfo[$user])) { @@ -729,7 +729,7 @@ class auth_plugin_authmysql extends DokuWiki_Auth_Plugin { $info = $this->_retrieveUserInfo($user); } - if ($requireGroups && $info && !isset($info['grps'])) { + if (($requireGroups == DokuWiki_Auth_Plugin::REQUIRE_GROUPS) && $info && !isset($info['grps'])) { $info['grps'] = $this->_getGroups($user); } -- cgit v1.2.3 From 2dc9e90007f12ac996b0e74479137a9dc6243c3c Mon Sep 17 00:00:00 2001 From: Christopher Smith Date: Sun, 4 May 2014 19:20:11 +0100 Subject: KISS - remove class constants for REQUIRE_GROUPS & IGNORE_GROUPS and replace with boolean values --- lib/plugins/auth.php | 5 +---- lib/plugins/authmysql/auth.php | 10 +++++----- 2 files changed, 6 insertions(+), 9 deletions(-) (limited to 'lib') diff --git a/lib/plugins/auth.php b/lib/plugins/auth.php index 9275611be..b38b591a3 100644 --- a/lib/plugins/auth.php +++ b/lib/plugins/auth.php @@ -15,9 +15,6 @@ if(!defined('DOKU_INC')) die(); class DokuWiki_Auth_Plugin extends DokuWiki_Plugin { public $success = true; - const IGNORE_GROUPS = false; - const REQUIRE_GROUPS = true; - /** * Possible things an auth backend module may be able to * do. The things a backend can do need to be set to true @@ -240,7 +237,7 @@ class DokuWiki_Auth_Plugin extends DokuWiki_Plugin { * @param bool $requireGroups whether or not the returned data must include groups * @return array containing user data or false */ - public function getUserData($user, $requireGroups=self::REQUIRE_GROUPS) { + public function getUserData($user, $requireGroups=true) { if(!$this->cando['external']) msg("no valid authorisation system in use", -1); return false; } diff --git a/lib/plugins/authmysql/auth.php b/lib/plugins/authmysql/auth.php index d3906759b..95c62f636 100644 --- a/lib/plugins/authmysql/auth.php +++ b/lib/plugins/authmysql/auth.php @@ -182,7 +182,7 @@ class auth_plugin_authmysql extends DokuWiki_Auth_Plugin { * when false, it maybe included, but is not required by the caller * @return array|bool */ - public function getUserData($user, $requireGroups=DokuWiki_Auth_Plugin::REQUIRE_GROUPS) { + public function getUserData($user, $requireGroups=true) { if($this->_cacheExists($user, $requireGroups)) { return $this->cacheUserInfo[$user]; } @@ -690,13 +690,13 @@ class auth_plugin_authmysql extends DokuWiki_Auth_Plugin { * * @return bool existence of required user information in the cache */ - protected function _cacheExists($user, $requireGroups=DokuWiki_Auth_Plugin::REQUIRE_GROUPS) { + protected function _cacheExists($user, $requireGroups=true) { if (isset($this->cacheUserInfo[$user])) { if (!is_array($this->cacheUserInfo[$user])) { return true; // user doesn't exist } - if ($requireGroups == DokuWiki_Auth_Plugin::IGNORE_GROUPS || isset($this->cacheUserInfo[$user]['grps'])) { + if (!$requireGroups || isset($this->cacheUserInfo[$user]['grps'])) { return true; } } @@ -718,7 +718,7 @@ class auth_plugin_authmysql extends DokuWiki_Auth_Plugin { * @return mixed false|array false if the user doesn't exist * array containing user information if user does exist */ - protected function _getUserInfo($user, $requireGroups=DokuWiki_Auth_Plugin::REQUIRE_GROUPS, $useCache=true) { + protected function _getUserInfo($user, $requireGroups=true, $useCache=true) { $info = null; if ($useCache && isset($this->cacheUserInfo[$user])) { @@ -729,7 +729,7 @@ class auth_plugin_authmysql extends DokuWiki_Auth_Plugin { $info = $this->_retrieveUserInfo($user); } - if (($requireGroups == DokuWiki_Auth_Plugin::REQUIRE_GROUPS) && $info && !isset($info['grps'])) { + if (($requireGroups == true) && $info && !isset($info['grps'])) { $info['grps'] = $this->_getGroups($user); } -- cgit v1.2.3 From 06da270e039cf517a6bd847ca0cd4a7819c9f879 Mon Sep 17 00:00:00 2001 From: Axel Angel Date: Sun, 4 May 2014 11:46:35 +0200 Subject: Authldap: implement change password in modifyUser --- lib/plugins/authldap/auth.php | 55 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/plugins/authldap/auth.php b/lib/plugins/authldap/auth.php index 6c3637e15..13ffb8be2 100644 --- a/lib/plugins/authldap/auth.php +++ b/lib/plugins/authldap/auth.php @@ -36,8 +36,8 @@ class auth_plugin_authldap extends DokuWiki_Auth_Plugin { return; } - // auth_ldap currently just handles authentication, so no - // capabilities are set + // Add the capabilities to change the password + $this->cando['modPass'] = true; } /** @@ -263,6 +263,57 @@ class auth_plugin_authldap extends DokuWiki_Auth_Plugin { return $info; } + /** + * Definition of the function modifyUser in order to modify the password + */ + + function modifyUser($user,$changes){ + + // open the connection to the ldap + if(!$this->_openLDAP()){ + msg('LDAP cannot connect: '. htmlspecialchars(ldap_error($this->con))); + return false; + } + + // find the information about the user, in particular the "dn" + $info = $this->getUserData($user,true); + if(empty($info['dn'])) { + msg('LDAP cannot find your user dn: '. htmlspecialchars($info['dn'])); + return false; + } else { + $dn = $info['dn']; + } + + // find the new password and encrypt it whit SSHA + if(empty($changes['pass'])) { + msg('The new password is not allow because it\'s empty'); + return false; + } else { + mt_srand((double)microtime()*1000000); + $salt = pack("CCCC", mt_rand(), mt_rand(), mt_rand(), mt_rand()); + $hash = "{SSHA}" . base64_encode(pack("H*", sha1($changes['pass'] . $salt)) . $salt); + } + + // find the old password of the user + list($loginuser,$loginsticky,$loginpass) = auth_getCookie(); + $secret = auth_cookiesalt(!$sticky, true); //bind non-sticky to session + $pass = auth_decrypt($loginpass, $secret); + + // bind with the ldap + if(!@ldap_bind($this->con,$dn,$pass)){ + msg('LDAP user bind failed: '. htmlspecialchars($dn) .': '.htmlspecialchars(ldap_error($this->con)), 0, __LINE__, __FILE__); + return false; + } + + // change the password + if(!@ldap_mod_replace($this->con, $dn,array('userpassword' => $hash))){ + msg('LDAP mod replace failed: '. htmlspecialchars($dn) .': '.htmlspecialchars(ldap_error($this->con))); + return false; + } + + return true; + } + /** * Most values in LDAP are case-insensitive * -- cgit v1.2.3 From 719c6730c7da93e830205e42dc230de831446e8f Mon Sep 17 00:00:00 2001 From: Axel Angel Date: Sun, 4 May 2014 12:26:13 +0200 Subject: Allow authldap to change password with ldap superuser only if necessary --- lib/plugins/authldap/auth.php | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) (limited to 'lib') diff --git a/lib/plugins/authldap/auth.php b/lib/plugins/authldap/auth.php index 13ffb8be2..5bdaf0446 100644 --- a/lib/plugins/authldap/auth.php +++ b/lib/plugins/authldap/auth.php @@ -296,13 +296,25 @@ class auth_plugin_authldap extends DokuWiki_Auth_Plugin { // find the old password of the user list($loginuser,$loginsticky,$loginpass) = auth_getCookie(); - $secret = auth_cookiesalt(!$sticky, true); //bind non-sticky to session - $pass = auth_decrypt($loginpass, $secret); + if ($loginuser !== null) { // the user is currently logged in + $secret = auth_cookiesalt(!$sticky, true); + $pass = auth_decrypt($loginpass, $secret); - // bind with the ldap - if(!@ldap_bind($this->con,$dn,$pass)){ - msg('LDAP user bind failed: '. htmlspecialchars($dn) .': '.htmlspecialchars(ldap_error($this->con)), 0, __LINE__, __FILE__); - return false; + // bind with the ldap + if(!@ldap_bind($this->con, $dn, $pass)){ + msg('LDAP user bind failed: '. htmlspecialchars($dn) .': '.htmlspecialchars(ldap_error($this->con)), 0, __LINE__, __FILE__); + return false; + } + } elseif ($this->getConf('binddn') && $this->getConf('bindpw')) { + // we are changing the password on behalf of the user (eg: forgotten password) + // bind with the superuser ldap + if (!@ldap_bind($this->con, $this->getConf('binddn'), $this->getConf('bindpw'))){ + $this->_debug('LDAP bind as superuser: '.htmlspecialchars(ldap_error($this->con)), 0, __LINE__, __FILE__); + return false; + } + } + else { + return false; // no otherway } // change the password -- cgit v1.2.3 From 67723447f02824ff2df7daa0f1f97d8b289c5d7a Mon Sep 17 00:00:00 2001 From: Axel Angel Date: Sun, 4 May 2014 19:54:37 +0200 Subject: Hash and salt password with PassHash::ssha Moved the block closer to the variable use (indent clearer) --- lib/plugins/authldap/auth.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/plugins/authldap/auth.php b/lib/plugins/authldap/auth.php index 5bdaf0446..ecbbc2a3a 100644 --- a/lib/plugins/authldap/auth.php +++ b/lib/plugins/authldap/auth.php @@ -288,10 +288,6 @@ class auth_plugin_authldap extends DokuWiki_Auth_Plugin { if(empty($changes['pass'])) { msg('The new password is not allow because it\'s empty'); return false; - } else { - mt_srand((double)microtime()*1000000); - $salt = pack("CCCC", mt_rand(), mt_rand(), mt_rand(), mt_rand()); - $hash = "{SSHA}" . base64_encode(pack("H*", sha1($changes['pass'] . $salt)) . $salt); } // find the old password of the user @@ -317,6 +313,10 @@ class auth_plugin_authldap extends DokuWiki_Auth_Plugin { return false; // no otherway } + // Generate the salted hashed password for LDAP + $phash = new PassHash(); + $hash = $phash->hash_ssha($changes['pass']); + // change the password if(!@ldap_mod_replace($this->con, $dn,array('userpassword' => $hash))){ msg('LDAP mod replace failed: '. htmlspecialchars($dn) .': '.htmlspecialchars(ldap_error($this->con))); -- cgit v1.2.3 From 8f2ea93bb09b8744de56a8797176d3a209c2e8d7 Mon Sep 17 00:00:00 2001 From: Axel Angel Date: Thu, 8 May 2014 12:19:39 +0200 Subject: Simplify code and remove unreachable check --- lib/plugins/authldap/auth.php | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) (limited to 'lib') diff --git a/lib/plugins/authldap/auth.php b/lib/plugins/authldap/auth.php index ecbbc2a3a..bda8f2abe 100644 --- a/lib/plugins/authldap/auth.php +++ b/lib/plugins/authldap/auth.php @@ -278,17 +278,10 @@ class auth_plugin_authldap extends DokuWiki_Auth_Plugin { // find the information about the user, in particular the "dn" $info = $this->getUserData($user,true); if(empty($info['dn'])) { - msg('LDAP cannot find your user dn: '. htmlspecialchars($info['dn'])); - return false; - } else { - $dn = $info['dn']; - } - - // find the new password and encrypt it whit SSHA - if(empty($changes['pass'])) { - msg('The new password is not allow because it\'s empty'); + msg('LDAP cannot find your user dn'); return false; } + $dn = $info['dn']; // find the old password of the user list($loginuser,$loginsticky,$loginpass) = auth_getCookie(); -- cgit v1.2.3 From bd0092a4db2ae72d5c564d383810c0b65c9b9ece Mon Sep 17 00:00:00 2001 From: Rene Date: Thu, 8 May 2014 13:11:03 +0200 Subject: translation update --- lib/plugins/extension/lang/nl/intro_search.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/plugins/extension/lang/nl/intro_search.txt b/lib/plugins/extension/lang/nl/intro_search.txt index 8fc3900ad..f0c8d7435 100644 --- a/lib/plugins/extension/lang/nl/intro_search.txt +++ b/lib/plugins/extension/lang/nl/intro_search.txt @@ -1 +1 @@ -Deze tab verschaft u toegang tot alle plugins en templates vervaardigd door derden en bestemd voor Dokuwiki. Houdt er rekening meel dat indien u Plugins van derden installeerd deze een **veiligheids risico ** kunnen bevatten, geadviseerd wordt om eerst te lezen [[doku>security#plugin_security|plugin security]]. \ No newline at end of file +Deze tab verschaft u toegang tot alle plugins en templates vervaardigd door derden en bestemd voor Dokuwiki. Houdt er rekening mee dat indien u Plugins van derden installeert deze een **veiligheids risico ** kunnen bevatten, geadviseerd wordt om eerst te lezen [[doku>security#plugin_security|plugin security]]. \ No newline at end of file -- cgit v1.2.3 From 90bedf259b8cdbaf36db4b77301e271d0865c642 Mon Sep 17 00:00:00 2001 From: Rene Date: Fri, 9 May 2014 21:22:20 +0200 Subject: translation update --- lib/plugins/extension/lang/nl/intro_plugins.txt | 2 +- lib/plugins/extension/lang/nl/intro_templates.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/plugins/extension/lang/nl/intro_plugins.txt b/lib/plugins/extension/lang/nl/intro_plugins.txt index 0077aca30..e12bdf0f8 100644 --- a/lib/plugins/extension/lang/nl/intro_plugins.txt +++ b/lib/plugins/extension/lang/nl/intro_plugins.txt @@ -1 +1 @@ -Dit zijn de momenteel in uw Dokuwiki geïnstalleerde plugins. U kunt deze hier aan of uitschakelen danwel geheel deïnstalleren. Plugin updates zijn hier ook opgenomen, lees de pluin documentatie voordat u update. \ No newline at end of file +Dit zijn de momenteel in uw Dokuwiki geïnstalleerde plugins. U kunt deze hier aan of uitschakelen danwel geheel deïnstalleren. Plugin updates zijn hier ook opgenomen, lees de plugin documentatie voordat u update. \ No newline at end of file diff --git a/lib/plugins/extension/lang/nl/intro_templates.txt b/lib/plugins/extension/lang/nl/intro_templates.txt index 5ef23dadf..52c96cef7 100644 --- a/lib/plugins/extension/lang/nl/intro_templates.txt +++ b/lib/plugins/extension/lang/nl/intro_templates.txt @@ -1 +1 @@ -Deze templates zijn thans in DokuWiki geïnstalleerd. U kent een template selecteren middels [[?do=admin&page=config|Configuration Manager]] . \ No newline at end of file +Deze templates zijn thans in DokuWiki geïnstalleerd. U kunt een template selecteren middels [[?do=admin&page=config|Configuration Manager]] . \ No newline at end of file -- cgit v1.2.3 From 436a2a19870a5c73575f574040be44d333f55409 Mon Sep 17 00:00:00 2001 From: Hideaki SAWADA Date: Sat, 10 May 2014 10:21:16 +0200 Subject: translation update --- lib/plugins/extension/lang/ja/lang.php | 39 ++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/plugins/extension/lang/ja/lang.php b/lib/plugins/extension/lang/ja/lang.php index 0401d7630..b42e4aefd 100644 --- a/lib/plugins/extension/lang/ja/lang.php +++ b/lib/plugins/extension/lang/ja/lang.php @@ -8,6 +8,7 @@ $lang['menu'] = '拡張機能管理'; $lang['tab_plugins'] = 'インストール済プラグイン'; $lang['tab_templates'] = 'インストール済テンプレート'; +$lang['tab_search'] = '検索とインストール'; $lang['tab_install'] = '手動インストール'; $lang['notimplemented'] = 'この機能は未実装です。'; $lang['notinstalled'] = 'この拡張機能はインストールされていません。'; @@ -17,17 +18,38 @@ $lang['pluginlistsaveerror'] = 'プラグイン一覧の保存中にエラー $lang['unknownauthor'] = '作者不明'; $lang['unknownversion'] = 'バージョン不明'; $lang['btn_info'] = '詳細情報を表示する。'; -$lang['btn_update'] = 'アップデート'; +$lang['btn_update'] = '更新'; $lang['btn_uninstall'] = 'アンインストール'; $lang['btn_enable'] = '有効化'; $lang['btn_disable'] = '無効化'; $lang['btn_install'] = 'インストール'; $lang['btn_reinstall'] = '再インストール'; $lang['js']['reallydel'] = 'この拡張機能を本当にアンインストールしますか?'; +$lang['search_for'] = '拡張機能の検索:'; +$lang['search'] = '検索'; +$lang['extensionby'] = '%s 作者: %s'; +$lang['screenshot'] = '%s のスクリーンショット'; +$lang['popularity'] = '利用状況:%s%%'; +$lang['homepage_link'] = '解説'; +$lang['bugs_features'] = 'バグ'; +$lang['tags'] = 'タグ:'; +$lang['author_hint'] = 'この作者で拡張機能を検索'; +$lang['installed'] = 'インストール済:'; $lang['downloadurl'] = 'ダウンロード URL:'; $lang['repository'] = 'リポジトリ:'; +$lang['unknown'] = '不明'; +$lang['installed_version'] = 'インストール済バージョン:'; +$lang['install_date'] = '最終更新日:'; +$lang['available_version'] = '利用可能バージョン:'; +$lang['compatible'] = '互換:'; $lang['depends'] = '依存:'; $lang['similar'] = '類似:'; +$lang['conflicts'] = '競合:'; +$lang['donate'] = 'お気に入り?'; +$lang['donate_action'] = '寄付先'; +$lang['repo_retry'] = '再実行'; +$lang['provides'] = '提供:'; +$lang['status'] = '状態:'; $lang['status_installed'] = 'インストール済'; $lang['status_not_installed'] = '未インストール'; $lang['status_enabled'] = '有効'; @@ -39,16 +61,25 @@ $lang['msg_enabled'] = '%s プラグインを有効化しました。' $lang['msg_disabled'] = '%s プラグインを無効化しました。'; $lang['msg_delete_success'] = '拡張機能をアンインストールしました。'; $lang['msg_template_install_success'] = '%s テンプレートをインストールできました。'; -$lang['msg_template_update_success'] = '%s テンプレートをアップデートできました。'; +$lang['msg_template_update_success'] = '%s テンプレートを更新できました。'; $lang['msg_plugin_install_success'] = '%s プラグインをインストールできました。'; -$lang['msg_plugin_update_success'] = '%s プラグインをアップデートできました。'; +$lang['msg_plugin_update_success'] = '%s プラグインを更新できました。'; $lang['msg_upload_failed'] = 'ファイルのアップロードに失敗しました。'; +$lang['missing_dependency'] = '依存関係が欠落または無効: %s'; $lang['security_issue'] = 'セキュリティ問題: %s'; $lang['security_warning'] = 'セキュリティ警告: %s'; -$lang['update_available'] = 'アップデート:%sの新バージョンが利用可能です。 '; +$lang['update_available'] = '更新: %sの新バージョンが利用可能です。'; +$lang['wrong_folder'] = 'プラグインは正しくインストールされませんでした: プラグインのディレクトリを "%s" から "%s" へ変更して下さい。'; +$lang['url_change'] = 'URL が変更されました: 最後にダウンロードした後、ダウンロード URL が変更されました。拡張機能のアップデート前に新 URL が正しいかを確認して下さい。
新:%s
旧:%s'; $lang['error_badurl'] = 'URLはhttpかhttpsで始まる必要があります。'; $lang['error_dircreate'] = 'ダウンロード用の一時フォルダが作成できません。'; $lang['error_download'] = 'ファイルをダウンロードできません:%s'; +$lang['error_decompress'] = 'ダウンロードしたファイルを解凍できません。ダウンロードの失敗の結果であれば、再度試して下さい。圧縮形式が不明の場合は、手動でダウンロード・インストールしてください。'; +$lang['error_findfolder'] = '拡張機能ディレクトリを認識できません。手動でダウンロード・インストールしてください。'; +$lang['error_copy'] = '%s ディレクトリのファイルをインストールしようとした時、ファイルコピーエラーが発生しました:ディスクがいっぱいかもしれませんし、ファイルのアクセス権が正しくないかもしれません。プラグインが一部分インストールされ、wiki が不安定になるかもしれません。'; $lang['noperms'] = '拡張機能ディレクトリが書き込み不可です。'; $lang['notplperms'] = 'テンプレートディレクトリが書き込み不可です。'; $lang['nopluginperms'] = 'プラグインディレクトリが書き込み不可です。'; +$lang['git'] = 'この拡張機能は Git 経由でインストールされており、ここで更新すべきでないかもしれません。'; +$lang['install_url'] = 'URL からインストール:'; +$lang['install_upload'] = '拡張機能をアップロード:'; -- cgit v1.2.3 From bc34cd3821f87b31d4259fe33c4c853eb1245df8 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Sat, 10 May 2014 14:33:51 +0200 Subject: added missing translation string in extension manager --- lib/plugins/extension/lang/en/lang.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/plugins/extension/lang/en/lang.php b/lib/plugins/extension/lang/en/lang.php index 5224f694a..72c9b9e2d 100644 --- a/lib/plugins/extension/lang/en/lang.php +++ b/lib/plugins/extension/lang/en/lang.php @@ -96,4 +96,6 @@ $lang['nopluginperms'] = 'Plugin directory is not writable'; $lang['git'] = 'This extension was installed via git, you may not want to update it here.'; $lang['install_url'] = 'Install from URL:'; -$lang['install_upload'] = 'Upload Extension:'; \ No newline at end of file +$lang['install_upload'] = 'Upload Extension:'; + +$lang['repo_error'] = 'The plugin repository could not be contacted. Make sure your server is allowed to contact www.dokuwiki.org and check your proxy settings.'; \ No newline at end of file -- cgit v1.2.3 From 52ab27d83d0fa4f6be37eb2d4d58d6c51b76e66f Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Sat, 10 May 2014 14:43:50 +0200 Subject: corrected german translation --- lib/plugins/extension/lang/de/lang.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/plugins/extension/lang/de/lang.php b/lib/plugins/extension/lang/de/lang.php index 89c6b55b7..b0e3b4ff7 100644 --- a/lib/plugins/extension/lang/de/lang.php +++ b/lib/plugins/extension/lang/de/lang.php @@ -85,6 +85,6 @@ $lang['error_copy'] = 'Beim Versuch Dateien in den Ordner %s $lang['noperms'] = 'Das Erweiterungs-Verzeichnis ist schreibgeschützt'; $lang['notplperms'] = 'Das Template-Verzeichnis ist schreibgeschützt'; $lang['nopluginperms'] = 'Das Plugin-Verzeichnis ist schreibgeschützt'; -$lang['git'] = 'Diese Erweiterung wurde über git installiert, daher kann diese nicht hier aktualisiert werden.'; +$lang['git'] = 'Diese Erweiterung wurde über git installiert und sollte daher nicht hier aktualisiert werden.'; $lang['install_url'] = 'Von Webadresse (URL) installieren'; $lang['install_upload'] = 'Erweiterung hochladen:'; -- cgit v1.2.3 From 811653d71565cce94ca1b27f4cdebc60769e2d11 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Sat, 10 May 2014 15:18:20 +0200 Subject: fix icon for plugins following templates in extension manager --- lib/plugins/extension/helper/extension.php | 2 ++ 1 file changed, 2 insertions(+) (limited to 'lib') diff --git a/lib/plugins/extension/helper/extension.php b/lib/plugins/extension/helper/extension.php index 7958cd2da..43e4452c0 100644 --- a/lib/plugins/extension/helper/extension.php +++ b/lib/plugins/extension/helper/extension.php @@ -57,6 +57,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { if(substr($id, 0 , 9) == 'template:'){ $this->base = substr($id, 9); $this->is_template = true; + } else { + $this->is_template = false; } $this->localInfo = array(); -- cgit v1.2.3 From 227a9832a9d65a12d88704e2bf18db63dae1ad57 Mon Sep 17 00:00:00 2001 From: lainme Date: Tue, 13 May 2014 06:36:05 +0200 Subject: translation update --- lib/plugins/authad/lang/zh/lang.php | 8 ++++++++ lib/plugins/extension/lang/zh/lang.php | 19 +++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 lib/plugins/authad/lang/zh/lang.php (limited to 'lib') diff --git a/lib/plugins/authad/lang/zh/lang.php b/lib/plugins/authad/lang/zh/lang.php new file mode 100644 index 000000000..2a05aa168 --- /dev/null +++ b/lib/plugins/authad/lang/zh/lang.php @@ -0,0 +1,8 @@ + + */ +$lang['domain'] = '登录域'; diff --git a/lib/plugins/extension/lang/zh/lang.php b/lib/plugins/extension/lang/zh/lang.php index 4d40acc1d..0264f3e9c 100644 --- a/lib/plugins/extension/lang/zh/lang.php +++ b/lib/plugins/extension/lang/zh/lang.php @@ -6,6 +6,7 @@ * @author Cupen * @author xiqingongzi * @author qinghao + * @author lainme */ $lang['menu'] = '扩展管理器'; $lang['tab_plugins'] = '安装插件'; @@ -42,6 +43,7 @@ $lang['repository'] = '版本库:'; $lang['unknown'] = '未知的'; $lang['installed_version'] = '已安装版本:'; $lang['install_date'] = '您的最后一次升级:'; +$lang['available_version'] = '可用版本:'; $lang['compatible'] = '兼容于:'; $lang['depends'] = '依赖于:'; $lang['similar'] = '相似于:'; @@ -59,6 +61,7 @@ $lang['status_disabled'] = '禁用'; $lang['status_unmodifiable'] = '不可修改'; $lang['status_plugin'] = '插件'; $lang['status_template'] = '模板'; +$lang['status_bundled'] = '内建'; $lang['msg_enabled'] = '插件 %s 已启用'; $lang['msg_disabled'] = '插件 %s 已禁用'; $lang['msg_delete_success'] = '插件已经卸载'; @@ -70,3 +73,19 @@ $lang['msg_upload_failed'] = '上传文件失败'; $lang['missing_dependency'] = '缺少或者被禁用依赖: %s'; $lang['security_issue'] = '安全问题: %s'; $lang['security_warning'] = '安全警告: %s'; +$lang['update_available'] = '更新:新版本 %s 已经可用。'; +$lang['wrong_folder'] = '插件安装不正确:重命名插件目录 "%s" 为 "%s"。'; +$lang['url_change'] = 'URL已改变:自上次下载以来的下载 URL 已经改变。请在更新扩展前检查新 URL 是否有效。
新的:%s
旧的:%s'; +$lang['error_badurl'] = 'URL 应当以 http 或者 https 作为开头'; +$lang['error_dircreate'] = '无法创建用于保存下载的临时文件夹'; +$lang['error_download'] = '无法下载文件:%s'; +$lang['error_decompress'] = '无法解压下载的文件。这可能是由于文件损坏,在这种情况下您可以重试。这也可能是由于压缩格式是未知的,在这种情况下您需要手动下载并且安装。'; +$lang['error_findfolder'] = '无法识别扩展目录,您需要手动下载和安装'; +$lang['error_copy'] = '在尝试安装文件到目录 %s 时出现文件复制错误:磁盘可能已满或者文件访问权限不正确。这可能导致插件被部分安装并使您的维基处在不稳定状态'; +$lang['noperms'] = '扩展目录不可写'; +$lang['notplperms'] = '模板目录不可写'; +$lang['nopluginperms'] = '插件目录不可写'; +$lang['git'] = '这个扩展是通过 git 安装的,您可能不想在这里升级它'; +$lang['install_url'] = '从 URL 安装:'; +$lang['install_upload'] = '上传扩展:'; +$lang['repo_error'] = '无法连接到插件仓库。请确定您的服务器可以连接 www.dokuwiki.org 并检查您的代理设置。'; -- cgit v1.2.3 From 93691af57f65173963a122e19915917814a32b71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ilker=20rifat=20kapa=C3=A7?= Date: Tue, 13 May 2014 10:15:52 +0200 Subject: translation update --- lib/plugins/authldap/lang/tr/settings.php | 8 ++++++++ lib/plugins/authmysql/lang/tr/settings.php | 12 ++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 lib/plugins/authldap/lang/tr/settings.php create mode 100644 lib/plugins/authmysql/lang/tr/settings.php (limited to 'lib') diff --git a/lib/plugins/authldap/lang/tr/settings.php b/lib/plugins/authldap/lang/tr/settings.php new file mode 100644 index 000000000..843b7ef9c --- /dev/null +++ b/lib/plugins/authldap/lang/tr/settings.php @@ -0,0 +1,8 @@ + + */ +$lang['bindpw'] = 'Üstteki kullanıcının şifresi'; diff --git a/lib/plugins/authmysql/lang/tr/settings.php b/lib/plugins/authmysql/lang/tr/settings.php new file mode 100644 index 000000000..f38b6a03b --- /dev/null +++ b/lib/plugins/authmysql/lang/tr/settings.php @@ -0,0 +1,12 @@ + + */ +$lang['server'] = 'Sizin MySQL sunucunuz'; +$lang['user'] = 'MySQL kullanıcısının adı'; +$lang['password'] = 'Üstteki kullanıcı için şifre'; +$lang['database'] = 'Kullanılacak veritabanı'; +$lang['charset'] = 'Veritabanında kullanılacak karakter seti'; -- cgit v1.2.3 From b5638884124386a376237d025d6a68583e61e1e4 Mon Sep 17 00:00:00 2001 From: Rene Date: Tue, 13 May 2014 12:56:02 +0200 Subject: translation update --- lib/plugins/extension/lang/nl/lang.php | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) (limited to 'lib') diff --git a/lib/plugins/extension/lang/nl/lang.php b/lib/plugins/extension/lang/nl/lang.php index 2983f9fee..f1dc125da 100644 --- a/lib/plugins/extension/lang/nl/lang.php +++ b/lib/plugins/extension/lang/nl/lang.php @@ -5,7 +5,7 @@ * * @author Rene */ -$lang['menu'] = 'Extension Manager (Uitbreidings Beheerder)'; +$lang['menu'] = 'Uitbreidings Beheerder'; $lang['tab_plugins'] = 'Geïnstalleerde Plugins'; $lang['tab_templates'] = 'Geïnstalleerde Templates'; $lang['tab_search'] = 'Zoek en installeer'; @@ -24,7 +24,7 @@ $lang['btn_enable'] = 'Schakel aan'; $lang['btn_disable'] = 'Schakel uit'; $lang['btn_install'] = 'Installeer'; $lang['btn_reinstall'] = 'Her-installeer'; -$lang['js']['reallydel'] = 'Wilt u deze uitbreiding deinstalleren ?'; +$lang['js']['reallydel'] = 'Wilt u deze uitbreiding deinstalleren?'; $lang['search_for'] = 'Zoek Uitbreiding:'; $lang['search'] = 'Zoek'; $lang['extensionby'] = '%s by %s'; @@ -36,16 +36,16 @@ $lang['tags'] = 'Tags:'; $lang['author_hint'] = 'Zoek uitbreidingen van deze auteur:'; $lang['installed'] = 'Geinstalleerd:'; $lang['downloadurl'] = 'Download URL:'; -$lang['repository'] = 'Repository ( centrale opslag)'; +$lang['repository'] = 'Centrale opslag:'; $lang['unknown'] = 'onbekend'; -$lang['installed_version'] = 'Geïnstalleerde versie'; -$lang['install_date'] = 'Uw laatste update :'; +$lang['installed_version'] = 'Geïnstalleerde versie:'; +$lang['install_date'] = 'Uw laatste update:'; $lang['available_version'] = 'Beschikbare versie:'; -$lang['compatible'] = 'Compatible met :'; -$lang['depends'] = 'Afhankelijk van :'; -$lang['similar'] = 'Soortgelijk :'; -$lang['conflicts'] = 'Conflicteerd met :'; -$lang['donate'] = 'Vindt u dit leuk ?'; +$lang['compatible'] = 'Compatible met:'; +$lang['depends'] = 'Afhankelijk van:'; +$lang['similar'] = 'Soortgelijk:'; +$lang['conflicts'] = 'Conflicteerd met:'; +$lang['donate'] = 'Vindt u dit leuk?'; $lang['donate_action'] = 'Koop een kop koffie voor de auteur!'; $lang['repo_retry'] = 'Herhaal'; $lang['provides'] = 'Zorgt voor:'; @@ -85,3 +85,4 @@ $lang['nopluginperms'] = 'Plugin directory is niet schrijfbaar'; $lang['git'] = 'De uitbreiding werd geïnstalleerd via git, u wilt deze hier wellicht niet aanpassen.'; $lang['install_url'] = 'Installeer vanaf URL:'; $lang['install_upload'] = 'Upload Uitbreiding:'; +$lang['repo_error'] = 'Er kon geen verbinding worden gemaakt met de centrale plugin opslag. Controleer of de server verbinding mag maken met www.dokuwiki.org en controleer de proxy instellingen.'; -- cgit v1.2.3 From 6222beda4249ebf70fa3bcb226355339ca7d765c Mon Sep 17 00:00:00 2001 From: Mirko Date: Tue, 13 May 2014 14:35:54 +0200 Subject: translation update --- lib/plugins/authmysql/lang/it/settings.php | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'lib') diff --git a/lib/plugins/authmysql/lang/it/settings.php b/lib/plugins/authmysql/lang/it/settings.php index e493ec7e9..bae021e98 100644 --- a/lib/plugins/authmysql/lang/it/settings.php +++ b/lib/plugins/authmysql/lang/it/settings.php @@ -4,5 +4,10 @@ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * * @author Claudio Lanconelli + * @author Mirko */ +$lang['server'] = 'Il tuo server MySQL'; +$lang['user'] = 'User name di MySQL'; +$lang['database'] = 'Database da usare'; +$lang['charset'] = 'Set di caratteri usato nel database'; $lang['debug'] = 'Mostra ulteriori informazioni di debug'; -- cgit v1.2.3 From 4862797473d3fb509669a7ded14059f3a412bb15 Mon Sep 17 00:00:00 2001 From: Gerrit Uitslag Date: Tue, 13 May 2014 18:41:02 +0200 Subject: translation update --- lib/plugins/extension/lang/nl/lang.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/plugins/extension/lang/nl/lang.php b/lib/plugins/extension/lang/nl/lang.php index f1dc125da..524c2b2e7 100644 --- a/lib/plugins/extension/lang/nl/lang.php +++ b/lib/plugins/extension/lang/nl/lang.php @@ -4,6 +4,7 @@ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * * @author Rene + * @author Gerrit Uitslag */ $lang['menu'] = 'Uitbreidings Beheerder'; $lang['tab_plugins'] = 'Geïnstalleerde Plugins'; @@ -30,7 +31,7 @@ $lang['search'] = 'Zoek'; $lang['extensionby'] = '%s by %s'; $lang['screenshot'] = 'Schermafdruk bij %s'; $lang['popularity'] = 'Populariteit:%s%%'; -$lang['homepage_link'] = 'Dokumenten'; +$lang['homepage_link'] = 'Documentatie'; $lang['bugs_features'] = 'Bugs'; $lang['tags'] = 'Tags:'; $lang['author_hint'] = 'Zoek uitbreidingen van deze auteur:'; -- cgit v1.2.3 From c0d17c851c1bf4371e550a105c91a17644de6b29 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Tue, 13 May 2014 18:39:04 +0200 Subject: removed another case of superflous multiple cache invalidation --- lib/plugins/extension/helper/extension.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'lib') diff --git a/lib/plugins/extension/helper/extension.php b/lib/plugins/extension/helper/extension.php index 43e4452c0..a4736a850 100644 --- a/lib/plugins/extension/helper/extension.php +++ b/lib/plugins/extension/helper/extension.php @@ -597,11 +597,8 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { $path = $this->download($url); $installed = $this->installArchive($path, true); - // purge caches - foreach($installed as $ext => $info){ - $this->setExtension($ext); - $this->purgeCache(); - } + // purge cache + $this->purgeCache(); }catch (Exception $e){ throw $e; } -- cgit v1.2.3 From 59d6be95449db272ee0a6b6d437f535246f795c9 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Tue, 13 May 2014 19:42:05 +0200 Subject: update manager.dat correctly on install. closes #704 --- lib/plugins/extension/helper/extension.php | 39 ++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/plugins/extension/helper/extension.php b/lib/plugins/extension/helper/extension.php index a4736a850..2aca0e218 100644 --- a/lib/plugins/extension/helper/extension.php +++ b/lib/plugins/extension/helper/extension.php @@ -292,7 +292,7 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { */ public function getUpdateDate() { if (!empty($this->managerData['updated'])) return $this->managerData['updated']; - return false; + return $this->getInstallDate(); } /** @@ -577,6 +577,7 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { try { $installed = $this->installArchive("$tmp/upload.archive", true, $basename); + $this->updateManagerData('', $installed); // purge cache $this->purgeCache(); }catch (Exception $e){ @@ -596,6 +597,7 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { try { $path = $this->download($url); $installed = $this->installArchive($path, true); + $this->updateManagerData($url, $installed); // purge cache $this->purgeCache(); @@ -612,8 +614,10 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { * @return array The list of installed extensions */ public function installOrUpdate() { - $path = $this->download($this->getDownloadURL()); + $url = $this->getDownloadURL(); + $path = $this->download($url); $installed = $this->installArchive($path, $this->isInstalled(), $this->getBase()); + $this->updateManagerData($url, $installed); // refresh extension information if (!isset($installed[$this->getID()])) { @@ -727,6 +731,37 @@ class helper_plugin_extension_extension extends DokuWiki_Plugin { } } + /** + * Save the given URL and current datetime in the manager.dat file of all installed extensions + * + * @param string $url Where the extension was downloaded from. (empty for manual installs via upload) + * @param array $installed Optional list of installed plugins + */ + protected function updateManagerData($url = '', $installed = null) { + $origID = $this->getID(); + + if(is_null($installed)) { + $installed = array($origID); + } + + foreach($installed as $ext => $info) { + if($this->getID() != $ext) $this->setExtension($ext); + if($url) { + $this->managerData['downloadurl'] = $url; + } elseif(isset($this->managerData['downloadurl'])) { + unset($this->managerData['downloadurl']); + } + if(isset($this->managerData['installed'])) { + $this->managerData['updated'] = date('r'); + } else { + $this->managerData['installed'] = date('r'); + } + $this->writeManagerData(); + } + + if($this->getID() != $origID) $this->setExtension($origID); + } + /** * Read the manager.dat file */ -- cgit v1.2.3 From 1b4623e76a474977d730f37c3c54ff1cf66489ac Mon Sep 17 00:00:00 2001 From: Mati Date: Wed, 14 May 2014 07:45:56 +0200 Subject: translation update --- lib/plugins/authad/lang/pl/settings.php | 1 + lib/plugins/authmysql/lang/pl/settings.php | 2 ++ lib/plugins/authpgsql/lang/pl/settings.php | 9 +++++++ lib/plugins/extension/lang/pl/lang.php | 39 ++++++++++++++++++++++++++++++ 4 files changed, 51 insertions(+) create mode 100644 lib/plugins/authpgsql/lang/pl/settings.php create mode 100644 lib/plugins/extension/lang/pl/lang.php (limited to 'lib') diff --git a/lib/plugins/authad/lang/pl/settings.php b/lib/plugins/authad/lang/pl/settings.php index 4e397fc98..91cadca6f 100644 --- a/lib/plugins/authad/lang/pl/settings.php +++ b/lib/plugins/authad/lang/pl/settings.php @@ -5,6 +5,7 @@ * * @author Tomasz Bosak * @author Paweł Jan Czochański + * @author Mati */ $lang['account_suffix'] = 'Przyrostek twojej nazwy konta np. @my.domain.org'; $lang['base_dn'] = 'Twoje bazowe DN. Na przykład: DC=my,DC=domain,DC=org'; diff --git a/lib/plugins/authmysql/lang/pl/settings.php b/lib/plugins/authmysql/lang/pl/settings.php index 88cbd5d6f..9dc798ee0 100644 --- a/lib/plugins/authmysql/lang/pl/settings.php +++ b/lib/plugins/authmysql/lang/pl/settings.php @@ -4,6 +4,7 @@ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * * @author Paweł Jan Czochański + * @author Mati */ $lang['server'] = 'Twój server MySQL'; $lang['user'] = 'Nazwa użytkownika MySQL'; @@ -12,3 +13,4 @@ $lang['database'] = 'Używana baza danych'; $lang['charset'] = 'Zestaw znaków uzyty w bazie danych'; $lang['debug'] = 'Wyświetlaj dodatkowe informacje do debugowania.'; $lang['checkPass'] = 'Zapytanie SQL wykorzystywane do sprawdzania haseł.'; +$lang['debug_o_2'] = 'wszystkie zapytania SQL'; diff --git a/lib/plugins/authpgsql/lang/pl/settings.php b/lib/plugins/authpgsql/lang/pl/settings.php new file mode 100644 index 000000000..25a2afd4f --- /dev/null +++ b/lib/plugins/authpgsql/lang/pl/settings.php @@ -0,0 +1,9 @@ + + */ +$lang['server'] = 'Twój serwer PostgreSQL'; +$lang['database'] = 'Baza danych do użycia'; diff --git a/lib/plugins/extension/lang/pl/lang.php b/lib/plugins/extension/lang/pl/lang.php new file mode 100644 index 000000000..4fdca79c9 --- /dev/null +++ b/lib/plugins/extension/lang/pl/lang.php @@ -0,0 +1,39 @@ + + */ +$lang['menu'] = 'Menedżer rozszerzeń'; +$lang['tab_plugins'] = 'Zainstalowane dodatki'; +$lang['tab_search'] = 'Znajdź i zainstaluj'; +$lang['notinstalled'] = 'Te rozszerzenie nie zostało zainstalowane'; +$lang['alreadyenabled'] = 'Te rozszerzenie jest już uruchomione'; +$lang['unknownauthor'] = 'Nieznany autor'; +$lang['unknownversion'] = 'Nieznana wersja'; +$lang['btn_info'] = 'Pokaż więcej informacji'; +$lang['btn_enable'] = 'Uruchom'; +$lang['btn_disable'] = 'Wyłącz'; +$lang['btn_reinstall'] = 'Ponowna instalacja'; +$lang['js']['reallydel'] = 'Naprawdę odinstalować te rozszerzenie?'; +$lang['search'] = 'Szukaj'; +$lang['bugs_features'] = 'Błędy'; +$lang['tags'] = 'Tagi:'; +$lang['installed'] = 'Zainstalowano:'; +$lang['repository'] = 'Repozytorium'; +$lang['installed_version'] = 'Zainstalowana wersja:'; +$lang['install_date'] = 'Twoja ostatnia aktualizacja:'; +$lang['available_version'] = 'Dostępna wersja:'; +$lang['depends'] = 'Zależy od:'; +$lang['conflicts'] = 'Konflikt z:'; +$lang['donate'] = 'Lubisz to?'; +$lang['donate_action'] = 'Kup autorowi kawę!'; +$lang['repo_retry'] = 'Ponów'; +$lang['status'] = 'Status:'; +$lang['status_installed'] = 'zainstalowano'; +$lang['status_not_installed'] = 'nie zainstalowano'; +$lang['status_enabled'] = 'uruchomione'; +$lang['status_disabled'] = 'wyłączone'; +$lang['status_plugin'] = 'dodatek'; +$lang['msg_delete_success'] = 'Rozszerzenie odinstalowane'; -- cgit v1.2.3 From 918a4468877109c2ba2f82fa4e1a225a4738ed9a Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Wed, 14 May 2014 19:24:01 +0200 Subject: don't treat double slashes as comments when used in string This avoids treating double slashes as single line comments in CSS when they are used in a filter or content string. closes #638 --- lib/exe/css.php | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) (limited to 'lib') diff --git a/lib/exe/css.php b/lib/exe/css.php index 30d0d18c5..6c1d60751 100644 --- a/lib/exe/css.php +++ b/lib/exe/css.php @@ -602,30 +602,47 @@ function css_comment_cb($matches){ function css_onelinecomment_cb($matches) { $line = $matches[0]; - $out = ''; $i = 0; $len = strlen($line); + while ($i< $len){ $nextcom = strpos($line, '//', $i); $nexturl = stripos($line, 'url(', $i); if($nextcom === false) { // no more comments, we're done - $out .= substr($line, $i, $len-$i); + $i = $len; break; } + + // keep any quoted string that starts before a comment + $nextsqt = strpos($line, "'", $i); + $nextdqt = strpos($line, '"', $i); + if(min($nextsqt, $nextdqt) < $nextcom) { + $skipto = false; + if($nextsqt !== false && ($nextdqt === false || $nextsqt < $nextdqt)) { + $skipto = strpos($line, "'", $nextsqt+1) +1; + } else if ($nextdqt !== false) { + $skipto = strpos($line, '"', $nextdqt+1) +1; + } + + if($skipto !== false) { + $i = $skipto; + continue; + } + } + if($nexturl === false || $nextcom < $nexturl) { // no url anymore, strip comment and be done - $out .= substr($line, $i, $nextcom-$i); + $i = $nextcom; break; } + // we have an upcoming url - $urlclose = strpos($line, ')', $nexturl); - $out .= substr($line, $i, $urlclose-$i); - $i = $urlclose; + $i = strpos($line, ')', $nexturl); } - return $out; + return substr($line, 0, $i); } //Setup VIM: ex: et ts=4 : -- cgit v1.2.3 From 33cfab00505903e3bee37020f5e099e5c0fd70a9 Mon Sep 17 00:00:00 2001 From: Francesco Date: Wed, 14 May 2014 21:20:56 +0200 Subject: translation update --- lib/plugins/authldap/lang/it/settings.php | 6 ++++++ lib/plugins/authmysql/lang/it/settings.php | 24 ++++++++++++++++++++++++ lib/plugins/authpgsql/lang/it/settings.php | 11 +++++++++++ 3 files changed, 41 insertions(+) create mode 100644 lib/plugins/authpgsql/lang/it/settings.php (limited to 'lib') diff --git a/lib/plugins/authldap/lang/it/settings.php b/lib/plugins/authldap/lang/it/settings.php index eba7cde6e..858c694b8 100644 --- a/lib/plugins/authldap/lang/it/settings.php +++ b/lib/plugins/authldap/lang/it/settings.php @@ -5,6 +5,7 @@ * * @author Edmondo Di Tucci * @author Claudio Lanconelli + * @author Francesco */ $lang['server'] = 'Il tuo server LDAP. Inserire o l\'hostname (localhost) oppure un URL completo (ldap://server.tld:389)'; $lang['port'] = 'Porta del server LDAP se non è stato fornito un URL completo più sopra.'; @@ -14,6 +15,11 @@ $lang['userfilter'] = 'Filtro per cercare l\'account utente LDAP. Eg. $lang['groupfilter'] = 'Filtro per cercare i gruppi LDAP. Eg. (&(objectClass=posixGroup)(|(gidNumber=%{gid})(memberUID=%{user})))'; $lang['version'] = 'Versione protocollo da usare. Pu3'; $lang['starttls'] = 'Usare la connessione TSL?'; +$lang['deref'] = 'Come differenziare un alias?'; $lang['userscope'] = 'Limita il contesto di ricerca per la ricerca degli utenti'; $lang['groupscope'] = 'Limita il contesto di ricerca per la ricerca dei gruppi'; $lang['debug'] = 'In caso di errori mostra ulteriori informazioni di debug'; +$lang['deref_o_0'] = 'LDAP_DEREF_NEVER'; +$lang['deref_o_1'] = 'LDAP_DEREF_SEARCHING'; +$lang['deref_o_2'] = 'LDAP_DEREF_FINDING'; +$lang['deref_o_3'] = 'LDAP_DEREF_ALWAYS'; diff --git a/lib/plugins/authmysql/lang/it/settings.php b/lib/plugins/authmysql/lang/it/settings.php index bae021e98..10c0de96f 100644 --- a/lib/plugins/authmysql/lang/it/settings.php +++ b/lib/plugins/authmysql/lang/it/settings.php @@ -5,9 +5,33 @@ * * @author Claudio Lanconelli * @author Mirko + * @author Francesco */ $lang['server'] = 'Il tuo server MySQL'; $lang['user'] = 'User name di MySQL'; $lang['database'] = 'Database da usare'; $lang['charset'] = 'Set di caratteri usato nel database'; $lang['debug'] = 'Mostra ulteriori informazioni di debug'; +$lang['TablesToLock'] = 'Lista, separata da virgola, delle tabelle che devono essere bloccate in scrittura'; +$lang['checkPass'] = 'Istruzione SQL per il controllo password'; +$lang['getUserInfo'] = 'Istruzione SQL per recuperare le informazioni utente'; +$lang['getUsers'] = 'Istruzione SQL per listare tutti gli utenti'; +$lang['FilterLogin'] = 'Istruzione SQL per per filtrare gli utenti in funzione del "login name"'; +$lang['SortOrder'] = 'Istruzione SQL per ordinare gli utenti'; +$lang['addUser'] = 'Istruzione SQL per aggiungere un nuovo utente'; +$lang['addGroup'] = 'Istruzione SQL per aggiungere un nuovo gruppo'; +$lang['addUserGroup'] = 'Istruzione SQL per aggiungere un utente ad un gruppo esistente'; +$lang['delGroup'] = 'Istruzione SQL per imuovere un gruppo'; +$lang['getUserID'] = 'Istruzione SQL per recuperare la primary key di un utente'; +$lang['delUser'] = 'Istruzione SQL per cancellare un utente'; +$lang['delUserRefs'] = 'Istruzione SQL per rimuovere un utente da tutti i gruppi'; +$lang['updateUser'] = 'Istruzione SQL per aggiornare il profilo utente'; +$lang['UpdateLogin'] = 'Clausola per aggiornare il "login name" dell\'utente'; +$lang['UpdatePass'] = 'Clausola per aggiornare la password utente'; +$lang['UpdateEmail'] = 'Clausola per aggiornare l\'email utente'; +$lang['UpdateName'] = 'Clausola per aggiornare il nome completo'; +$lang['delUserGroup'] = 'Istruzione SQL per rimuovere un utente da un dato gruppo'; +$lang['getGroupID'] = 'Istruzione SQL per avere la primary key di un dato gruppo'; +$lang['debug_o_0'] = 'Nulla'; +$lang['debug_o_1'] = 'Solo in errore'; +$lang['debug_o_2'] = 'Tutte le query SQL'; diff --git a/lib/plugins/authpgsql/lang/it/settings.php b/lib/plugins/authpgsql/lang/it/settings.php new file mode 100644 index 000000000..baf40a468 --- /dev/null +++ b/lib/plugins/authpgsql/lang/it/settings.php @@ -0,0 +1,11 @@ + + */ +$lang['server'] = 'Il tuo server PostgreSQL '; +$lang['port'] = 'La porta del tuo server PostgreSQL '; +$lang['user'] = 'Lo username PostgreSQL'; +$lang['database'] = 'Database da usare'; -- cgit v1.2.3 From b9b1fa3cc0cbb48e6e911f62909871bb8c72c71d Mon Sep 17 00:00:00 2001 From: Francesco Date: Wed, 14 May 2014 21:30:56 +0200 Subject: translation update --- lib/plugins/extension/lang/it/lang.php | 41 ++++++++++++++++++++++++++++++++ lib/plugins/usermanager/lang/it/lang.php | 8 +++++++ 2 files changed, 49 insertions(+) create mode 100644 lib/plugins/extension/lang/it/lang.php (limited to 'lib') diff --git a/lib/plugins/extension/lang/it/lang.php b/lib/plugins/extension/lang/it/lang.php new file mode 100644 index 000000000..fbf163d57 --- /dev/null +++ b/lib/plugins/extension/lang/it/lang.php @@ -0,0 +1,41 @@ + + */ +$lang['btn_enable'] = 'Abilita'; +$lang['btn_disable'] = 'Disabilita'; +$lang['btn_install'] = 'Installa'; +$lang['btn_reinstall'] = 'Reinstalla'; +$lang['search'] = 'Cerca'; +$lang['homepage_link'] = 'Documenti'; +$lang['bugs_features'] = 'Bug'; +$lang['tags'] = 'Tag:'; +$lang['author_hint'] = 'Cerca estensioni per questo autore'; +$lang['installed'] = 'Installato:'; +$lang['downloadurl'] = 'URL download:'; +$lang['repository'] = 'Repository'; +$lang['installed_version'] = 'Versione installata'; +$lang['install_date'] = 'Il tuo ultimo aggiornamento:'; +$lang['available_version'] = 'Versione disponibile:'; +$lang['compatible'] = 'Compatibile con:'; +$lang['similar'] = 'Simile a:'; +$lang['donate'] = 'Simile a questo?'; +$lang['repo_retry'] = 'Riprova'; +$lang['status'] = 'Status:'; +$lang['status_installed'] = 'installato'; +$lang['status_not_installed'] = 'non installato'; +$lang['status_protected'] = 'protetto'; +$lang['status_enabled'] = 'abilitato'; +$lang['status_disabled'] = 'disabilitato'; +$lang['status_unmodifiable'] = 'inmodificabile'; +$lang['status_plugin'] = 'plugin'; +$lang['status_template'] = 'modello'; +$lang['error_badurl'] = 'URLs deve iniziare con http o https'; +$lang['error_dircreate'] = 'Impossibile creare una cartella temporanea per ricevere il download'; +$lang['error_download'] = 'Impossibile scaricare il file: %s'; +$lang['notplperms'] = 'Il modello di cartella non è scrivibile'; +$lang['nopluginperms'] = 'La cartella plugin non è scrivibile'; +$lang['install_url'] = 'Installa da URL:'; diff --git a/lib/plugins/usermanager/lang/it/lang.php b/lib/plugins/usermanager/lang/it/lang.php index 6c6789442..af19e293e 100644 --- a/lib/plugins/usermanager/lang/it/lang.php +++ b/lib/plugins/usermanager/lang/it/lang.php @@ -16,6 +16,7 @@ * @author Matteo Pasotti * @author snarchio@gmail.com * @author Claudio Lanconelli + * @author Francesco */ $lang['menu'] = 'Gestione Utenti'; $lang['noauth'] = '(autenticazione non disponibile)'; @@ -40,6 +41,9 @@ $lang['clear'] = 'Azzera filtro di ricerca'; $lang['filter'] = 'Filtro'; $lang['export_all'] = 'Esporta tutti gli utenti (CSV)'; $lang['export_filtered'] = 'Esporta elenco utenti filtrati (CSV)'; +$lang['import'] = 'Importa nuovi utenti'; +$lang['line'] = 'Linea numero'; +$lang['error'] = 'Messaggio di errore'; $lang['summary'] = 'Visualizzazione utenti %1$d-%2$d di %3$d trovati. %4$d utenti totali.'; $lang['nonefound'] = 'Nessun utente trovato. %d utenti totali.'; $lang['delete_ok'] = '%d utenti eliminati'; @@ -60,3 +64,7 @@ $lang['add_ok'] = 'Utente aggiunto correttamente'; $lang['add_fail'] = 'Aggiunta utente fallita'; $lang['notify_ok'] = 'Email di notifica inviata'; $lang['notify_fail'] = 'L\'email di notifica non può essere inviata'; +$lang['import_error_badname'] = 'Nome errato'; +$lang['import_error_badmail'] = 'Indirizzo email errato'; +$lang['import_error_readfail'] = 'Importazione in errore. Impossibile leggere i file caricati.'; +$lang['import_error_create'] = 'Impossibile creare l\'utente'; -- cgit v1.2.3 From fde860be8cb3ed16b2b0843b77b093a60397083e Mon Sep 17 00:00:00 2001 From: Gerrit Uitslag Date: Fri, 16 May 2014 00:09:32 +0200 Subject: Move colon from code to language strings --- lib/plugins/acl/admin.php | 4 ++-- lib/plugins/acl/lang/ar/lang.php | 4 ++-- lib/plugins/acl/lang/bg/lang.php | 4 ++-- lib/plugins/acl/lang/ca-valencia/lang.php | 4 ++-- lib/plugins/acl/lang/ca/lang.php | 4 ++-- lib/plugins/acl/lang/cs/lang.php | 4 ++-- lib/plugins/acl/lang/da/lang.php | 4 ++-- lib/plugins/acl/lang/de-informal/lang.php | 4 ++-- lib/plugins/acl/lang/de/lang.php | 4 ++-- lib/plugins/acl/lang/el/lang.php | 4 ++-- lib/plugins/acl/lang/en/lang.php | 4 ++-- lib/plugins/acl/lang/eo/lang.php | 4 ++-- lib/plugins/acl/lang/es/lang.php | 4 ++-- lib/plugins/acl/lang/et/lang.php | 4 ++-- lib/plugins/acl/lang/eu/lang.php | 4 ++-- lib/plugins/acl/lang/fa/lang.php | 4 ++-- lib/plugins/acl/lang/fi/lang.php | 4 ++-- lib/plugins/acl/lang/fr/lang.php | 4 ++-- lib/plugins/acl/lang/gl/lang.php | 4 ++-- lib/plugins/acl/lang/he/lang.php | 4 ++-- lib/plugins/acl/lang/hr/lang.php | 4 ++-- lib/plugins/acl/lang/ia/lang.php | 4 ++-- lib/plugins/acl/lang/id/lang.php | 4 ++-- lib/plugins/acl/lang/is/lang.php | 4 ++-- lib/plugins/acl/lang/it/lang.php | 4 ++-- lib/plugins/acl/lang/ja/lang.php | 4 ++-- lib/plugins/acl/lang/kk/lang.php | 4 ++-- lib/plugins/acl/lang/ko/lang.php | 4 ++-- lib/plugins/acl/lang/la/lang.php | 4 ++-- lib/plugins/acl/lang/lt/lang.php | 4 ++-- lib/plugins/acl/lang/lv/lang.php | 4 ++-- lib/plugins/acl/lang/mk/lang.php | 4 ++-- lib/plugins/acl/lang/mr/lang.php | 4 ++-- lib/plugins/acl/lang/ne/lang.php | 4 ++-- lib/plugins/acl/lang/nl/lang.php | 4 ++-- lib/plugins/acl/lang/no/lang.php | 4 ++-- lib/plugins/acl/lang/pl/lang.php | 4 ++-- lib/plugins/acl/lang/pt-br/lang.php | 4 ++-- lib/plugins/acl/lang/pt/lang.php | 4 ++-- lib/plugins/acl/lang/ro/lang.php | 4 ++-- lib/plugins/acl/lang/ru/lang.php | 4 ++-- lib/plugins/acl/lang/sk/lang.php | 4 ++-- lib/plugins/acl/lang/sl/lang.php | 4 ++-- lib/plugins/acl/lang/sq/lang.php | 4 ++-- lib/plugins/acl/lang/sr/lang.php | 4 ++-- lib/plugins/acl/lang/sv/lang.php | 4 ++-- lib/plugins/acl/lang/th/lang.php | 4 ++-- lib/plugins/acl/lang/tr/lang.php | 4 ++-- lib/plugins/acl/lang/uk/lang.php | 4 ++-- lib/plugins/acl/lang/vi/lang.php | 4 ++-- lib/plugins/acl/lang/zh-tw/lang.php | 4 ++-- lib/plugins/acl/lang/zh/lang.php | 4 ++-- 52 files changed, 104 insertions(+), 104 deletions(-) (limited to 'lib') diff --git a/lib/plugins/acl/admin.php b/lib/plugins/acl/admin.php index de38aedd5..ebb097a04 100644 --- a/lib/plugins/acl/admin.php +++ b/lib/plugins/acl/admin.php @@ -779,8 +779,8 @@ class admin_plugin_acl extends DokuWiki_Admin_Plugin { } echo '", +/*! + * jQuery UI Menu 1.11.0 + * http://jqueryui.com + * + * Copyright 2014 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/menu/ + */ + + +var menu = $.widget( "ui.menu", { + version: "1.11.0", + defaultElement: "
    ", + delay: 300, options: { - appendTo: null, - autoFocus: false, - delay: 300, - minLength: 1, + icons: { + submenu: "ui-icon-carat-1-e" + }, + items: "> *", + menus: "ul", position: { - my: "left top", - at: "left bottom", - collision: "none" + my: "left-1 top", + at: "right top" }, - source: null, + role: "menu", // callbacks - change: null, - close: null, + blur: null, focus: null, - open: null, - response: null, - search: null, select: null }, - requestIndex: 0, - pending: 0, - _create: function() { - // Some browsers only repeat keydown events, not keypress events, - // so we use the suppressKeyPress flag to determine if we've already - // handled the keydown event. #7269 - // Unfortunately the code for & in keypress is the same as the up arrow, - // so we use the suppressKeyPressRepeat flag to avoid handling keypress - // events when we know the keydown event was used to modify the - // search term. #7799 - var suppressKeyPress, suppressKeyPressRepeat, suppressInput, - nodeName = this.element[0].nodeName.toLowerCase(), - isTextarea = nodeName === "textarea", - isInput = nodeName === "input"; - - this.isMultiLine = - // Textareas are always multi-line - isTextarea ? true : - // Inputs are always single-line, even if inside a contentEditable element - // IE also treats inputs as contentEditable - isInput ? false : - // All other element types are determined by whether or not they're contentEditable - this.element.prop( "isContentEditable" ); - - this.valueMethod = this.element[ isTextarea || isInput ? "val" : "text" ]; - this.isNewMenu = true; + this.activeMenu = this.element; + // Flag used to prevent firing of the click handler + // as the event bubbles up through nested menus + this.mouseHandled = false; this.element - .addClass( "ui-autocomplete-input" ) - .attr( "autocomplete", "off" ); + .uniqueId() + .addClass( "ui-menu ui-widget ui-widget-content" ) + .toggleClass( "ui-menu-icons", !!this.element.find( ".ui-icon" ).length ) + .attr({ + role: this.options.role, + tabIndex: 0 + }); - this._on( this.element, { - keydown: function( event ) { - if ( this.element.prop( "readOnly" ) ) { - suppressKeyPress = true; - suppressInput = true; - suppressKeyPressRepeat = true; - return; - } + if ( this.options.disabled ) { + this.element + .addClass( "ui-state-disabled" ) + .attr( "aria-disabled", "true" ); + } - suppressKeyPress = false; - suppressInput = false; - suppressKeyPressRepeat = false; - var keyCode = $.ui.keyCode; - switch( event.keyCode ) { - case keyCode.PAGE_UP: - suppressKeyPress = true; - this._move( "previousPage", event ); - break; - case keyCode.PAGE_DOWN: - suppressKeyPress = true; - this._move( "nextPage", event ); - break; - case keyCode.UP: - suppressKeyPress = true; - this._keyEvent( "previous", event ); - break; - case keyCode.DOWN: - suppressKeyPress = true; - this._keyEvent( "next", event ); - break; - case keyCode.ENTER: - case keyCode.NUMPAD_ENTER: - // when menu is open and has focus - if ( this.menu.active ) { - // #6055 - Opera still allows the keypress to occur - // which causes forms to submit - suppressKeyPress = true; - event.preventDefault(); - this.menu.select( event ); - } - break; - case keyCode.TAB: - if ( this.menu.active ) { - this.menu.select( event ); - } - break; - case keyCode.ESCAPE: - if ( this.menu.element.is( ":visible" ) ) { - this._value( this.term ); - this.close( event ); - // Different browsers have different default behavior for escape - // Single press can mean undo or clear - // Double press in IE means clear the whole form - event.preventDefault(); - } - break; - default: - suppressKeyPressRepeat = true; - // search timeout should be triggered before the input value is changed - this._searchTimeout( event ); - break; - } + this._on({ + // Prevent focus from sticking to links inside menu after clicking + // them (focus should always stay on UL during navigation). + "mousedown .ui-menu-item": function( event ) { + event.preventDefault(); }, - keypress: function( event ) { - if ( suppressKeyPress ) { - suppressKeyPress = false; - if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) { - event.preventDefault(); + "click .ui-menu-item": function( event ) { + var target = $( event.target ); + if ( !this.mouseHandled && target.not( ".ui-state-disabled" ).length ) { + this.select( event ); + + // Only set the mouseHandled flag if the event will bubble, see #9469. + if ( !event.isPropagationStopped() ) { + this.mouseHandled = true; } - return; - } - if ( suppressKeyPressRepeat ) { - return; - } - // replicate some key handlers to allow them to repeat in Firefox and Opera - var keyCode = $.ui.keyCode; - switch( event.keyCode ) { - case keyCode.PAGE_UP: - this._move( "previousPage", event ); - break; - case keyCode.PAGE_DOWN: - this._move( "nextPage", event ); - break; - case keyCode.UP: - this._keyEvent( "previous", event ); - break; - case keyCode.DOWN: - this._keyEvent( "next", event ); - break; + // Open submenu on click + if ( target.has( ".ui-menu" ).length ) { + this.expand( event ); + } else if ( !this.element.is( ":focus" ) && $( this.document[ 0 ].activeElement ).closest( ".ui-menu" ).length ) { + + // Redirect focus to the menu + this.element.trigger( "focus", [ true ] ); + + // If the active item is on the top level, let it stay active. + // Otherwise, blur the active item since it is no longer visible. + if ( this.active && this.active.parents( ".ui-menu" ).length === 1 ) { + clearTimeout( this.timer ); + } + } } }, - input: function( event ) { - if ( suppressInput ) { - suppressInput = false; - event.preventDefault(); - return; - } - this._searchTimeout( event ); + "mouseenter .ui-menu-item": function( event ) { + var target = $( event.currentTarget ); + // Remove ui-state-active class from siblings of the newly focused menu item + // to avoid a jump caused by adjacent elements both having a class with a border + target.siblings( ".ui-state-active" ).removeClass( "ui-state-active" ); + this.focus( event, target ); }, - focus: function() { - this.selectedItem = null; - this.previous = this._value(); + mouseleave: "collapseAll", + "mouseleave .ui-menu": "collapseAll", + focus: function( event, keepActiveItem ) { + // If there's already an active item, keep it active + // If not, activate the first item + var item = this.active || this.element.find( this.options.items ).eq( 0 ); + + if ( !keepActiveItem ) { + this.focus( event, item ); + } }, blur: function( event ) { - if ( this.cancelBlur ) { - delete this.cancelBlur; - return; + this._delay(function() { + if ( !$.contains( this.element[0], this.document[0].activeElement ) ) { + this.collapseAll( event ); + } + }); + }, + keydown: "_keydown" + }); + + this.refresh(); + + // Clicks outside of a menu collapse any open menus + this._on( this.document, { + click: function( event ) { + if ( this._closeOnDocumentClick( event ) ) { + this.collapseAll( event ); } - clearTimeout( this.searching ); - this.close( event ); - this._change( event ); + // Reset the mouseHandled flag + this.mouseHandled = false; } }); + }, - this._initSource(); - this.menu = $( "
      " ) - .addClass( "ui-autocomplete ui-front" ) - .appendTo( this._appendTo() ) - .menu({ - // disable ARIA support, the live region takes care of that - role: null - }) - .hide() - .data( "ui-menu" ); + _destroy: function() { + // Destroy (sub)menus + this.element + .removeAttr( "aria-activedescendant" ) + .find( ".ui-menu" ).addBack() + .removeClass( "ui-menu ui-widget ui-widget-content ui-menu-icons ui-front" ) + .removeAttr( "role" ) + .removeAttr( "tabIndex" ) + .removeAttr( "aria-labelledby" ) + .removeAttr( "aria-expanded" ) + .removeAttr( "aria-hidden" ) + .removeAttr( "aria-disabled" ) + .removeUniqueId() + .show(); - this._on( this.menu.element, { - mousedown: function( event ) { - // prevent moving focus out of the text field - event.preventDefault(); + // Destroy menu items + this.element.find( ".ui-menu-item" ) + .removeClass( "ui-menu-item" ) + .removeAttr( "role" ) + .removeAttr( "aria-disabled" ) + .removeUniqueId() + .removeClass( "ui-state-hover" ) + .removeAttr( "tabIndex" ) + .removeAttr( "role" ) + .removeAttr( "aria-haspopup" ) + .children().each( function() { + var elem = $( this ); + if ( elem.data( "ui-menu-submenu-carat" ) ) { + elem.remove(); + } + }); - // IE doesn't prevent moving focus even with event.preventDefault() - // so we set a flag to know when we should ignore the blur event - this.cancelBlur = true; - this._delay(function() { - delete this.cancelBlur; - }); + // Destroy menu dividers + this.element.find( ".ui-menu-divider" ).removeClass( "ui-menu-divider ui-widget-content" ); + }, - // clicking on the scrollbar causes focus to shift to the body - // but we can't detect a mouseup or a click immediately afterward - // so we have to track the next mousedown and close the menu if - // the user clicks somewhere outside of the autocomplete - var menuElement = this.menu.element[ 0 ]; - if ( !$( event.target ).closest( ".ui-menu-item" ).length ) { - this._delay(function() { - var that = this; - this.document.one( "mousedown", function( event ) { - if ( event.target !== that.element[ 0 ] && - event.target !== menuElement && - !$.contains( menuElement, event.target ) ) { - that.close(); - } - }); - }); - } - }, - menufocus: function( event, ui ) { - // support: Firefox - // Prevent accidental activation of menu items in Firefox (#7024 #9118) - if ( this.isNewMenu ) { - this.isNewMenu = false; - if ( event.originalEvent && /^mouse/.test( event.originalEvent.type ) ) { - this.menu.blur(); + _keydown: function( event ) { + var match, prev, character, skip, regex, + preventDefault = true; - this.document.one( "mousemove", function() { - $( event.target ).trigger( event.originalEvent ); - }); + function escape( value ) { + return value.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" ); + } - return; - } - } + switch ( event.keyCode ) { + case $.ui.keyCode.PAGE_UP: + this.previousPage( event ); + break; + case $.ui.keyCode.PAGE_DOWN: + this.nextPage( event ); + break; + case $.ui.keyCode.HOME: + this._move( "first", "first", event ); + break; + case $.ui.keyCode.END: + this._move( "last", "last", event ); + break; + case $.ui.keyCode.UP: + this.previous( event ); + break; + case $.ui.keyCode.DOWN: + this.next( event ); + break; + case $.ui.keyCode.LEFT: + this.collapse( event ); + break; + case $.ui.keyCode.RIGHT: + if ( this.active && !this.active.is( ".ui-state-disabled" ) ) { + this.expand( event ); + } + break; + case $.ui.keyCode.ENTER: + case $.ui.keyCode.SPACE: + this._activate( event ); + break; + case $.ui.keyCode.ESCAPE: + this.collapse( event ); + break; + default: + preventDefault = false; + prev = this.previousFilter || ""; + character = String.fromCharCode( event.keyCode ); + skip = false; - var item = ui.item.data( "ui-autocomplete-item" ); - if ( false !== this._trigger( "focus", event, { item: item } ) ) { - // use value to match what will end up in the input, if it was a key event - if ( event.originalEvent && /^key/.test( event.originalEvent.type ) ) { - this._value( item.value ); - } - } else { - // Normally the input is populated with the item's value as the - // menu is navigated, causing screen readers to notice a change and - // announce the item. Since the focus event was canceled, this doesn't - // happen, so we update the live region so that screen readers can - // still notice the change and announce it. - this.liveRegion.text( item.value ); - } - }, - menuselect: function( event, ui ) { - var item = ui.item.data( "ui-autocomplete-item" ), - previous = this.previous; + clearTimeout( this.filterTimer ); - // only trigger when focus was lost (click on menu) - if ( this.element[0] !== this.document[0].activeElement ) { - this.element.focus(); - this.previous = previous; - // #6109 - IE triggers two focus events and the second - // is asynchronous, so we need to reset the previous - // term synchronously and asynchronously :-( - this._delay(function() { - this.previous = previous; - this.selectedItem = item; - }); - } + if ( character === prev ) { + skip = true; + } else { + character = prev + character; + } - if ( false !== this._trigger( "select", event, { item: item } ) ) { - this._value( item.value ); + regex = new RegExp( "^" + escape( character ), "i" ); + match = this.activeMenu.find( this.options.items ).filter(function() { + return regex.test( $( this ).text() ); + }); + match = skip && match.index( this.active.next() ) !== -1 ? + this.active.nextAll( ".ui-menu-item" ) : + match; + + // If no matches on the current filter, reset to the last character pressed + // to move down the menu to the first item that starts with that character + if ( !match.length ) { + character = String.fromCharCode( event.keyCode ); + regex = new RegExp( "^" + escape( character ), "i" ); + match = this.activeMenu.find( this.options.items ).filter(function() { + return regex.test( $( this ).text() ); + }); + } + + if ( match.length ) { + this.focus( event, match ); + if ( match.length > 1 ) { + this.previousFilter = character; + this.filterTimer = this._delay(function() { + delete this.previousFilter; + }, 1000 ); + } else { + delete this.previousFilter; } - // reset the term after the select event - // this allows custom select handling to work properly - this.term = this._value(); + } else { + delete this.previousFilter; + } + } - this.close( event ); - this.selectedItem = item; + if ( preventDefault ) { + event.preventDefault(); + } + }, + + _activate: function( event ) { + if ( !this.active.is( ".ui-state-disabled" ) ) { + if ( this.active.is( "[aria-haspopup='true']" ) ) { + this.expand( event ); + } else { + this.select( event ); } - }); + } + }, - this.liveRegion = $( "", { - role: "status", - "aria-live": "polite" + refresh: function() { + var menus, items, + that = this, + icon = this.options.icons.submenu, + submenus = this.element.find( this.options.menus ); + + this.element.toggleClass( "ui-menu-icons", !!this.element.find( ".ui-icon" ).length ); + + // Initialize nested menus + submenus.filter( ":not(.ui-menu)" ) + .addClass( "ui-menu ui-widget ui-widget-content ui-front" ) + .hide() + .attr({ + role: this.options.role, + "aria-hidden": "true", + "aria-expanded": "false" }) - .addClass( "ui-helper-hidden-accessible" ) - .insertBefore( this.element ); + .each(function() { + var menu = $( this ), + item = menu.parent(), + submenuCarat = $( "" ) + .addClass( "ui-menu-icon ui-icon " + icon ) + .data( "ui-menu-submenu-carat", true ); - // turning off autocomplete prevents the browser from remembering the - // value when navigating through history, so we re-enable autocomplete - // if the page is unloaded before the widget is destroyed. #7790 - this._on( this.window, { - beforeunload: function() { - this.element.removeAttr( "autocomplete" ); + item + .attr( "aria-haspopup", "true" ) + .prepend( submenuCarat ); + menu.attr( "aria-labelledby", item.attr( "id" ) ); + }); + + menus = submenus.add( this.element ); + items = menus.find( this.options.items ); + + // Initialize menu-items containing spaces and/or dashes only as dividers + items.not( ".ui-menu-item" ).each(function() { + var item = $( this ); + if ( that._isDivider( item ) ) { + item.addClass( "ui-widget-content ui-menu-divider" ); } }); + + // Don't refresh list items that are already adapted + items.not( ".ui-menu-item, .ui-menu-divider" ) + .addClass( "ui-menu-item" ) + .uniqueId() + .attr({ + tabIndex: -1, + role: this._itemRole() + }); + + // Add aria-disabled attribute to any disabled menu item + items.filter( ".ui-state-disabled" ).attr( "aria-disabled", "true" ); + + // If the active item has been removed, blur the menu + if ( this.active && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) { + this.blur(); + } }, - _destroy: function() { - clearTimeout( this.searching ); - this.element - .removeClass( "ui-autocomplete-input" ) - .removeAttr( "autocomplete" ); - this.menu.element.remove(); - this.liveRegion.remove(); + _itemRole: function() { + return { + menu: "menuitem", + listbox: "option" + }[ this.options.role ]; }, _setOption: function( key, value ) { - this._super( key, value ); - if ( key === "source" ) { - this._initSource(); - } - if ( key === "appendTo" ) { - this.menu.element.appendTo( this._appendTo() ); + if ( key === "icons" ) { + this.element.find( ".ui-menu-icon" ) + .removeClass( this.options.icons.submenu ) + .addClass( value.submenu ); } - if ( key === "disabled" && value && this.xhr ) { - this.xhr.abort(); + if ( key === "disabled" ) { + this.element + .toggleClass( "ui-state-disabled", !!value ) + .attr( "aria-disabled", value ); } + this._super( key, value ); }, - _appendTo: function() { - var element = this.options.appendTo; + focus: function( event, item ) { + var nested, focused; + this.blur( event, event && event.type === "focus" ); - if ( element ) { - element = element.jquery || element.nodeType ? - $( element ) : - this.document.find( element ).eq( 0 ); + this._scrollIntoView( item ); + + this.active = item.first(); + focused = this.active.addClass( "ui-state-focus" ).removeClass( "ui-state-active" ); + // Only update aria-activedescendant if there's a role + // otherwise we assume focus is managed elsewhere + if ( this.options.role ) { + this.element.attr( "aria-activedescendant", focused.attr( "id" ) ); } - if ( !element ) { - element = this.element.closest( ".ui-front" ); + // Highlight active parent menu item, if any + this.active + .parent() + .closest( ".ui-menu-item" ) + .addClass( "ui-state-active" ); + + if ( event && event.type === "keydown" ) { + this._close(); + } else { + this.timer = this._delay(function() { + this._close(); + }, this.delay ); } - if ( !element.length ) { - element = this.document[0].body; + nested = item.children( ".ui-menu" ); + if ( nested.length && event && ( /^mouse/.test( event.type ) ) ) { + this._startOpening(nested); } + this.activeMenu = item.parent(); - return element; + this._trigger( "focus", event, { item: item } ); }, - _initSource: function() { - var array, url, - that = this; - if ( $.isArray(this.options.source) ) { - array = this.options.source; - this.source = function( request, response ) { - response( $.ui.autocomplete.filter( array, request.term ) ); - }; - } else if ( typeof this.options.source === "string" ) { - url = this.options.source; - this.source = function( request, response ) { - if ( that.xhr ) { - that.xhr.abort(); - } - that.xhr = $.ajax({ - url: url, - data: request, - dataType: "json", - success: function( data ) { - response( data ); - }, - error: function() { - response( [] ); - } - }); - }; - } else { - this.source = this.options.source; - } - }, + _scrollIntoView: function( item ) { + var borderTop, paddingTop, offset, scroll, elementHeight, itemHeight; + if ( this._hasScroll() ) { + borderTop = parseFloat( $.css( this.activeMenu[0], "borderTopWidth" ) ) || 0; + paddingTop = parseFloat( $.css( this.activeMenu[0], "paddingTop" ) ) || 0; + offset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop; + scroll = this.activeMenu.scrollTop(); + elementHeight = this.activeMenu.height(); + itemHeight = item.outerHeight(); - _searchTimeout: function( event ) { - clearTimeout( this.searching ); - this.searching = this._delay(function() { - // only search if the value has changed - if ( this.term !== this._value() ) { - this.selectedItem = null; - this.search( null, event ); + if ( offset < 0 ) { + this.activeMenu.scrollTop( scroll + offset ); + } else if ( offset + itemHeight > elementHeight ) { + this.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight ); } - }, this.options.delay ); + } }, - search: function( value, event ) { - value = value != null ? value : this._value(); - - // always save the actual value, not the one passed as an argument - this.term = this._value(); + blur: function( event, fromFocus ) { + if ( !fromFocus ) { + clearTimeout( this.timer ); + } - if ( value.length < this.options.minLength ) { - return this.close( event ); + if ( !this.active ) { + return; } - if ( this._trigger( "search", event ) === false ) { + this.active.removeClass( "ui-state-focus" ); + this.active = null; + + this._trigger( "blur", event, { item: this.active } ); + }, + + _startOpening: function( submenu ) { + clearTimeout( this.timer ); + + // Don't open if already open fixes a Firefox bug that caused a .5 pixel + // shift in the submenu position when mousing over the carat icon + if ( submenu.attr( "aria-hidden" ) !== "true" ) { return; } - return this._search( value ); + this.timer = this._delay(function() { + this._close(); + this._open( submenu ); + }, this.delay ); }, - _search: function( value ) { - this.pending++; - this.element.addClass( "ui-autocomplete-loading" ); - this.cancelSearch = false; + _open: function( submenu ) { + var position = $.extend({ + of: this.active + }, this.options.position ); - this.source( { term: value }, this._response() ); + clearTimeout( this.timer ); + this.element.find( ".ui-menu" ).not( submenu.parents( ".ui-menu" ) ) + .hide() + .attr( "aria-hidden", "true" ); + + submenu + .show() + .removeAttr( "aria-hidden" ) + .attr( "aria-expanded", "true" ) + .position( position ); }, - _response: function() { - var index = ++this.requestIndex; + collapseAll: function( event, all ) { + clearTimeout( this.timer ); + this.timer = this._delay(function() { + // If we were passed an event, look for the submenu that contains the event + var currentMenu = all ? this.element : + $( event && event.target ).closest( this.element.find( ".ui-menu" ) ); - return $.proxy(function( content ) { - if ( index === this.requestIndex ) { - this.__response( content ); + // If we found no valid submenu ancestor, use the main menu to close all sub menus anyway + if ( !currentMenu.length ) { + currentMenu = this.element; } - this.pending--; - if ( !this.pending ) { - this.element.removeClass( "ui-autocomplete-loading" ); - } - }, this ); + this._close( currentMenu ); + + this.blur( event ); + this.activeMenu = currentMenu; + }, this.delay ); }, - __response: function( content ) { - if ( content ) { - content = this._normalize( content ); - } - this._trigger( "response", null, { content: content } ); - if ( !this.options.disabled && content && content.length && !this.cancelSearch ) { - this._suggest( content ); - this._trigger( "open" ); - } else { - // use ._close() instead of .close() so we don't cancel future searches - this._close(); + // With no arguments, closes the currently active menu - if nothing is active + // it closes all menus. If passed an argument, it will search for menus BELOW + _close: function( startMenu ) { + if ( !startMenu ) { + startMenu = this.active ? this.active.parent() : this.element; } - }, - close: function( event ) { - this.cancelSearch = true; - this._close( event ); + startMenu + .find( ".ui-menu" ) + .hide() + .attr( "aria-hidden", "true" ) + .attr( "aria-expanded", "false" ) + .end() + .find( ".ui-state-active" ).not( ".ui-state-focus" ) + .removeClass( "ui-state-active" ); }, - _close: function( event ) { - if ( this.menu.element.is( ":visible" ) ) { - this.menu.element.hide(); - this.menu.blur(); - this.isNewMenu = true; - this._trigger( "close", event ); - } + _closeOnDocumentClick: function( event ) { + return !$( event.target ).closest( ".ui-menu" ).length; }, - _change: function( event ) { - if ( this.previous !== this._value() ) { - this._trigger( "change", event, { item: this.selectedItem } ); - } + _isDivider: function( item ) { + + // Match hyphen, em dash, en dash + return !/[^\-\u2014\u2013\s]/.test( item.text() ); }, - _normalize: function( items ) { - // assume all items have the right format when the first item is complete - if ( items.length && items[0].label && items[0].value ) { - return items; + collapse: function( event ) { + var newItem = this.active && + this.active.parent().closest( ".ui-menu-item", this.element ); + if ( newItem && newItem.length ) { + this._close(); + this.focus( event, newItem ); } - return $.map( items, function( item ) { - if ( typeof item === "string" ) { - return { - label: item, - value: item - }; - } - return $.extend({ - label: item.label || item.value, - value: item.value || item.label - }, item ); - }); }, - _suggest: function( items ) { - var ul = this.menu.element.empty(); - this._renderMenu( ul, items ); - this.isNewMenu = true; - this.menu.refresh(); + expand: function( event ) { + var newItem = this.active && + this.active + .children( ".ui-menu " ) + .find( this.options.items ) + .first(); - // size and position menu - ul.show(); - this._resizeMenu(); - ul.position( $.extend({ - of: this.element - }, this.options.position )); + if ( newItem && newItem.length ) { + this._open( newItem.parent() ); - if ( this.options.autoFocus ) { - this.menu.next(); + // Delay so Firefox will not hide activedescendant change in expanding submenu from AT + this._delay(function() { + this.focus( event, newItem ); + }); } }, - _resizeMenu: function() { - var ul = this.menu.element; - ul.outerWidth( Math.max( - // Firefox wraps long text (possibly a rounding bug) - // so we add 1px to avoid the wrapping (#7513) - ul.width( "" ).outerWidth() + 1, - this.element.outerWidth() - ) ); + next: function( event ) { + this._move( "next", "first", event ); }, - _renderMenu: function( ul, items ) { - var that = this; - $.each( items, function( index, item ) { - that._renderItemData( ul, item ); - }); + previous: function( event ) { + this._move( "prev", "last", event ); }, - _renderItemData: function( ul, item ) { - return this._renderItem( ul, item ).data( "ui-autocomplete-item", item ); + isFirstItem: function() { + return this.active && !this.active.prevAll( ".ui-menu-item" ).length; }, - _renderItem: function( ul, item ) { - return $( "
    • " ) - .append( $( "" ).text( item.label ) ) - .appendTo( ul ); + isLastItem: function() { + return this.active && !this.active.nextAll( ".ui-menu-item" ).length; }, - _move: function( direction, event ) { - if ( !this.menu.element.is( ":visible" ) ) { - this.search( null, event ); - return; + _move: function( direction, filter, event ) { + var next; + if ( this.active ) { + if ( direction === "first" || direction === "last" ) { + next = this.active + [ direction === "first" ? "prevAll" : "nextAll" ]( ".ui-menu-item" ) + .eq( -1 ); + } else { + next = this.active + [ direction + "All" ]( ".ui-menu-item" ) + .eq( 0 ); + } } - if ( this.menu.isFirstItem() && /^previous/.test( direction ) || - this.menu.isLastItem() && /^next/.test( direction ) ) { - this._value( this.term ); - this.menu.blur(); - return; + if ( !next || !next.length || !this.active ) { + next = this.activeMenu.find( this.options.items )[ filter ](); } - this.menu[ direction ]( event ); - }, - widget: function() { - return this.menu.element; + this.focus( event, next ); }, - _value: function() { - return this.valueMethod.apply( this.element, arguments ); - }, + nextPage: function( event ) { + var item, base, height; - _keyEvent: function( keyEvent, event ) { - if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) { - this._move( keyEvent, event ); - - // prevents moving cursor to beginning/end of the text field in some browsers - event.preventDefault(); + if ( !this.active ) { + this.next( event ); + return; } - } -}); - -$.extend( $.ui.autocomplete, { - escapeRegex: function( value ) { - return value.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&"); - }, - filter: function(array, term) { - var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" ); - return $.grep( array, function(value) { - return matcher.test( value.label || value.value || value ); - }); - } -}); - + if ( this.isLastItem() ) { + return; + } + if ( this._hasScroll() ) { + base = this.active.offset().top; + height = this.element.height(); + this.active.nextAll( ".ui-menu-item" ).each(function() { + item = $( this ); + return item.offset().top - base - height < 0; + }); -// live region extension, adding a `messages` option -// NOTE: This is an experimental API. We are still investigating -// a full solution for string manipulation and internationalization. -$.widget( "ui.autocomplete", $.ui.autocomplete, { - options: { - messages: { - noResults: "No search results.", - results: function( amount ) { - return amount + ( amount > 1 ? " results are" : " result is" ) + - " available, use up and down arrow keys to navigate."; - } + this.focus( event, item ); + } else { + this.focus( event, this.activeMenu.find( this.options.items ) + [ !this.active ? "first" : "last" ]() ); } }, - __response: function( content ) { - var message; - this._superApply( arguments ); - if ( this.options.disabled || this.cancelSearch ) { + previousPage: function( event ) { + var item, base, height; + if ( !this.active ) { + this.next( event ); return; } - if ( content && content.length ) { - message = this.options.messages.results( content.length ); + if ( this.isFirstItem() ) { + return; + } + if ( this._hasScroll() ) { + base = this.active.offset().top; + height = this.element.height(); + this.active.prevAll( ".ui-menu-item" ).each(function() { + item = $( this ); + return item.offset().top - base + height > 0; + }); + + this.focus( event, item ); } else { - message = this.options.messages.noResults; + this.focus( event, this.activeMenu.find( this.options.items ).first() ); + } + }, + + _hasScroll: function() { + return this.element.outerHeight() < this.element.prop( "scrollHeight" ); + }, + + select: function( event ) { + // TODO: It should never be possible to not have an active item at this + // point, but the tests don't trigger mouseenter before click. + this.active = this.active || $( event.target ).closest( ".ui-menu-item" ); + var ui = { item: this.active }; + if ( !this.active.has( ".ui-menu" ).length ) { + this.collapseAll( event, true ); } - this.liveRegion.text( message ); + this._trigger( "select", event, ui ); } }); -}( jQuery )); -(function( $, undefined ) { -var lastActive, - baseClasses = "ui-button ui-widget ui-state-default ui-corner-all", - typeClasses = "ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only", - formResetHandler = function() { - var form = $( this ); - setTimeout(function() { - form.find( ":ui-button" ).button( "refresh" ); - }, 1 ); - }, - radioGroup = function( radio ) { - var name = radio.name, - form = radio.form, - radios = $( [] ); - if ( name ) { - name = name.replace( /'/g, "\\'" ); - if ( form ) { - radios = $( form ).find( "[name='" + name + "']" ); - } else { - radios = $( "[name='" + name + "']", radio.ownerDocument ) - .filter(function() { - return !this.form; - }); - } - } - return radios; - }; +/*! + * jQuery UI Autocomplete 1.11.0 + * http://jqueryui.com + * + * Copyright 2014 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/autocomplete/ + */ -$.widget( "ui.button", { - version: "1.10.4", - defaultElement: "").addClass(this._triggerClass). - html(!buttonImage ? buttonText : $("").attr( - { src:buttonImage, alt:buttonText, title:buttonText }))); - input[isRTL ? "before" : "after"](inst.trigger); - inst.trigger.click(function() { - if ($.datepicker._datepickerShowing && $.datepicker._lastInput === input[0]) { - $.datepicker._hideDatepicker(); - } else if ($.datepicker._datepickerShowing && $.datepicker._lastInput !== input[0]) { - $.datepicker._hideDatepicker(); - $.datepicker._showDatepicker(input[0]); - } else { - $.datepicker._showDatepicker(input[0]); - } - return false; - }); - } - }, - - /* Apply the maximum length for the date format. */ - _autoSize: function(inst) { - if (this._get(inst, "autoSize") && !inst.inline) { - var findMax, max, maxI, i, - date = new Date(2009, 12 - 1, 20), // Ensure double digits - dateFormat = this._get(inst, "dateFormat"); - - if (dateFormat.match(/[DM]/)) { - findMax = function(names) { - max = 0; - maxI = 0; - for (i = 0; i < names.length; i++) { - if (names[i].length > max) { - max = names[i].length; - maxI = i; - } - } - return maxI; - }; - date.setMonth(findMax(this._get(inst, (dateFormat.match(/MM/) ? - "monthNames" : "monthNamesShort")))); - date.setDate(findMax(this._get(inst, (dateFormat.match(/DD/) ? - "dayNames" : "dayNamesShort"))) + 20 - date.getDay()); - } - inst.input.attr("size", this._formatDate(inst, date).length); +$.extend( $.ui.autocomplete, { + escapeRegex: function( value ) { + return value.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" ); + }, + filter: function( array, term ) { + var matcher = new RegExp( $.ui.autocomplete.escapeRegex( term ), "i" ); + return $.grep( array, function( value ) { + return matcher.test( value.label || value.value || value ); + }); + } +}); + +// live region extension, adding a `messages` option +// NOTE: This is an experimental API. We are still investigating +// a full solution for string manipulation and internationalization. +$.widget( "ui.autocomplete", $.ui.autocomplete, { + options: { + messages: { + noResults: "No search results.", + results: function( amount ) { + return amount + ( amount > 1 ? " results are" : " result is" ) + + " available, use up and down arrow keys to navigate."; + } } }, - /* Attach an inline date picker to a div. */ - _inlineDatepicker: function(target, inst) { - var divSpan = $(target); - if (divSpan.hasClass(this.markerClassName)) { + __response: function( content ) { + var message; + this._superApply( arguments ); + if ( this.options.disabled || this.cancelSearch ) { return; } - divSpan.addClass(this.markerClassName).append(inst.dpDiv); - $.data(target, PROP_NAME, inst); - this._setDate(inst, this._getDefaultDate(inst), true); - this._updateDatepicker(inst); - this._updateAlternate(inst); - //If disabled option is true, disable the datepicker before showing it (see ticket #5665) - if( inst.settings.disabled ) { - this._disableDatepicker( target ); + if ( content && content.length ) { + message = this.options.messages.results( content.length ); + } else { + message = this.options.messages.noResults; } - // Set display:block in place of inst.dpDiv.show() which won't work on disconnected elements - // http://bugs.jqueryui.com/ticket/7552 - A Datepicker created on a detached div has zero height - inst.dpDiv.css( "display", "block" ); - }, - - /* Pop-up the date picker in a "dialog" box. - * @param input element - ignored - * @param date string or Date - the initial date to display - * @param onSelect function - the function to call when a date is selected - * @param settings object - update the dialog date picker instance's settings (anonymous object) - * @param pos int[2] - coordinates for the dialog's position within the screen or - * event - with x/y coordinates or - * leave empty for default (screen centre) - * @return the manager object - */ - _dialogDatepicker: function(input, date, onSelect, settings, pos) { - var id, browserWidth, browserHeight, scrollX, scrollY, - inst = this._dialogInst; // internal instance + this.liveRegion.children().hide(); + $( "
      " ).text( message ).appendTo( this.liveRegion ); + } +}); - if (!inst) { - this.uuid += 1; - id = "dp" + this.uuid; - this._dialogInput = $(""); - this._dialogInput.keydown(this._doKeyDown); - $("body").append(this._dialogInput); - inst = this._dialogInst = this._newInst(this._dialogInput, false); - inst.settings = {}; - $.data(this._dialogInput[0], PROP_NAME, inst); - } - extendRemove(inst.settings, settings || {}); - date = (date && date.constructor === Date ? this._formatDate(inst, date) : date); - this._dialogInput.val(date); +var autocomplete = $.ui.autocomplete; - this._pos = (pos ? (pos.length ? pos : [pos.pageX, pos.pageY]) : null); - if (!this._pos) { - browserWidth = document.documentElement.clientWidth; - browserHeight = document.documentElement.clientHeight; - scrollX = document.documentElement.scrollLeft || document.body.scrollLeft; - scrollY = document.documentElement.scrollTop || document.body.scrollTop; - this._pos = // should use actual width/height below - [(browserWidth / 2) - 100 + scrollX, (browserHeight / 2) - 150 + scrollY]; - } - // move input on screen for focus, but hidden behind dialog - this._dialogInput.css("left", (this._pos[0] + 20) + "px").css("top", this._pos[1] + "px"); - inst.settings.onSelect = onSelect; - this._inDialog = true; - this.dpDiv.addClass(this._dialogClass); - this._showDatepicker(this._dialogInput[0]); - if ($.blockUI) { - $.blockUI(this.dpDiv); - } - $.data(this._dialogInput[0], PROP_NAME, inst); - return this; - }, +/*! + * jQuery UI Button 1.11.0 + * http://jqueryui.com + * + * Copyright 2014 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/button/ + */ - /* Detach a datepicker from its control. - * @param target element - the target input field or division or span - */ - _destroyDatepicker: function(target) { - var nodeName, - $target = $(target), - inst = $.data(target, PROP_NAME); - if (!$target.hasClass(this.markerClassName)) { - return; +var lastActive, + baseClasses = "ui-button ui-widget ui-state-default ui-corner-all", + typeClasses = "ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only", + formResetHandler = function() { + var form = $( this ); + setTimeout(function() { + form.find( ":ui-button" ).button( "refresh" ); + }, 1 ); + }, + radioGroup = function( radio ) { + var name = radio.name, + form = radio.form, + radios = $( [] ); + if ( name ) { + name = name.replace( /'/g, "\\'" ); + if ( form ) { + radios = $( form ).find( "[name='" + name + "'][type=radio]" ); + } else { + radios = $( "[name='" + name + "'][type=radio]", radio.ownerDocument ) + .filter(function() { + return !this.form; + }); + } } + return radios; + }; - nodeName = target.nodeName.toLowerCase(); - $.removeData(target, PROP_NAME); - if (nodeName === "input") { - inst.append.remove(); - inst.trigger.remove(); - $target.removeClass(this.markerClassName). - unbind("focus", this._showDatepicker). - unbind("keydown", this._doKeyDown). - unbind("keypress", this._doKeyPress). - unbind("keyup", this._doKeyUp); - } else if (nodeName === "div" || nodeName === "span") { - $target.removeClass(this.markerClassName).empty(); +$.widget( "ui.button", { + version: "1.11.0", + defaultElement: "").addClass(this._triggerClass). + html(!buttonImage ? buttonText : $("").attr( + { src:buttonImage, alt:buttonText, title:buttonText }))); + input[isRTL ? "before" : "after"](inst.trigger); + inst.trigger.click(function() { + if ($.datepicker._datepickerShowing && $.datepicker._lastInput === input[0]) { + $.datepicker._hideDatepicker(); + } else if ($.datepicker._datepickerShowing && $.datepicker._lastInput !== input[0]) { + $.datepicker._hideDatepicker(); + $.datepicker._showDatepicker(input[0]); + } else { + $.datepicker._showDatepicker(input[0]); + } + return false; + }); + } + }, - onClose = this._get(inst, "onClose"); - if (onClose) { - onClose.apply((inst.input ? inst.input[0] : null), [(inst.input ? inst.input.val() : ""), inst]); - } + /* Apply the maximum length for the date format. */ + _autoSize: function(inst) { + if (this._get(inst, "autoSize") && !inst.inline) { + var findMax, max, maxI, i, + date = new Date(2009, 12 - 1, 20), // Ensure double digits + dateFormat = this._get(inst, "dateFormat"); - this._lastInput = null; - if (this._inDialog) { - this._dialogInput.css({ position: "absolute", left: "0", top: "-100px" }); - if ($.blockUI) { - $.unblockUI(); - $("body").append(this.dpDiv); - } + if (dateFormat.match(/[DM]/)) { + findMax = function(names) { + max = 0; + maxI = 0; + for (i = 0; i < names.length; i++) { + if (names[i].length > max) { + max = names[i].length; + maxI = i; + } + } + return maxI; + }; + date.setMonth(findMax(this._get(inst, (dateFormat.match(/MM/) ? + "monthNames" : "monthNamesShort")))); + date.setDate(findMax(this._get(inst, (dateFormat.match(/DD/) ? + "dayNames" : "dayNamesShort"))) + 20 - date.getDay()); } - this._inDialog = false; + inst.input.attr("size", this._formatDate(inst, date).length); } }, - /* Tidy up after a dialog display. */ - _tidyDialog: function(inst) { - inst.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar"); + /* Attach an inline date picker to a div. */ + _inlineDatepicker: function(target, inst) { + var divSpan = $(target); + if (divSpan.hasClass(this.markerClassName)) { + return; + } + divSpan.addClass(this.markerClassName).append(inst.dpDiv); + $.data(target, "datepicker", inst); + this._setDate(inst, this._getDefaultDate(inst), true); + this._updateDatepicker(inst); + this._updateAlternate(inst); + //If disabled option is true, disable the datepicker before showing it (see ticket #5665) + if( inst.settings.disabled ) { + this._disableDatepicker( target ); + } + // Set display:block in place of inst.dpDiv.show() which won't work on disconnected elements + // http://bugs.jqueryui.com/ticket/7552 - A Datepicker created on a detached div has zero height + inst.dpDiv.css( "display", "block" ); }, - /* Close date picker if clicked elsewhere. */ - _checkExternalClick: function(event) { - if (!$.datepicker._curInst) { - return; + /* Pop-up the date picker in a "dialog" box. + * @param input element - ignored + * @param date string or Date - the initial date to display + * @param onSelect function - the function to call when a date is selected + * @param settings object - update the dialog date picker instance's settings (anonymous object) + * @param pos int[2] - coordinates for the dialog's position within the screen or + * event - with x/y coordinates or + * leave empty for default (screen centre) + * @return the manager object + */ + _dialogDatepicker: function(input, date, onSelect, settings, pos) { + var id, browserWidth, browserHeight, scrollX, scrollY, + inst = this._dialogInst; // internal instance + + if (!inst) { + this.uuid += 1; + id = "dp" + this.uuid; + this._dialogInput = $(""); + this._dialogInput.keydown(this._doKeyDown); + $("body").append(this._dialogInput); + inst = this._dialogInst = this._newInst(this._dialogInput, false); + inst.settings = {}; + $.data(this._dialogInput[0], "datepicker", inst); } + datepicker_extendRemove(inst.settings, settings || {}); + date = (date && date.constructor === Date ? this._formatDate(inst, date) : date); + this._dialogInput.val(date); - var $target = $(event.target), - inst = $.datepicker._getInst($target[0]); + this._pos = (pos ? (pos.length ? pos : [pos.pageX, pos.pageY]) : null); + if (!this._pos) { + browserWidth = document.documentElement.clientWidth; + browserHeight = document.documentElement.clientHeight; + scrollX = document.documentElement.scrollLeft || document.body.scrollLeft; + scrollY = document.documentElement.scrollTop || document.body.scrollTop; + this._pos = // should use actual width/height below + [(browserWidth / 2) - 100 + scrollX, (browserHeight / 2) - 150 + scrollY]; + } - if ( ( ( $target[0].id !== $.datepicker._mainDivId && - $target.parents("#" + $.datepicker._mainDivId).length === 0 && - !$target.hasClass($.datepicker.markerClassName) && - !$target.closest("." + $.datepicker._triggerClass).length && - $.datepicker._datepickerShowing && !($.datepicker._inDialog && $.blockUI) ) ) || - ( $target.hasClass($.datepicker.markerClassName) && $.datepicker._curInst !== inst ) ) { - $.datepicker._hideDatepicker(); + // move input on screen for focus, but hidden behind dialog + this._dialogInput.css("left", (this._pos[0] + 20) + "px").css("top", this._pos[1] + "px"); + inst.settings.onSelect = onSelect; + this._inDialog = true; + this.dpDiv.addClass(this._dialogClass); + this._showDatepicker(this._dialogInput[0]); + if ($.blockUI) { + $.blockUI(this.dpDiv); } + $.data(this._dialogInput[0], "datepicker", inst); + return this; }, - /* Adjust one of the date sub-fields. */ - _adjustDate: function(id, offset, period) { - var target = $(id), - inst = this._getInst(target[0]); + /* Detach a datepicker from its control. + * @param target element - the target input field or division or span + */ + _destroyDatepicker: function(target) { + var nodeName, + $target = $(target), + inst = $.data(target, "datepicker"); - if (this._isDisabledDatepicker(target[0])) { + if (!$target.hasClass(this.markerClassName)) { return; } - this._adjustInstDate(inst, offset + - (period === "M" ? this._get(inst, "showCurrentAtPos") : 0), // undo positioning - period); - this._updateDatepicker(inst); - }, - - /* Action for current link. */ - _gotoToday: function(id) { - var date, - target = $(id), - inst = this._getInst(target[0]); - if (this._get(inst, "gotoCurrent") && inst.currentDay) { - inst.selectedDay = inst.currentDay; - inst.drawMonth = inst.selectedMonth = inst.currentMonth; - inst.drawYear = inst.selectedYear = inst.currentYear; - } else { - date = new Date(); - inst.selectedDay = date.getDate(); - inst.drawMonth = inst.selectedMonth = date.getMonth(); - inst.drawYear = inst.selectedYear = date.getFullYear(); + nodeName = target.nodeName.toLowerCase(); + $.removeData(target, "datepicker"); + if (nodeName === "input") { + inst.append.remove(); + inst.trigger.remove(); + $target.removeClass(this.markerClassName). + unbind("focus", this._showDatepicker). + unbind("keydown", this._doKeyDown). + unbind("keypress", this._doKeyPress). + unbind("keyup", this._doKeyUp); + } else if (nodeName === "div" || nodeName === "span") { + $target.removeClass(this.markerClassName).empty(); } - this._notifyChange(inst); - this._adjustDate(target); }, - /* Action for selecting a new month/year. */ - _selectMonthYear: function(id, select, period) { - var target = $(id), - inst = this._getInst(target[0]); + /* Enable the date picker to a jQuery selection. + * @param target element - the target input field or division or span + */ + _enableDatepicker: function(target) { + var nodeName, inline, + $target = $(target), + inst = $.data(target, "datepicker"); - inst["selected" + (period === "M" ? "Month" : "Year")] = - inst["draw" + (period === "M" ? "Month" : "Year")] = - parseInt(select.options[select.selectedIndex].value,10); + if (!$target.hasClass(this.markerClassName)) { + return; + } - this._notifyChange(inst); - this._adjustDate(target); + nodeName = target.nodeName.toLowerCase(); + if (nodeName === "input") { + target.disabled = false; + inst.trigger.filter("button"). + each(function() { this.disabled = false; }).end(). + filter("img").css({opacity: "1.0", cursor: ""}); + } else if (nodeName === "div" || nodeName === "span") { + inline = $target.children("." + this._inlineClass); + inline.children().removeClass("ui-state-disabled"); + inline.find("select.ui-datepicker-month, select.ui-datepicker-year"). + prop("disabled", false); + } + this._disabledInputs = $.map(this._disabledInputs, + function(value) { return (value === target ? null : value); }); // delete entry }, - /* Action for selecting a day. */ - _selectDay: function(id, month, year, td) { - var inst, - target = $(id); + /* Disable the date picker to a jQuery selection. + * @param target element - the target input field or division or span + */ + _disableDatepicker: function(target) { + var nodeName, inline, + $target = $(target), + inst = $.data(target, "datepicker"); - if ($(td).hasClass(this._unselectableClass) || this._isDisabledDatepicker(target[0])) { + if (!$target.hasClass(this.markerClassName)) { return; } - inst = this._getInst(target[0]); - inst.selectedDay = inst.currentDay = $("a", td).html(); - inst.selectedMonth = inst.currentMonth = month; - inst.selectedYear = inst.currentYear = year; - this._selectDate(id, this._formatDate(inst, - inst.currentDay, inst.currentMonth, inst.currentYear)); + nodeName = target.nodeName.toLowerCase(); + if (nodeName === "input") { + target.disabled = true; + inst.trigger.filter("button"). + each(function() { this.disabled = true; }).end(). + filter("img").css({opacity: "0.5", cursor: "default"}); + } else if (nodeName === "div" || nodeName === "span") { + inline = $target.children("." + this._inlineClass); + inline.children().addClass("ui-state-disabled"); + inline.find("select.ui-datepicker-month, select.ui-datepicker-year"). + prop("disabled", true); + } + this._disabledInputs = $.map(this._disabledInputs, + function(value) { return (value === target ? null : value); }); // delete entry + this._disabledInputs[this._disabledInputs.length] = target; }, - /* Erase the input field and hide the date picker. */ - _clearDate: function(id) { - var target = $(id); - this._selectDate(target, ""); + /* Is the first field in a jQuery collection disabled as a datepicker? + * @param target element - the target input field or division or span + * @return boolean - true if disabled, false if enabled + */ + _isDisabledDatepicker: function(target) { + if (!target) { + return false; + } + for (var i = 0; i < this._disabledInputs.length; i++) { + if (this._disabledInputs[i] === target) { + return true; + } + } + return false; }, - /* Update the input field with the selected date. */ - _selectDate: function(id, dateStr) { - var onSelect, - target = $(id), - inst = this._getInst(target[0]); + /* Retrieve the instance data for the target control. + * @param target element - the target input field or division or span + * @return object - the associated instance data + * @throws error if a jQuery problem getting data + */ + _getInst: function(target) { + try { + return $.data(target, "datepicker"); + } + catch (err) { + throw "Missing instance data for this datepicker"; + } + }, - dateStr = (dateStr != null ? dateStr : this._formatDate(inst)); - if (inst.input) { - inst.input.val(dateStr); + /* Update or retrieve the settings for a date picker attached to an input field or division. + * @param target element - the target input field or division or span + * @param name object - the new settings to update or + * string - the name of the setting to change or retrieve, + * when retrieving also "all" for all instance settings or + * "defaults" for all global defaults + * @param value any - the new value for the setting + * (omit if above is an object or to retrieve a value) + */ + _optionDatepicker: function(target, name, value) { + var settings, date, minDate, maxDate, + inst = this._getInst(target); + + if (arguments.length === 2 && typeof name === "string") { + return (name === "defaults" ? $.extend({}, $.datepicker._defaults) : + (inst ? (name === "all" ? $.extend({}, inst.settings) : + this._get(inst, name)) : null)); } - this._updateAlternate(inst); - onSelect = this._get(inst, "onSelect"); - if (onSelect) { - onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]); // trigger custom callback - } else if (inst.input) { - inst.input.trigger("change"); // fire the change event + settings = name || {}; + if (typeof name === "string") { + settings = {}; + settings[name] = value; } - if (inst.inline){ - this._updateDatepicker(inst); - } else { - this._hideDatepicker(); - this._lastInput = inst.input[0]; - if (typeof(inst.input[0]) !== "object") { - inst.input.focus(); // restore focus + if (inst) { + if (this._curInst === inst) { + this._hideDatepicker(); } - this._lastInput = null; + + date = this._getDateDatepicker(target, true); + minDate = this._getMinMaxDate(inst, "min"); + maxDate = this._getMinMaxDate(inst, "max"); + datepicker_extendRemove(inst.settings, settings); + // reformat the old minDate/maxDate values if dateFormat changes and a new minDate/maxDate isn't provided + if (minDate !== null && settings.dateFormat !== undefined && settings.minDate === undefined) { + inst.settings.minDate = this._formatDate(inst, minDate); + } + if (maxDate !== null && settings.dateFormat !== undefined && settings.maxDate === undefined) { + inst.settings.maxDate = this._formatDate(inst, maxDate); + } + if ( "disabled" in settings ) { + if ( settings.disabled ) { + this._disableDatepicker(target); + } else { + this._enableDatepicker(target); + } + } + this._attachments($(target), inst); + this._autoSize(inst); + this._setDate(inst, date); + this._updateAlternate(inst); + this._updateDatepicker(inst); } }, - /* Update any alternate field to synchronise with the main field. */ - _updateAlternate: function(inst) { - var altFormat, date, dateStr, - altField = this._get(inst, "altField"); + // change method deprecated + _changeDatepicker: function(target, name, value) { + this._optionDatepicker(target, name, value); + }, - if (altField) { // update alternate field too - altFormat = this._get(inst, "altFormat") || this._get(inst, "dateFormat"); - date = this._getDate(inst); - dateStr = this.formatDate(altFormat, date, this._getFormatConfig(inst)); - $(altField).each(function() { $(this).val(dateStr); }); + /* Redraw the date picker attached to an input field or division. + * @param target element - the target input field or division or span + */ + _refreshDatepicker: function(target) { + var inst = this._getInst(target); + if (inst) { + this._updateDatepicker(inst); } }, - /* Set as beforeShowDay function to prevent selection of weekends. - * @param date Date - the date to customise - * @return [boolean, string] - is this date selectable?, what is its CSS class? + /* Set the dates for a jQuery selection. + * @param target element - the target input field or division or span + * @param date Date - the new date */ - noWeekends: function(date) { - var day = date.getDay(); - return [(day > 0 && day < 6), ""]; + _setDateDatepicker: function(target, date) { + var inst = this._getInst(target); + if (inst) { + this._setDate(inst, date); + this._updateDatepicker(inst); + this._updateAlternate(inst); + } }, - /* Set as calculateWeek to determine the week of the year based on the ISO 8601 definition. - * @param date Date - the date to get the week for - * @return number - the number of the week within the year that contains this date + /* Get the date(s) for the first entry in a jQuery selection. + * @param target element - the target input field or division or span + * @param noDefault boolean - true if no default date is to be used + * @return Date - the current date */ - iso8601Week: function(date) { - var time, - checkDate = new Date(date.getTime()); + _getDateDatepicker: function(target, noDefault) { + var inst = this._getInst(target); + if (inst && !inst.inline) { + this._setDateFromField(inst, noDefault); + } + return (inst ? this._getDate(inst) : null); + }, - // Find Thursday of this week starting on Monday - checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); + /* Handle keystrokes. */ + _doKeyDown: function(event) { + var onSelect, dateStr, sel, + inst = $.datepicker._getInst(event.target), + handled = true, + isRTL = inst.dpDiv.is(".ui-datepicker-rtl"); - time = checkDate.getTime(); - checkDate.setMonth(0); // Compare with Jan 1 - checkDate.setDate(1); - return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1; - }, + inst._keyEvent = true; + if ($.datepicker._datepickerShowing) { + switch (event.keyCode) { + case 9: $.datepicker._hideDatepicker(); + handled = false; + break; // hide on tab out + case 13: sel = $("td." + $.datepicker._dayOverClass + ":not(." + + $.datepicker._currentClass + ")", inst.dpDiv); + if (sel[0]) { + $.datepicker._selectDay(event.target, inst.selectedMonth, inst.selectedYear, sel[0]); + } - /* Parse a string value into a date object. - * See formatDate below for the possible formats. - * - * @param format string - the expected format of the date - * @param value string - the date in the above format - * @param settings Object - attributes include: - * shortYearCutoff number - the cutoff year for determining the century (optional) - * dayNamesShort string[7] - abbreviated names of the days from Sunday (optional) - * dayNames string[7] - names of the days from Sunday (optional) - * monthNamesShort string[12] - abbreviated names of the months (optional) - * monthNames string[12] - names of the months (optional) - * @return Date - the extracted date value or null if value is blank - */ - parseDate: function (format, value, settings) { - if (format == null || value == null) { - throw "Invalid arguments"; - } + onSelect = $.datepicker._get(inst, "onSelect"); + if (onSelect) { + dateStr = $.datepicker._formatDate(inst); - value = (typeof value === "object" ? value.toString() : value + ""); - if (value === "") { - return null; - } + // trigger custom callback + onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]); + } else { + $.datepicker._hideDatepicker(); + } - var iFormat, dim, extra, - iValue = 0, - shortYearCutoffTemp = (settings ? settings.shortYearCutoff : null) || this._defaults.shortYearCutoff, - shortYearCutoff = (typeof shortYearCutoffTemp !== "string" ? shortYearCutoffTemp : - new Date().getFullYear() % 100 + parseInt(shortYearCutoffTemp, 10)), - dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort, - dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames, - monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort, - monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames, - year = -1, - month = -1, - day = -1, - doy = -1, - literal = false, - date, - // Check whether a format character is doubled - lookAhead = function(match) { - var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match); - if (matches) { - iFormat++; - } - return matches; - }, - // Extract a number from the string value - getNumber = function(match) { - var isDoubled = lookAhead(match), - size = (match === "@" ? 14 : (match === "!" ? 20 : - (match === "y" && isDoubled ? 4 : (match === "o" ? 3 : 2)))), - digits = new RegExp("^\\d{1," + size + "}"), - num = value.substring(iValue).match(digits); - if (!num) { - throw "Missing number at position " + iValue; - } - iValue += num[0].length; - return parseInt(num[0], 10); - }, - // Extract a name from the string value and convert to an index - getName = function(match, shortNames, longNames) { - var index = -1, - names = $.map(lookAhead(match) ? longNames : shortNames, function (v, k) { - return [ [k, v] ]; - }).sort(function (a, b) { - return -(a[1].length - b[1].length); - }); - - $.each(names, function (i, pair) { - var name = pair[1]; - if (value.substr(iValue, name.length).toLowerCase() === name.toLowerCase()) { - index = pair[0]; - iValue += name.length; - return false; - } - }); - if (index !== -1) { - return index + 1; - } else { - throw "Unknown name at position " + iValue; - } - }, - // Confirm that a literal character matches the string value - checkLiteral = function() { - if (value.charAt(iValue) !== format.charAt(iFormat)) { - throw "Unexpected literal at position " + iValue; - } - iValue++; - }; - - for (iFormat = 0; iFormat < format.length; iFormat++) { - if (literal) { - if (format.charAt(iFormat) === "'" && !lookAhead("'")) { - literal = false; - } else { - checkLiteral(); - } - } else { - switch (format.charAt(iFormat)) { - case "d": - day = getNumber("d"); - break; - case "D": - getName("D", dayNamesShort, dayNames); - break; - case "o": - doy = getNumber("o"); - break; - case "m": - month = getNumber("m"); - break; - case "M": - month = getName("M", monthNamesShort, monthNames); - break; - case "y": - year = getNumber("y"); - break; - case "@": - date = new Date(getNumber("@")); - year = date.getFullYear(); - month = date.getMonth() + 1; - day = date.getDate(); - break; - case "!": - date = new Date((getNumber("!") - this._ticksTo1970) / 10000); - year = date.getFullYear(); - month = date.getMonth() + 1; - day = date.getDate(); + return false; // don't submit the form + case 27: $.datepicker._hideDatepicker(); + break; // hide on escape + case 33: $.datepicker._adjustDate(event.target, (event.ctrlKey ? + -$.datepicker._get(inst, "stepBigMonths") : + -$.datepicker._get(inst, "stepMonths")), "M"); + break; // previous month/year on page up/+ ctrl + case 34: $.datepicker._adjustDate(event.target, (event.ctrlKey ? + +$.datepicker._get(inst, "stepBigMonths") : + +$.datepicker._get(inst, "stepMonths")), "M"); + break; // next month/year on page down/+ ctrl + case 35: if (event.ctrlKey || event.metaKey) { + $.datepicker._clearDate(event.target); + } + handled = event.ctrlKey || event.metaKey; + break; // clear on ctrl or command +end + case 36: if (event.ctrlKey || event.metaKey) { + $.datepicker._gotoToday(event.target); + } + handled = event.ctrlKey || event.metaKey; + break; // current on ctrl or command +home + case 37: if (event.ctrlKey || event.metaKey) { + $.datepicker._adjustDate(event.target, (isRTL ? +1 : -1), "D"); + } + handled = event.ctrlKey || event.metaKey; + // -1 day on ctrl or command +left + if (event.originalEvent.altKey) { + $.datepicker._adjustDate(event.target, (event.ctrlKey ? + -$.datepicker._get(inst, "stepBigMonths") : + -$.datepicker._get(inst, "stepMonths")), "M"); + } + // next month/year on alt +left on Mac break; - case "'": - if (lookAhead("'")){ - checkLiteral(); - } else { - literal = true; + case 38: if (event.ctrlKey || event.metaKey) { + $.datepicker._adjustDate(event.target, -7, "D"); + } + handled = event.ctrlKey || event.metaKey; + break; // -1 week on ctrl or command +up + case 39: if (event.ctrlKey || event.metaKey) { + $.datepicker._adjustDate(event.target, (isRTL ? -1 : +1), "D"); + } + handled = event.ctrlKey || event.metaKey; + // +1 day on ctrl or command +right + if (event.originalEvent.altKey) { + $.datepicker._adjustDate(event.target, (event.ctrlKey ? + +$.datepicker._get(inst, "stepBigMonths") : + +$.datepicker._get(inst, "stepMonths")), "M"); } + // next month/year on alt +right break; - default: - checkLiteral(); - } - } - } - - if (iValue < value.length){ - extra = value.substr(iValue); - if (!/^\s+/.test(extra)) { - throw "Extra/unparsed characters found in date: " + extra; + case 40: if (event.ctrlKey || event.metaKey) { + $.datepicker._adjustDate(event.target, +7, "D"); + } + handled = event.ctrlKey || event.metaKey; + break; // +1 week on ctrl or command +down + default: handled = false; } + } else if (event.keyCode === 36 && event.ctrlKey) { // display the date picker on ctrl+home + $.datepicker._showDatepicker(this); + } else { + handled = false; } - if (year === -1) { - year = new Date().getFullYear(); - } else if (year < 100) { - year += new Date().getFullYear() - new Date().getFullYear() % 100 + - (year <= shortYearCutoff ? 0 : -100); + if (handled) { + event.preventDefault(); + event.stopPropagation(); } + }, - if (doy > -1) { - month = 1; - day = doy; - do { - dim = this._getDaysInMonth(year, month - 1); - if (day <= dim) { - break; - } - month++; - day -= dim; - } while (true); - } + /* Filter entered characters - based on date format. */ + _doKeyPress: function(event) { + var chars, chr, + inst = $.datepicker._getInst(event.target); - date = this._daylightSavingAdjust(new Date(year, month - 1, day)); - if (date.getFullYear() !== year || date.getMonth() + 1 !== month || date.getDate() !== day) { - throw "Invalid date"; // E.g. 31/02/00 + if ($.datepicker._get(inst, "constrainInput")) { + chars = $.datepicker._possibleChars($.datepicker._get(inst, "dateFormat")); + chr = String.fromCharCode(event.charCode == null ? event.keyCode : event.charCode); + return event.ctrlKey || event.metaKey || (chr < " " || !chars || chars.indexOf(chr) > -1); } - return date; }, - /* Standard date formats. */ - ATOM: "yy-mm-dd", // RFC 3339 (ISO 8601) - COOKIE: "D, dd M yy", - ISO_8601: "yy-mm-dd", - RFC_822: "D, d M y", - RFC_850: "DD, dd-M-y", - RFC_1036: "D, d M y", - RFC_1123: "D, d M yy", - RFC_2822: "D, d M yy", - RSS: "D, d M y", // RFC 822 - TICKS: "!", - TIMESTAMP: "@", - W3C: "yy-mm-dd", // ISO 8601 + /* Synchronise manual entry and field/alternate field. */ + _doKeyUp: function(event) { + var date, + inst = $.datepicker._getInst(event.target); - _ticksTo1970: (((1970 - 1) * 365 + Math.floor(1970 / 4) - Math.floor(1970 / 100) + - Math.floor(1970 / 400)) * 24 * 60 * 60 * 10000000), + if (inst.input.val() !== inst.lastVal) { + try { + date = $.datepicker.parseDate($.datepicker._get(inst, "dateFormat"), + (inst.input ? inst.input.val() : null), + $.datepicker._getFormatConfig(inst)); - /* Format a date object into a string value. - * The format can be combinations of the following: - * d - day of month (no leading zero) - * dd - day of month (two digit) - * o - day of year (no leading zeros) - * oo - day of year (three digit) - * D - day name short - * DD - day name long - * m - month of year (no leading zero) - * mm - month of year (two digit) - * M - month name short - * MM - month name long - * y - year (two digit) - * yy - year (four digit) - * @ - Unix timestamp (ms since 01/01/1970) - * ! - Windows ticks (100ns since 01/01/0001) - * "..." - literal text - * '' - single quote - * - * @param format string - the desired format of the date - * @param date Date - the date value to format - * @param settings Object - attributes include: - * dayNamesShort string[7] - abbreviated names of the days from Sunday (optional) - * dayNames string[7] - names of the days from Sunday (optional) - * monthNamesShort string[12] - abbreviated names of the months (optional) - * monthNames string[12] - names of the months (optional) - * @return string - the date in the above format + if (date) { // only if valid + $.datepicker._setDateFromField(inst); + $.datepicker._updateAlternate(inst); + $.datepicker._updateDatepicker(inst); + } + } + catch (err) { + } + } + return true; + }, + + /* Pop-up the date picker for a given input field. + * If false returned from beforeShow event handler do not show. + * @param input element - the input field attached to the date picker or + * event - if triggered by focus */ - formatDate: function (format, date, settings) { - if (!date) { - return ""; + _showDatepicker: function(input) { + input = input.target || input; + if (input.nodeName.toLowerCase() !== "input") { // find from button/image trigger + input = $("input", input.parentNode)[0]; } - var iFormat, - dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort, - dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames, - monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort, - monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames, - // Check whether a format character is doubled - lookAhead = function(match) { - var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match); - if (matches) { - iFormat++; - } - return matches; - }, - // Format a number, with leading zero if necessary - formatNumber = function(match, value, len) { - var num = "" + value; - if (lookAhead(match)) { - while (num.length < len) { - num = "0" + num; - } - } - return num; - }, - // Format a name, short or long as requested - formatName = function(match, value, shortNames, longNames) { - return (lookAhead(match) ? longNames[value] : shortNames[value]); - }, - output = "", - literal = false; - - if (date) { - for (iFormat = 0; iFormat < format.length; iFormat++) { - if (literal) { - if (format.charAt(iFormat) === "'" && !lookAhead("'")) { - literal = false; - } else { - output += format.charAt(iFormat); - } - } else { - switch (format.charAt(iFormat)) { - case "d": - output += formatNumber("d", date.getDate(), 2); - break; - case "D": - output += formatName("D", date.getDay(), dayNamesShort, dayNames); - break; - case "o": - output += formatNumber("o", - Math.round((new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime() - new Date(date.getFullYear(), 0, 0).getTime()) / 86400000), 3); - break; - case "m": - output += formatNumber("m", date.getMonth() + 1, 2); - break; - case "M": - output += formatName("M", date.getMonth(), monthNamesShort, monthNames); - break; - case "y": - output += (lookAhead("y") ? date.getFullYear() : - (date.getYear() % 100 < 10 ? "0" : "") + date.getYear() % 100); - break; - case "@": - output += date.getTime(); - break; - case "!": - output += date.getTime() * 10000 + this._ticksTo1970; - break; - case "'": - if (lookAhead("'")) { - output += "'"; - } else { - literal = true; - } - break; - default: - output += format.charAt(iFormat); - } - } - } + if ($.datepicker._isDisabledDatepicker(input) || $.datepicker._lastInput === input) { // already here + return; } - return output; - }, - /* Extract all possible characters from the date format. */ - _possibleChars: function (format) { - var iFormat, - chars = "", - literal = false, - // Check whether a format character is doubled - lookAhead = function(match) { - var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match); - if (matches) { - iFormat++; - } - return matches; - }; + var inst, beforeShow, beforeShowSettings, isFixed, + offset, showAnim, duration; - for (iFormat = 0; iFormat < format.length; iFormat++) { - if (literal) { - if (format.charAt(iFormat) === "'" && !lookAhead("'")) { - literal = false; - } else { - chars += format.charAt(iFormat); - } - } else { - switch (format.charAt(iFormat)) { - case "d": case "m": case "y": case "@": - chars += "0123456789"; - break; - case "D": case "M": - return null; // Accept anything - case "'": - if (lookAhead("'")) { - chars += "'"; - } else { - literal = true; - } - break; - default: - chars += format.charAt(iFormat); - } + inst = $.datepicker._getInst(input); + if ($.datepicker._curInst && $.datepicker._curInst !== inst) { + $.datepicker._curInst.dpDiv.stop(true, true); + if ( inst && $.datepicker._datepickerShowing ) { + $.datepicker._hideDatepicker( $.datepicker._curInst.input[0] ); } } - return chars; - }, - - /* Get a setting value, defaulting if necessary. */ - _get: function(inst, name) { - return inst.settings[name] !== undefined ? - inst.settings[name] : this._defaults[name]; - }, - /* Parse existing date and initialise date picker. */ - _setDateFromField: function(inst, noDefault) { - if (inst.input.val() === inst.lastVal) { + beforeShow = $.datepicker._get(inst, "beforeShow"); + beforeShowSettings = beforeShow ? beforeShow.apply(input, [input, inst]) : {}; + if(beforeShowSettings === false){ return; } + datepicker_extendRemove(inst.settings, beforeShowSettings); - var dateFormat = this._get(inst, "dateFormat"), - dates = inst.lastVal = inst.input ? inst.input.val() : null, - defaultDate = this._getDefaultDate(inst), - date = defaultDate, - settings = this._getFormatConfig(inst); + inst.lastVal = null; + $.datepicker._lastInput = input; + $.datepicker._setDateFromField(inst); - try { - date = this.parseDate(dateFormat, dates, settings) || defaultDate; - } catch (event) { - dates = (noDefault ? "" : dates); + if ($.datepicker._inDialog) { // hide cursor + input.value = ""; + } + if (!$.datepicker._pos) { // position below input + $.datepicker._pos = $.datepicker._findPos(input); + $.datepicker._pos[1] += input.offsetHeight; // add the height } - inst.selectedDay = date.getDate(); - inst.drawMonth = inst.selectedMonth = date.getMonth(); - inst.drawYear = inst.selectedYear = date.getFullYear(); - inst.currentDay = (dates ? date.getDate() : 0); - inst.currentMonth = (dates ? date.getMonth() : 0); - inst.currentYear = (dates ? date.getFullYear() : 0); - this._adjustInstDate(inst); - }, - /* Retrieve the default date shown on opening. */ - _getDefaultDate: function(inst) { - return this._restrictMinMax(inst, - this._determineDate(inst, this._get(inst, "defaultDate"), new Date())); - }, + isFixed = false; + $(input).parents().each(function() { + isFixed |= $(this).css("position") === "fixed"; + return !isFixed; + }); - /* A date may be specified as an exact value or a relative one. */ - _determineDate: function(inst, date, defaultDate) { - var offsetNumeric = function(offset) { - var date = new Date(); - date.setDate(date.getDate() + offset); - return date; - }, - offsetString = function(offset) { - try { - return $.datepicker.parseDate($.datepicker._get(inst, "dateFormat"), - offset, $.datepicker._getFormatConfig(inst)); - } - catch (e) { - // Ignore - } + offset = {left: $.datepicker._pos[0], top: $.datepicker._pos[1]}; + $.datepicker._pos = null; + //to avoid flashes on Firefox + inst.dpDiv.empty(); + // determine sizing offscreen + inst.dpDiv.css({position: "absolute", display: "block", top: "-1000px"}); + $.datepicker._updateDatepicker(inst); + // fix width for dynamic number of date pickers + // and adjust position before showing + offset = $.datepicker._checkOffset(inst, offset, isFixed); + inst.dpDiv.css({position: ($.datepicker._inDialog && $.blockUI ? + "static" : (isFixed ? "fixed" : "absolute")), display: "none", + left: offset.left + "px", top: offset.top + "px"}); - var date = (offset.toLowerCase().match(/^c/) ? - $.datepicker._getDate(inst) : null) || new Date(), - year = date.getFullYear(), - month = date.getMonth(), - day = date.getDate(), - pattern = /([+\-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g, - matches = pattern.exec(offset); + if (!inst.inline) { + showAnim = $.datepicker._get(inst, "showAnim"); + duration = $.datepicker._get(inst, "duration"); + inst.dpDiv.css( "z-index", datepicker_getZindex( $( input ) ) + 1 ); + $.datepicker._datepickerShowing = true; - while (matches) { - switch (matches[2] || "d") { - case "d" : case "D" : - day += parseInt(matches[1],10); break; - case "w" : case "W" : - day += parseInt(matches[1],10) * 7; break; - case "m" : case "M" : - month += parseInt(matches[1],10); - day = Math.min(day, $.datepicker._getDaysInMonth(year, month)); - break; - case "y": case "Y" : - year += parseInt(matches[1],10); - day = Math.min(day, $.datepicker._getDaysInMonth(year, month)); - break; - } - matches = pattern.exec(offset); - } - return new Date(year, month, day); - }, - newDate = (date == null || date === "" ? defaultDate : (typeof date === "string" ? offsetString(date) : - (typeof date === "number" ? (isNaN(date) ? defaultDate : offsetNumeric(date)) : new Date(date.getTime())))); + if ( $.effects && $.effects.effect[ showAnim ] ) { + inst.dpDiv.show(showAnim, $.datepicker._get(inst, "showOptions"), duration); + } else { + inst.dpDiv[showAnim || "show"](showAnim ? duration : null); + } - newDate = (newDate && newDate.toString() === "Invalid Date" ? defaultDate : newDate); - if (newDate) { - newDate.setHours(0); - newDate.setMinutes(0); - newDate.setSeconds(0); - newDate.setMilliseconds(0); + if ( $.datepicker._shouldFocusInput( inst ) ) { + inst.input.focus(); + } + + $.datepicker._curInst = inst; } - return this._daylightSavingAdjust(newDate); }, - /* Handle switch to/from daylight saving. - * Hours may be non-zero on daylight saving cut-over: - * > 12 when midnight changeover, but then cannot generate - * midnight datetime, so jump to 1AM, otherwise reset. - * @param date (Date) the date to check - * @return (Date) the corrected date - */ - _daylightSavingAdjust: function(date) { - if (!date) { - return null; - } - date.setHours(date.getHours() > 12 ? date.getHours() + 2 : 0); - return date; - }, + /* Generate the date picker content. */ + _updateDatepicker: function(inst) { + this.maxRows = 4; //Reset the max number of rows being displayed (see #7043) + datepicker_instActive = inst; // for delegate hover events + inst.dpDiv.empty().append(this._generateHTML(inst)); + this._attachHandlers(inst); + inst.dpDiv.find("." + this._dayOverClass + " a"); - /* Set the date(s) directly. */ - _setDate: function(inst, date, noChange) { - var clear = !date, - origMonth = inst.selectedMonth, - origYear = inst.selectedYear, - newDate = this._restrictMinMax(inst, this._determineDate(inst, date, new Date())); + var origyearshtml, + numMonths = this._getNumberOfMonths(inst), + cols = numMonths[1], + width = 17; - inst.selectedDay = inst.currentDay = newDate.getDate(); - inst.drawMonth = inst.selectedMonth = inst.currentMonth = newDate.getMonth(); - inst.drawYear = inst.selectedYear = inst.currentYear = newDate.getFullYear(); - if ((origMonth !== inst.selectedMonth || origYear !== inst.selectedYear) && !noChange) { - this._notifyChange(inst); + inst.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width(""); + if (cols > 1) { + inst.dpDiv.addClass("ui-datepicker-multi-" + cols).css("width", (width * cols) + "em"); } - this._adjustInstDate(inst); - if (inst.input) { - inst.input.val(clear ? "" : this._formatDate(inst)); + inst.dpDiv[(numMonths[0] !== 1 || numMonths[1] !== 1 ? "add" : "remove") + + "Class"]("ui-datepicker-multi"); + inst.dpDiv[(this._get(inst, "isRTL") ? "add" : "remove") + + "Class"]("ui-datepicker-rtl"); + + if (inst === $.datepicker._curInst && $.datepicker._datepickerShowing && $.datepicker._shouldFocusInput( inst ) ) { + inst.input.focus(); + } + + // deffered render of the years select (to avoid flashes on Firefox) + if( inst.yearshtml ){ + origyearshtml = inst.yearshtml; + setTimeout(function(){ + //assure that inst.yearshtml didn't change. + if( origyearshtml === inst.yearshtml && inst.yearshtml ){ + inst.dpDiv.find("select.ui-datepicker-year:first").replaceWith(inst.yearshtml); + } + origyearshtml = inst.yearshtml = null; + }, 0); } }, - /* Retrieve the date(s) directly. */ - _getDate: function(inst) { - var startDate = (!inst.currentYear || (inst.input && inst.input.val() === "") ? null : - this._daylightSavingAdjust(new Date( - inst.currentYear, inst.currentMonth, inst.currentDay))); - return startDate; + // #6694 - don't focus the input if it's already focused + // this breaks the change event in IE + // Support: IE and jQuery <1.9 + _shouldFocusInput: function( inst ) { + return inst.input && inst.input.is( ":visible" ) && !inst.input.is( ":disabled" ) && !inst.input.is( ":focus" ); }, - /* Attach the onxxx handlers. These are declared statically so - * they work with static code transformers like Caja. - */ - _attachHandlers: function(inst) { - var stepMonths = this._get(inst, "stepMonths"), - id = "#" + inst.id.replace( /\\\\/g, "\\" ); - inst.dpDiv.find("[data-handler]").map(function () { - var handler = { - prev: function () { - $.datepicker._adjustDate(id, -stepMonths, "M"); - }, - next: function () { - $.datepicker._adjustDate(id, +stepMonths, "M"); - }, - hide: function () { - $.datepicker._hideDatepicker(); - }, - today: function () { - $.datepicker._gotoToday(id); - }, - selectDay: function () { - $.datepicker._selectDay(id, +this.getAttribute("data-month"), +this.getAttribute("data-year"), this); - return false; - }, - selectMonth: function () { - $.datepicker._selectMonthYear(id, this, "M"); - return false; - }, - selectYear: function () { - $.datepicker._selectMonthYear(id, this, "Y"); - return false; - } - }; - $(this).bind(this.getAttribute("data-event"), handler[this.getAttribute("data-handler")]); - }); + /* Check positioning to remain on screen. */ + _checkOffset: function(inst, offset, isFixed) { + var dpWidth = inst.dpDiv.outerWidth(), + dpHeight = inst.dpDiv.outerHeight(), + inputWidth = inst.input ? inst.input.outerWidth() : 0, + inputHeight = inst.input ? inst.input.outerHeight() : 0, + viewWidth = document.documentElement.clientWidth + (isFixed ? 0 : $(document).scrollLeft()), + viewHeight = document.documentElement.clientHeight + (isFixed ? 0 : $(document).scrollTop()); + + offset.left -= (this._get(inst, "isRTL") ? (dpWidth - inputWidth) : 0); + offset.left -= (isFixed && offset.left === inst.input.offset().left) ? $(document).scrollLeft() : 0; + offset.top -= (isFixed && offset.top === (inst.input.offset().top + inputHeight)) ? $(document).scrollTop() : 0; + + // now check if datepicker is showing outside window viewport - move to a better place if so. + offset.left -= Math.min(offset.left, (offset.left + dpWidth > viewWidth && viewWidth > dpWidth) ? + Math.abs(offset.left + dpWidth - viewWidth) : 0); + offset.top -= Math.min(offset.top, (offset.top + dpHeight > viewHeight && viewHeight > dpHeight) ? + Math.abs(dpHeight + inputHeight) : 0); + + return offset; }, - /* Generate the HTML for the current state of the date picker. */ - _generateHTML: function(inst) { - var maxDraw, prevText, prev, nextText, next, currentText, gotoDate, - controls, buttonPanel, firstDay, showWeek, dayNames, dayNamesMin, - monthNames, monthNamesShort, beforeShowDay, showOtherMonths, - selectOtherMonths, defaultDate, html, dow, row, group, col, selectedDate, - cornerClass, calender, thead, day, daysInMonth, leadDays, curRows, numRows, - printDate, dRow, tbody, daySettings, otherMonth, unselectable, - tempDate = new Date(), - today = this._daylightSavingAdjust( - new Date(tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate())), // clear time - isRTL = this._get(inst, "isRTL"), - showButtonPanel = this._get(inst, "showButtonPanel"), - hideIfNoPrevNext = this._get(inst, "hideIfNoPrevNext"), - navigationAsDateFormat = this._get(inst, "navigationAsDateFormat"), - numMonths = this._getNumberOfMonths(inst), - showCurrentAtPos = this._get(inst, "showCurrentAtPos"), - stepMonths = this._get(inst, "stepMonths"), - isMultiMonth = (numMonths[0] !== 1 || numMonths[1] !== 1), - currentDate = this._daylightSavingAdjust((!inst.currentDay ? new Date(9999, 9, 9) : - new Date(inst.currentYear, inst.currentMonth, inst.currentDay))), - minDate = this._getMinMaxDate(inst, "min"), - maxDate = this._getMinMaxDate(inst, "max"), - drawMonth = inst.drawMonth - showCurrentAtPos, - drawYear = inst.drawYear; + /* Find an object's position on the screen. */ + _findPos: function(obj) { + var position, + inst = this._getInst(obj), + isRTL = this._get(inst, "isRTL"); - if (drawMonth < 0) { - drawMonth += 12; - drawYear--; + while (obj && (obj.type === "hidden" || obj.nodeType !== 1 || $.expr.filters.hidden(obj))) { + obj = obj[isRTL ? "previousSibling" : "nextSibling"]; } - if (maxDate) { - maxDraw = this._daylightSavingAdjust(new Date(maxDate.getFullYear(), - maxDate.getMonth() - (numMonths[0] * numMonths[1]) + 1, maxDate.getDate())); - maxDraw = (minDate && maxDraw < minDate ? minDate : maxDraw); - while (this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1)) > maxDraw) { - drawMonth--; - if (drawMonth < 0) { - drawMonth = 11; - drawYear--; - } - } + + position = $(obj).offset(); + return [position.left, position.top]; + }, + + /* Hide the date picker from view. + * @param input element - the input field attached to the date picker + */ + _hideDatepicker: function(input) { + var showAnim, duration, postProcess, onClose, + inst = this._curInst; + + if (!inst || (input && inst !== $.data(input, "datepicker"))) { + return; } - inst.drawMonth = drawMonth; - inst.drawYear = drawYear; - prevText = this._get(inst, "prevText"); - prevText = (!navigationAsDateFormat ? prevText : this.formatDate(prevText, - this._daylightSavingAdjust(new Date(drawYear, drawMonth - stepMonths, 1)), - this._getFormatConfig(inst))); + if (this._datepickerShowing) { + showAnim = this._get(inst, "showAnim"); + duration = this._get(inst, "duration"); + postProcess = function() { + $.datepicker._tidyDialog(inst); + }; - prev = (this._canAdjustMonth(inst, -1, drawYear, drawMonth) ? - "" + prevText + "" : - (hideIfNoPrevNext ? "" : "" + prevText + "")); + // DEPRECATED: after BC for 1.8.x $.effects[ showAnim ] is not needed + if ( $.effects && ( $.effects.effect[ showAnim ] || $.effects[ showAnim ] ) ) { + inst.dpDiv.hide(showAnim, $.datepicker._get(inst, "showOptions"), duration, postProcess); + } else { + inst.dpDiv[(showAnim === "slideDown" ? "slideUp" : + (showAnim === "fadeIn" ? "fadeOut" : "hide"))]((showAnim ? duration : null), postProcess); + } - nextText = this._get(inst, "nextText"); - nextText = (!navigationAsDateFormat ? nextText : this.formatDate(nextText, - this._daylightSavingAdjust(new Date(drawYear, drawMonth + stepMonths, 1)), - this._getFormatConfig(inst))); + if (!showAnim) { + postProcess(); + } + this._datepickerShowing = false; - next = (this._canAdjustMonth(inst, +1, drawYear, drawMonth) ? - "" + nextText + "" : - (hideIfNoPrevNext ? "" : "" + nextText + "")); + onClose = this._get(inst, "onClose"); + if (onClose) { + onClose.apply((inst.input ? inst.input[0] : null), [(inst.input ? inst.input.val() : ""), inst]); + } - currentText = this._get(inst, "currentText"); - gotoDate = (this._get(inst, "gotoCurrent") && inst.currentDay ? currentDate : today); - currentText = (!navigationAsDateFormat ? currentText : - this.formatDate(currentText, gotoDate, this._getFormatConfig(inst))); + this._lastInput = null; + if (this._inDialog) { + this._dialogInput.css({ position: "absolute", left: "0", top: "-100px" }); + if ($.blockUI) { + $.unblockUI(); + $("body").append(this.dpDiv); + } + } + this._inDialog = false; + } + }, - controls = (!inst.inline ? "" : ""); + /* Tidy up after a dialog display. */ + _tidyDialog: function(inst) { + inst.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar"); + }, - buttonPanel = (showButtonPanel) ? "
      " + (isRTL ? controls : "") + - (this._isInRange(inst, gotoDate) ? "" : "") + (isRTL ? "" : controls) + "
      " : ""; + /* Close date picker if clicked elsewhere. */ + _checkExternalClick: function(event) { + if (!$.datepicker._curInst) { + return; + } - firstDay = parseInt(this._get(inst, "firstDay"),10); - firstDay = (isNaN(firstDay) ? 0 : firstDay); + var $target = $(event.target), + inst = $.datepicker._getInst($target[0]); - showWeek = this._get(inst, "showWeek"); - dayNames = this._get(inst, "dayNames"); - dayNamesMin = this._get(inst, "dayNamesMin"); - monthNames = this._get(inst, "monthNames"); - monthNamesShort = this._get(inst, "monthNamesShort"); - beforeShowDay = this._get(inst, "beforeShowDay"); - showOtherMonths = this._get(inst, "showOtherMonths"); - selectOtherMonths = this._get(inst, "selectOtherMonths"); - defaultDate = this._getDefaultDate(inst); - html = ""; - dow; - for (row = 0; row < numMonths[0]; row++) { - group = ""; - this.maxRows = 4; - for (col = 0; col < numMonths[1]; col++) { - selectedDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, inst.selectedDay)); - cornerClass = " ui-corner-all"; - calender = ""; - if (isMultiMonth) { - calender += "
      "; - } - calender += "
      " + - (/all|left/.test(cornerClass) && row === 0 ? (isRTL ? next : prev) : "") + - (/all|right/.test(cornerClass) && row === 0 ? (isRTL ? prev : next) : "") + - this._generateMonthYearHeader(inst, drawMonth, drawYear, minDate, maxDate, - row > 0 || col > 0, monthNames, monthNamesShort) + // draw month headers - "
      " + - ""; - thead = (showWeek ? "" : ""); - for (dow = 0; dow < 7; dow++) { // days of the week - day = (dow + firstDay) % 7; - thead += "= 5 ? " class='ui-datepicker-week-end'" : "") + ">" + - "" + dayNamesMin[day] + ""; - } - calender += thead + ""; - daysInMonth = this._getDaysInMonth(drawYear, drawMonth); - if (drawYear === inst.selectedYear && drawMonth === inst.selectedMonth) { - inst.selectedDay = Math.min(inst.selectedDay, daysInMonth); - } - leadDays = (this._getFirstDayOfMonth(drawYear, drawMonth) - firstDay + 7) % 7; - curRows = Math.ceil((leadDays + daysInMonth) / 7); // calculate the number of rows to generate - numRows = (isMultiMonth ? this.maxRows > curRows ? this.maxRows : curRows : curRows); //If multiple months, use the higher number of rows (see #7043) - this.maxRows = numRows; - printDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1 - leadDays)); - for (dRow = 0; dRow < numRows; dRow++) { // create date picker rows - calender += ""; - tbody = (!showWeek ? "" : ""); - for (dow = 0; dow < 7; dow++) { // create date picker days - daySettings = (beforeShowDay ? - beforeShowDay.apply((inst.input ? inst.input[0] : null), [printDate]) : [true, ""]); - otherMonth = (printDate.getMonth() !== drawMonth); - unselectable = (otherMonth && !selectOtherMonths) || !daySettings[0] || - (minDate && printDate < minDate) || (maxDate && printDate > maxDate); - tbody += ""; // display selectable date - printDate.setDate(printDate.getDate() + 1); - printDate = this._daylightSavingAdjust(printDate); - } - calender += tbody + ""; - } - drawMonth++; - if (drawMonth > 11) { - drawMonth = 0; - drawYear++; - } - calender += "
      " + this._get(inst, "weekHeader") + "
      " + - this._get(inst, "calculateWeek")(printDate) + "" + // actions - (otherMonth && !showOtherMonths ? " " : // display for other months - (unselectable ? "" + printDate.getDate() + "" : "" + printDate.getDate() + "")) + "
      " + (isMultiMonth ? "
      " + - ((numMonths[0] > 0 && col === numMonths[1]-1) ? "
      " : "") : ""); - group += calender; - } - html += group; + if ( ( ( $target[0].id !== $.datepicker._mainDivId && + $target.parents("#" + $.datepicker._mainDivId).length === 0 && + !$target.hasClass($.datepicker.markerClassName) && + !$target.closest("." + $.datepicker._triggerClass).length && + $.datepicker._datepickerShowing && !($.datepicker._inDialog && $.blockUI) ) ) || + ( $target.hasClass($.datepicker.markerClassName) && $.datepicker._curInst !== inst ) ) { + $.datepicker._hideDatepicker(); } - html += buttonPanel; - inst._keyEvent = false; - return html; }, - /* Generate the month and year header. */ - _generateMonthYearHeader: function(inst, drawMonth, drawYear, minDate, maxDate, - secondary, monthNames, monthNamesShort) { - - var inMinYear, inMaxYear, month, years, thisYear, determineYear, year, endYear, - changeMonth = this._get(inst, "changeMonth"), - changeYear = this._get(inst, "changeYear"), - showMonthAfterYear = this._get(inst, "showMonthAfterYear"), - html = "
      ", - monthHtml = ""; - - // month selection - if (secondary || !changeMonth) { - monthHtml += "" + monthNames[drawMonth] + ""; - } else { - inMinYear = (minDate && minDate.getFullYear() === drawYear); - inMaxYear = (maxDate && maxDate.getFullYear() === drawYear); - monthHtml += ""; - } + /* Adjust one of the date sub-fields. */ + _adjustDate: function(id, offset, period) { + var target = $(id), + inst = this._getInst(target[0]); - if (!showMonthAfterYear) { - html += monthHtml + (secondary || !(changeMonth && changeYear) ? " " : ""); + if (this._isDisabledDatepicker(target[0])) { + return; } + this._adjustInstDate(inst, offset + + (period === "M" ? this._get(inst, "showCurrentAtPos") : 0), // undo positioning + period); + this._updateDatepicker(inst); + }, - // year selection - if ( !inst.yearshtml ) { - inst.yearshtml = ""; - if (secondary || !changeYear) { - html += "" + drawYear + ""; - } else { - // determine range of years to display - years = this._get(inst, "yearRange").split(":"); - thisYear = new Date().getFullYear(); - determineYear = function(value) { - var year = (value.match(/c[+\-].*/) ? drawYear + parseInt(value.substring(1), 10) : - (value.match(/[+\-].*/) ? thisYear + parseInt(value, 10) : - parseInt(value, 10))); - return (isNaN(year) ? thisYear : year); - }; - year = determineYear(years[0]); - endYear = Math.max(year, determineYear(years[1] || "")); - year = (minDate ? Math.max(year, minDate.getFullYear()) : year); - endYear = (maxDate ? Math.min(endYear, maxDate.getFullYear()) : endYear); - inst.yearshtml += ""; - - html += inst.yearshtml; - inst.yearshtml = null; - } - } + /* Action for current link. */ + _gotoToday: function(id) { + var date, + target = $(id), + inst = this._getInst(target[0]); - html += this._get(inst, "yearSuffix"); - if (showMonthAfterYear) { - html += (secondary || !(changeMonth && changeYear) ? " " : "") + monthHtml; + if (this._get(inst, "gotoCurrent") && inst.currentDay) { + inst.selectedDay = inst.currentDay; + inst.drawMonth = inst.selectedMonth = inst.currentMonth; + inst.drawYear = inst.selectedYear = inst.currentYear; + } else { + date = new Date(); + inst.selectedDay = date.getDate(); + inst.drawMonth = inst.selectedMonth = date.getMonth(); + inst.drawYear = inst.selectedYear = date.getFullYear(); } - html += "
      "; // Close datepicker_header - return html; + this._notifyChange(inst); + this._adjustDate(target); }, - /* Adjust one of the date sub-fields. */ - _adjustInstDate: function(inst, offset, period) { - var year = inst.drawYear + (period === "Y" ? offset : 0), - month = inst.drawMonth + (period === "M" ? offset : 0), - day = Math.min(inst.selectedDay, this._getDaysInMonth(year, month)) + (period === "D" ? offset : 0), - date = this._restrictMinMax(inst, this._daylightSavingAdjust(new Date(year, month, day))); + /* Action for selecting a new month/year. */ + _selectMonthYear: function(id, select, period) { + var target = $(id), + inst = this._getInst(target[0]); - inst.selectedDay = date.getDate(); - inst.drawMonth = inst.selectedMonth = date.getMonth(); - inst.drawYear = inst.selectedYear = date.getFullYear(); - if (period === "M" || period === "Y") { - this._notifyChange(inst); - } - }, + inst["selected" + (period === "M" ? "Month" : "Year")] = + inst["draw" + (period === "M" ? "Month" : "Year")] = + parseInt(select.options[select.selectedIndex].value,10); - /* Ensure a date is within any min/max bounds. */ - _restrictMinMax: function(inst, date) { - var minDate = this._getMinMaxDate(inst, "min"), - maxDate = this._getMinMaxDate(inst, "max"), - newDate = (minDate && date < minDate ? minDate : date); - return (maxDate && newDate > maxDate ? maxDate : newDate); + this._notifyChange(inst); + this._adjustDate(target); }, - /* Notify change of month/year. */ - _notifyChange: function(inst) { - var onChange = this._get(inst, "onChangeMonthYear"); - if (onChange) { - onChange.apply((inst.input ? inst.input[0] : null), - [inst.selectedYear, inst.selectedMonth + 1, inst]); + /* Action for selecting a day. */ + _selectDay: function(id, month, year, td) { + var inst, + target = $(id); + + if ($(td).hasClass(this._unselectableClass) || this._isDisabledDatepicker(target[0])) { + return; } - }, - /* Determine the number of months to show. */ - _getNumberOfMonths: function(inst) { - var numMonths = this._get(inst, "numberOfMonths"); - return (numMonths == null ? [1, 1] : (typeof numMonths === "number" ? [1, numMonths] : numMonths)); + inst = this._getInst(target[0]); + inst.selectedDay = inst.currentDay = $("a", td).html(); + inst.selectedMonth = inst.currentMonth = month; + inst.selectedYear = inst.currentYear = year; + this._selectDate(id, this._formatDate(inst, + inst.currentDay, inst.currentMonth, inst.currentYear)); }, - /* Determine the current maximum date - ensure no time components are set. */ - _getMinMaxDate: function(inst, minMax) { - return this._determineDate(inst, this._get(inst, minMax + "Date"), null); + /* Erase the input field and hide the date picker. */ + _clearDate: function(id) { + var target = $(id); + this._selectDate(target, ""); }, - /* Find the number of days in a given month. */ - _getDaysInMonth: function(year, month) { - return 32 - this._daylightSavingAdjust(new Date(year, month, 32)).getDate(); - }, + /* Update the input field with the selected date. */ + _selectDate: function(id, dateStr) { + var onSelect, + target = $(id), + inst = this._getInst(target[0]); - /* Find the day of the week of the first of a month. */ - _getFirstDayOfMonth: function(year, month) { - return new Date(year, month, 1).getDay(); - }, + dateStr = (dateStr != null ? dateStr : this._formatDate(inst)); + if (inst.input) { + inst.input.val(dateStr); + } + this._updateAlternate(inst); - /* Determines if we should allow a "next/prev" month display change. */ - _canAdjustMonth: function(inst, offset, curYear, curMonth) { - var numMonths = this._getNumberOfMonths(inst), - date = this._daylightSavingAdjust(new Date(curYear, - curMonth + (offset < 0 ? offset : numMonths[0] * numMonths[1]), 1)); + onSelect = this._get(inst, "onSelect"); + if (onSelect) { + onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]); // trigger custom callback + } else if (inst.input) { + inst.input.trigger("change"); // fire the change event + } - if (offset < 0) { - date.setDate(this._getDaysInMonth(date.getFullYear(), date.getMonth())); + if (inst.inline){ + this._updateDatepicker(inst); + } else { + this._hideDatepicker(); + this._lastInput = inst.input[0]; + if (typeof(inst.input[0]) !== "object") { + inst.input.focus(); // restore focus + } + this._lastInput = null; } - return this._isInRange(inst, date); }, - /* Is the given date in the accepted range? */ - _isInRange: function(inst, date) { - var yearSplit, currentYear, - minDate = this._getMinMaxDate(inst, "min"), - maxDate = this._getMinMaxDate(inst, "max"), - minYear = null, - maxYear = null, - years = this._get(inst, "yearRange"); - if (years){ - yearSplit = years.split(":"); - currentYear = new Date().getFullYear(); - minYear = parseInt(yearSplit[0], 10); - maxYear = parseInt(yearSplit[1], 10); - if ( yearSplit[0].match(/[+\-].*/) ) { - minYear += currentYear; - } - if ( yearSplit[1].match(/[+\-].*/) ) { - maxYear += currentYear; - } - } + /* Update any alternate field to synchronise with the main field. */ + _updateAlternate: function(inst) { + var altFormat, date, dateStr, + altField = this._get(inst, "altField"); - return ((!minDate || date.getTime() >= minDate.getTime()) && - (!maxDate || date.getTime() <= maxDate.getTime()) && - (!minYear || date.getFullYear() >= minYear) && - (!maxYear || date.getFullYear() <= maxYear)); + if (altField) { // update alternate field too + altFormat = this._get(inst, "altFormat") || this._get(inst, "dateFormat"); + date = this._getDate(inst); + dateStr = this.formatDate(altFormat, date, this._getFormatConfig(inst)); + $(altField).each(function() { $(this).val(dateStr); }); + } }, - /* Provide the configuration settings for formatting/parsing. */ - _getFormatConfig: function(inst) { - var shortYearCutoff = this._get(inst, "shortYearCutoff"); - shortYearCutoff = (typeof shortYearCutoff !== "string" ? shortYearCutoff : - new Date().getFullYear() % 100 + parseInt(shortYearCutoff, 10)); - return {shortYearCutoff: shortYearCutoff, - dayNamesShort: this._get(inst, "dayNamesShort"), dayNames: this._get(inst, "dayNames"), - monthNamesShort: this._get(inst, "monthNamesShort"), monthNames: this._get(inst, "monthNames")}; + /* Set as beforeShowDay function to prevent selection of weekends. + * @param date Date - the date to customise + * @return [boolean, string] - is this date selectable?, what is its CSS class? + */ + noWeekends: function(date) { + var day = date.getDay(); + return [(day > 0 && day < 6), ""]; }, - /* Format the given date for display. */ - _formatDate: function(inst, day, month, year) { - if (!day) { - inst.currentDay = inst.selectedDay; - inst.currentMonth = inst.selectedMonth; - inst.currentYear = inst.selectedYear; - } - var date = (day ? (typeof day === "object" ? day : - this._daylightSavingAdjust(new Date(year, month, day))) : - this._daylightSavingAdjust(new Date(inst.currentYear, inst.currentMonth, inst.currentDay))); - return this.formatDate(this._get(inst, "dateFormat"), date, this._getFormatConfig(inst)); - } -}); + /* Set as calculateWeek to determine the week of the year based on the ISO 8601 definition. + * @param date Date - the date to get the week for + * @return number - the number of the week within the year that contains this date + */ + iso8601Week: function(date) { + var time, + checkDate = new Date(date.getTime()); -/* - * Bind hover events for datepicker elements. - * Done via delegate so the binding only occurs once in the lifetime of the parent div. - * Global instActive, set by _updateDatepicker allows the handlers to find their way back to the active picker. - */ -function bindHover(dpDiv) { - var selector = "button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a"; - return dpDiv.delegate(selector, "mouseout", function() { - $(this).removeClass("ui-state-hover"); - if (this.className.indexOf("ui-datepicker-prev") !== -1) { - $(this).removeClass("ui-datepicker-prev-hover"); - } - if (this.className.indexOf("ui-datepicker-next") !== -1) { - $(this).removeClass("ui-datepicker-next-hover"); - } - }) - .delegate(selector, "mouseover", function(){ - if (!$.datepicker._isDisabledDatepicker( instActive.inline ? dpDiv.parent()[0] : instActive.input[0])) { - $(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"); - $(this).addClass("ui-state-hover"); - if (this.className.indexOf("ui-datepicker-prev") !== -1) { - $(this).addClass("ui-datepicker-prev-hover"); - } - if (this.className.indexOf("ui-datepicker-next") !== -1) { - $(this).addClass("ui-datepicker-next-hover"); - } - } - }); -} + // Find Thursday of this week starting on Monday + checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); -/* jQuery extend now ignores nulls! */ -function extendRemove(target, props) { - $.extend(target, props); - for (var name in props) { - if (props[name] == null) { - target[name] = props[name]; - } - } - return target; -} + time = checkDate.getTime(); + checkDate.setMonth(0); // Compare with Jan 1 + checkDate.setDate(1); + return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1; + }, -/* Invoke the datepicker functionality. - @param options string - a command, optionally followed by additional parameters or - Object - settings for attaching new datepicker functionality - @return jQuery object */ -$.fn.datepicker = function(options){ + /* Parse a string value into a date object. + * See formatDate below for the possible formats. + * + * @param format string - the expected format of the date + * @param value string - the date in the above format + * @param settings Object - attributes include: + * shortYearCutoff number - the cutoff year for determining the century (optional) + * dayNamesShort string[7] - abbreviated names of the days from Sunday (optional) + * dayNames string[7] - names of the days from Sunday (optional) + * monthNamesShort string[12] - abbreviated names of the months (optional) + * monthNames string[12] - names of the months (optional) + * @return Date - the extracted date value or null if value is blank + */ + parseDate: function (format, value, settings) { + if (format == null || value == null) { + throw "Invalid arguments"; + } - /* Verify an empty collection wasn't passed - Fixes #6976 */ - if ( !this.length ) { - return this; - } + value = (typeof value === "object" ? value.toString() : value + ""); + if (value === "") { + return null; + } - /* Initialise the date picker. */ - if (!$.datepicker.initialized) { - $(document).mousedown($.datepicker._checkExternalClick); - $.datepicker.initialized = true; - } + var iFormat, dim, extra, + iValue = 0, + shortYearCutoffTemp = (settings ? settings.shortYearCutoff : null) || this._defaults.shortYearCutoff, + shortYearCutoff = (typeof shortYearCutoffTemp !== "string" ? shortYearCutoffTemp : + new Date().getFullYear() % 100 + parseInt(shortYearCutoffTemp, 10)), + dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort, + dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames, + monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort, + monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames, + year = -1, + month = -1, + day = -1, + doy = -1, + literal = false, + date, + // Check whether a format character is doubled + lookAhead = function(match) { + var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match); + if (matches) { + iFormat++; + } + return matches; + }, + // Extract a number from the string value + getNumber = function(match) { + var isDoubled = lookAhead(match), + size = (match === "@" ? 14 : (match === "!" ? 20 : + (match === "y" && isDoubled ? 4 : (match === "o" ? 3 : 2)))), + digits = new RegExp("^\\d{1," + size + "}"), + num = value.substring(iValue).match(digits); + if (!num) { + throw "Missing number at position " + iValue; + } + iValue += num[0].length; + return parseInt(num[0], 10); + }, + // Extract a name from the string value and convert to an index + getName = function(match, shortNames, longNames) { + var index = -1, + names = $.map(lookAhead(match) ? longNames : shortNames, function (v, k) { + return [ [k, v] ]; + }).sort(function (a, b) { + return -(a[1].length - b[1].length); + }); - /* Append datepicker main container to body if not exist. */ - if ($("#"+$.datepicker._mainDivId).length === 0) { - $("body").append($.datepicker.dpDiv); - } + $.each(names, function (i, pair) { + var name = pair[1]; + if (value.substr(iValue, name.length).toLowerCase() === name.toLowerCase()) { + index = pair[0]; + iValue += name.length; + return false; + } + }); + if (index !== -1) { + return index + 1; + } else { + throw "Unknown name at position " + iValue; + } + }, + // Confirm that a literal character matches the string value + checkLiteral = function() { + if (value.charAt(iValue) !== format.charAt(iFormat)) { + throw "Unexpected literal at position " + iValue; + } + iValue++; + }; - var otherArgs = Array.prototype.slice.call(arguments, 1); - if (typeof options === "string" && (options === "isDisabled" || options === "getDate" || options === "widget")) { - return $.datepicker["_" + options + "Datepicker"]. - apply($.datepicker, [this[0]].concat(otherArgs)); - } - if (options === "option" && arguments.length === 2 && typeof arguments[1] === "string") { - return $.datepicker["_" + options + "Datepicker"]. - apply($.datepicker, [this[0]].concat(otherArgs)); - } - return this.each(function() { - typeof options === "string" ? - $.datepicker["_" + options + "Datepicker"]. - apply($.datepicker, [this].concat(otherArgs)) : - $.datepicker._attachDatepicker(this, options); - }); -}; - -$.datepicker = new Datepicker(); // singleton instance -$.datepicker.initialized = false; -$.datepicker.uuid = new Date().getTime(); -$.datepicker.version = "1.10.4"; - -})(jQuery); -(function( $, undefined ) { - -var sizeRelatedOptions = { - buttons: true, - height: true, - maxHeight: true, - maxWidth: true, - minHeight: true, - minWidth: true, - width: true - }, - resizableRelatedOptions = { - maxHeight: true, - maxWidth: true, - minHeight: true, - minWidth: true - }; - -$.widget( "ui.dialog", { - version: "1.10.4", - options: { - appendTo: "body", - autoOpen: true, - buttons: [], - closeOnEscape: true, - closeText: "close", - dialogClass: "", - draggable: true, - hide: null, - height: "auto", - maxHeight: null, - maxWidth: null, - minHeight: 150, - minWidth: 150, - modal: false, - position: { - my: "center", - at: "center", - of: window, - collision: "fit", - // Ensure the titlebar is always visible - using: function( pos ) { - var topOffset = $( this ).css( pos ).offset().top; - if ( topOffset < 0 ) { - $( this ).css( "top", pos.top - topOffset ); + for (iFormat = 0; iFormat < format.length; iFormat++) { + if (literal) { + if (format.charAt(iFormat) === "'" && !lookAhead("'")) { + literal = false; + } else { + checkLiteral(); + } + } else { + switch (format.charAt(iFormat)) { + case "d": + day = getNumber("d"); + break; + case "D": + getName("D", dayNamesShort, dayNames); + break; + case "o": + doy = getNumber("o"); + break; + case "m": + month = getNumber("m"); + break; + case "M": + month = getName("M", monthNamesShort, monthNames); + break; + case "y": + year = getNumber("y"); + break; + case "@": + date = new Date(getNumber("@")); + year = date.getFullYear(); + month = date.getMonth() + 1; + day = date.getDate(); + break; + case "!": + date = new Date((getNumber("!") - this._ticksTo1970) / 10000); + year = date.getFullYear(); + month = date.getMonth() + 1; + day = date.getDate(); + break; + case "'": + if (lookAhead("'")){ + checkLiteral(); + } else { + literal = true; + } + break; + default: + checkLiteral(); } } - }, - resizable: true, - show: null, - title: null, - width: 300, - - // callbacks - beforeClose: null, - close: null, - drag: null, - dragStart: null, - dragStop: null, - focus: null, - open: null, - resize: null, - resizeStart: null, - resizeStop: null - }, - - _create: function() { - this.originalCss = { - display: this.element[0].style.display, - width: this.element[0].style.width, - minHeight: this.element[0].style.minHeight, - maxHeight: this.element[0].style.maxHeight, - height: this.element[0].style.height - }; - this.originalPosition = { - parent: this.element.parent(), - index: this.element.parent().children().index( this.element ) - }; - this.originalTitle = this.element.attr("title"); - this.options.title = this.options.title || this.originalTitle; - - this._createWrapper(); - - this.element - .show() - .removeAttr("title") - .addClass("ui-dialog-content ui-widget-content") - .appendTo( this.uiDialog ); - - this._createTitlebar(); - this._createButtonPane(); - - if ( this.options.draggable && $.fn.draggable ) { - this._makeDraggable(); - } - if ( this.options.resizable && $.fn.resizable ) { - this._makeResizable(); } - this._isOpen = false; - }, - - _init: function() { - if ( this.options.autoOpen ) { - this.open(); + if (iValue < value.length){ + extra = value.substr(iValue); + if (!/^\s+/.test(extra)) { + throw "Extra/unparsed characters found in date: " + extra; + } } - }, - _appendTo: function() { - var element = this.options.appendTo; - if ( element && (element.jquery || element.nodeType) ) { - return $( element ); + if (year === -1) { + year = new Date().getFullYear(); + } else if (year < 100) { + year += new Date().getFullYear() - new Date().getFullYear() % 100 + + (year <= shortYearCutoff ? 0 : -100); } - return this.document.find( element || "body" ).eq( 0 ); - }, - - _destroy: function() { - var next, - originalPosition = this.originalPosition; - - this._destroyOverlay(); - - this.element - .removeUniqueId() - .removeClass("ui-dialog-content ui-widget-content") - .css( this.originalCss ) - // Without detaching first, the following becomes really slow - .detach(); - - this.uiDialog.stop( true, true ).remove(); - if ( this.originalTitle ) { - this.element.attr( "title", this.originalTitle ); + if (doy > -1) { + month = 1; + day = doy; + do { + dim = this._getDaysInMonth(year, month - 1); + if (day <= dim) { + break; + } + month++; + day -= dim; + } while (true); } - next = originalPosition.parent.children().eq( originalPosition.index ); - // Don't try to place the dialog next to itself (#8613) - if ( next.length && next[0] !== this.element[0] ) { - next.before( this.element ); - } else { - originalPosition.parent.append( this.element ); + date = this._daylightSavingAdjust(new Date(year, month - 1, day)); + if (date.getFullYear() !== year || date.getMonth() + 1 !== month || date.getDate() !== day) { + throw "Invalid date"; // E.g. 31/02/00 } + return date; }, - widget: function() { - return this.uiDialog; - }, - - disable: $.noop, - enable: $.noop, - - close: function( event ) { - var activeElement, - that = this; - - if ( !this._isOpen || this._trigger( "beforeClose", event ) === false ) { - return; - } - - this._isOpen = false; - this._destroyOverlay(); - - if ( !this.opener.filter(":focusable").focus().length ) { - - // support: IE9 - // IE9 throws an "Unspecified error" accessing document.activeElement from an