This commit is contained in:
coderkun 2015-04-27 16:42:05 +02:00
commit 046a724272
4209 changed files with 1186656 additions and 0 deletions

View file

@ -0,0 +1,646 @@
<?php
/**
* Piwik - Open source web analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\UsersManager;
use Exception;
use Piwik\Access;
use Piwik\Common;
use Piwik\Config;
use Piwik\Date;
use Piwik\Option;
use Piwik\Piwik;
use Piwik\Site;
use Piwik\Tracker\Cache;
/**
* The UsersManager API lets you Manage Users and their permissions to access specific websites.
*
* You can create users via "addUser", update existing users via "updateUser" and delete users via "deleteUser".
* There are many ways to list users based on their login "getUser" and "getUsers", their email "getUserByEmail",
* or which users have permission (view or admin) to access the specified websites "getUsersWithSiteAccess".
*
* Existing Permissions are listed given a login via "getSitesAccessFromUser", or a website ID via "getUsersAccessFromSite",
* or you can list all users and websites for a given permission via "getUsersSitesFromAccess". Permissions are set and updated
* via the method "setUserAccess".
* See also the documentation about <a href='http://piwik.org/docs/manage-users/' target='_blank'>Managing Users</a> in Piwik.
*/
class API extends \Piwik\Plugin\API
{
/**
* @var Model
*/
private $model;
const PREFERENCE_DEFAULT_REPORT = 'defaultReport';
const PREFERENCE_DEFAULT_REPORT_DATE = 'defaultReportDate';
static private $instance = null;
protected function __construct()
{
$this->model = new Model();
}
/**
* You can create your own Users Plugin to override this class.
* Example of how you would overwrite the UsersManager_API with your own class:
* Call the following in your plugin __construct() for example:
*
* Registry::set('UsersManager_API', \Piwik\Plugins\MyCustomUsersManager\API::getInstance());
*
* @throws Exception
* @return \Piwik\Plugins\UsersManager\API
*/
static public function getInstance()
{
try {
$instance = \Piwik\Registry::get('UsersManager_API');
if (!($instance instanceof API)) {
// Exception is caught below and corrected
throw new Exception('UsersManager_API must inherit API');
}
self::$instance = $instance;
} catch (Exception $e) {
self::$instance = new self;
\Piwik\Registry::set('UsersManager_API', self::$instance);
}
return self::$instance;
}
/**
* Sets a user preference
* @param string $userLogin
* @param string $preferenceName
* @param string $preferenceValue
* @return void
*/
public function setUserPreference($userLogin, $preferenceName, $preferenceValue)
{
Piwik::checkUserHasSuperUserAccessOrIsTheUser($userLogin);
Option::set($this->getPreferenceId($userLogin, $preferenceName), $preferenceValue);
}
/**
* Gets a user preference
* @param string $userLogin
* @param string $preferenceName
* @return bool|string
*/
public function getUserPreference($userLogin, $preferenceName)
{
Piwik::checkUserHasSuperUserAccessOrIsTheUser($userLogin);
$optionValue = Option::get($this->getPreferenceId($userLogin, $preferenceName));
if ($optionValue !== false) {
return $optionValue;
}
return $this->getDefaultUserPreference($preferenceName, $userLogin);
}
private function getPreferenceId($login, $preference)
{
return $login . '_' . $preference;
}
private function getDefaultUserPreference($preferenceName, $login)
{
switch ($preferenceName) {
case self::PREFERENCE_DEFAULT_REPORT:
$viewableSiteIds = \Piwik\Plugins\SitesManager\API::getInstance()->getSitesIdWithAtLeastViewAccess($login);
return reset($viewableSiteIds);
case self::PREFERENCE_DEFAULT_REPORT_DATE:
return Config::getInstance()->General['default_day'];
default:
return false;
}
}
/**
* Returns the list of all the users
*
* @param string $userLogins Comma separated list of users to select. If not specified, will return all users
* @return array the list of all the users
*/
public function getUsers($userLogins = '')
{
Piwik::checkUserHasSomeAdminAccess();
$logins = array();
if (!empty($userLogins)) {
$logins = explode(',', $userLogins);
}
$users = $this->model->getUsers($logins);
// Non Super user can only access login & alias
if (!Piwik::hasUserSuperUserAccess()) {
foreach ($users as &$user) {
$user = array('login' => $user['login'], 'alias' => $user['alias']);
}
}
return $users;
}
/**
* Returns the list of all the users login
*
* @return array the list of all the users login
*/
public function getUsersLogin()
{
Piwik::checkUserHasSomeAdminAccess();
return $this->model->getUsersLogin();
}
/**
* For each user, returns the list of website IDs where the user has the supplied $access level.
* If a user doesn't have the given $access to any website IDs,
* the user will not be in the returned array.
*
* @param string Access can have the following values : 'view' or 'admin'
*
* @return array The returned array has the format
* array(
* login1 => array ( idsite1,idsite2),
* login2 => array(idsite2),
* ...
* )
*/
public function getUsersSitesFromAccess($access)
{
Piwik::checkUserHasSuperUserAccess();
$this->checkAccessType($access);
return $this->model->getUsersSitesFromAccess($access);
}
/**
* For each user, returns his access level for the given $idSite.
* If a user doesn't have any access to the $idSite ('noaccess'),
* the user will not be in the returned array.
*
* @param int $idSite website ID
*
* @return array The returned array has the format
* array(
* login1 => 'view',
* login2 => 'admin',
* login3 => 'view',
* ...
* )
*/
public function getUsersAccessFromSite($idSite)
{
Piwik::checkUserHasAdminAccess($idSite);
return $this->model->getUsersAccessFromSite($idSite);
}
public function getUsersWithSiteAccess($idSite, $access)
{
Piwik::checkUserHasAdminAccess($idSite);
$this->checkAccessType($access);
$logins = $this->model->getUsersLoginWithSiteAccess($idSite, $access);
if (empty($logins)) {
return array();
}
$logins = implode(',', $logins);
return $this->getUsers($logins);
}
/**
* For each website ID, returns the access level of the given $userLogin.
* If the user doesn't have any access to a website ('noaccess'),
* this website will not be in the returned array.
* If the user doesn't have any access, the returned array will be an empty array.
*
* @param string $userLogin User that has to be valid
*
* @return array The returned array has the format
* array(
* idsite1 => 'view',
* idsite2 => 'admin',
* idsite3 => 'view',
* ...
* )
*/
public function getSitesAccessFromUser($userLogin)
{
Piwik::checkUserHasSuperUserAccess();
$this->checkUserExists($userLogin);
$this->checkUserHasNotSuperUserAccess($userLogin);
return $this->model->getSitesAccessFromUser($userLogin);
}
/**
* Returns the user information (login, password md5, alias, email, date_registered, etc.)
*
* @param string $userLogin the user login
*
* @return array the user information
*/
public function getUser($userLogin)
{
Piwik::checkUserHasSuperUserAccessOrIsTheUser($userLogin);
$this->checkUserExists($userLogin);
return $this->model->getUser($userLogin);
}
/**
* Returns the user information (login, password md5, alias, email, date_registered, etc.)
*
* @param string $userEmail the user email
*
* @return array the user information
*/
public function getUserByEmail($userEmail)
{
Piwik::checkUserHasSuperUserAccess();
$this->checkUserEmailExists($userEmail);
return $this->model->getUserByEmail($userEmail);
}
private function checkLogin($userLogin)
{
if ($this->userExists($userLogin)) {
throw new Exception(Piwik::translate('UsersManager_ExceptionLoginExists', $userLogin));
}
Piwik::checkValidLoginString($userLogin);
}
private function checkEmail($email)
{
if ($this->userEmailExists($email)) {
throw new Exception(Piwik::translate('UsersManager_ExceptionEmailExists', $email));
}
if (!Piwik::isValidEmailString($email)) {
throw new Exception(Piwik::translate('UsersManager_ExceptionInvalidEmail'));
}
}
private function getCleanAlias($alias, $userLogin)
{
if (empty($alias)) {
$alias = $userLogin;
}
return $alias;
}
/**
* Add a user in the database.
* A user is defined by
* - a login that has to be unique and valid
* - a password that has to be valid
* - an alias
* - an email that has to be in a correct format
*
* @see userExists()
* @see isValidLoginString()
* @see isValidPasswordString()
* @see isValidEmailString()
*
* @exception in case of an invalid parameter
*/
public function addUser($userLogin, $password, $email, $alias = false)
{
Piwik::checkUserHasSuperUserAccess();
$this->checkLogin($userLogin);
$this->checkEmail($email);
$password = Common::unsanitizeInputValue($password);
UsersManager::checkPassword($password);
$alias = $this->getCleanAlias($alias, $userLogin);
$passwordTransformed = UsersManager::getPasswordHash($password);
$token_auth = $this->getTokenAuth($userLogin, $passwordTransformed);
$this->model->addUser($userLogin, $passwordTransformed, $email, $alias, $token_auth, Date::now()->getDatetime());
// we reload the access list which doesn't yet take in consideration this new user
Access::getInstance()->reloadAccess();
Cache::deleteTrackerCache();
/**
* Triggered after a new user is created.
*
* @param string $userLogin The new user's login handle.
*/
Piwik::postEvent('UsersManager.addUser.end', array($userLogin));
}
/**
* Enable or disable Super user access to the given user login. Note: When granting Super User access all previous
* permissions of the user will be removed as the user gains access to everything.
*
* @param string $userLogin the user login.
* @param bool|int $hasSuperUserAccess true or '1' to grant Super User access, false or '0' to remove Super User
* access.
* @throws \Exception
*/
public function setSuperUserAccess($userLogin, $hasSuperUserAccess)
{
Piwik::checkUserHasSuperUserAccess();
$this->checkUserIsNotAnonymous($userLogin);
$this->checkUserExists($userLogin);
if (!$hasSuperUserAccess && $this->isUserTheOnlyUserHavingSuperUserAccess($userLogin)) {
$message = Piwik::translate("UsersManager_ExceptionRemoveSuperUserAccessOnlySuperUser", $userLogin)
. " "
. Piwik::translate("UsersManager_ExceptionYouMustGrantSuperUserAccessFirst");
throw new Exception($message);
}
$this->model->deleteUserAccess($userLogin);
$this->model->setSuperUserAccess($userLogin, $hasSuperUserAccess);
}
/**
* Detect whether the current user has super user access or not.
*
* @return bool
*/
public function hasSuperUserAccess()
{
return Piwik::hasUserSuperUserAccess();
}
/**
* Returns a list of all Super Users containing there userLogin and email address.
*
* @return array
*/
public function getUsersHavingSuperUserAccess()
{
Piwik::checkUserIsNotAnonymous();
return $this->model->getUsersHavingSuperUserAccess();
}
/**
* Updates a user in the database.
* Only login and password are required (case when we update the password).
* When the password changes, the key token for this user will change, which could break
* its API calls.
*
* @see addUser() for all the parameters
*/
public function updateUser($userLogin, $password = false, $email = false, $alias = false,
$_isPasswordHashed = false)
{
Piwik::checkUserHasSuperUserAccessOrIsTheUser($userLogin);
$this->checkUserIsNotAnonymous($userLogin);
$userInfo = $this->getUser($userLogin);
if (empty($password)) {
$password = $userInfo['password'];
} else {
$password = Common::unsanitizeInputValue($password);
if (!$_isPasswordHashed) {
UsersManager::checkPassword($password);
$password = UsersManager::getPasswordHash($password);
}
}
if (empty($alias)) {
$alias = $userInfo['alias'];
}
if (empty($email)) {
$email = $userInfo['email'];
}
if ($email != $userInfo['email']) {
$this->checkEmail($email);
}
$alias = $this->getCleanAlias($alias, $userLogin);
$token_auth = $this->getTokenAuth($userLogin, $password);
$this->model->updateUser($userLogin, $password, $email, $alias, $token_auth);
Cache::deleteTrackerCache();
/**
* Triggered after an existing user has been updated.
*
* @param string $userLogin The user's login handle.
*/
Piwik::postEvent('UsersManager.updateUser.end', array($userLogin));
}
/**
* Delete a user and all its access, given its login.
*
* @param string $userLogin the user login.
*
* @throws Exception if the user doesn't exist
*
* @return bool true on success
*/
public function deleteUser($userLogin)
{
Piwik::checkUserHasSuperUserAccess();
$this->checkUserIsNotAnonymous($userLogin);
if (!$this->userExists($userLogin)) {
throw new Exception(Piwik::translate("UsersManager_ExceptionDeleteDoesNotExist", $userLogin));
}
if ($this->isUserTheOnlyUserHavingSuperUserAccess($userLogin)) {
$message = Piwik::translate("UsersManager_ExceptionDeleteOnlyUserWithSuperUserAccess", $userLogin)
. " "
. Piwik::translate("UsersManager_ExceptionYouMustGrantSuperUserAccessFirst");
throw new Exception($message);
}
$this->model->deleteUserOnly($userLogin);
$this->model->deleteUserAccess($userLogin);
Cache::deleteTrackerCache();
}
/**
* Returns true if the given userLogin is known in the database
*
* @param string $userLogin
* @return bool true if the user is known
*/
public function userExists($userLogin)
{
if ($userLogin == 'anonymous') {
return true;
}
Piwik::checkUserIsNotAnonymous();
Piwik::checkUserHasSomeViewAccess();
if ($userLogin == Piwik::getCurrentUserLogin()) {
return true;
}
return $this->model->userExists($userLogin);
}
/**
* Returns true if user with given email (userEmail) is known in the database, or the Super User
*
* @param string $userEmail
* @return bool true if the user is known
*/
public function userEmailExists($userEmail)
{
Piwik::checkUserIsNotAnonymous();
return $this->model->userEmailExists($userEmail);
}
/**
* Set an access level to a given user for a list of websites ID.
*
* If access = 'noaccess' the current access (if any) will be deleted.
* If access = 'view' or 'admin' the current access level is deleted and updated with the new value.
*
* @param string $userLogin The user login
* @param string $access Access to grant. Must have one of the following value : noaccess, view, admin
* @param int|array $idSites The array of idSites on which to apply the access level for the user.
* If the value is "all" then we apply the access level to all the websites ID for which the current authentificated user has an 'admin' access.
*
* @throws Exception if the user doesn't exist
* @throws Exception if the access parameter doesn't have a correct value
* @throws Exception if any of the given website ID doesn't exist
*
* @return bool true on success
*/
public function setUserAccess($userLogin, $access, $idSites)
{
$this->checkAccessType($access);
$this->checkUserExists($userLogin);
$this->checkUserHasNotSuperUserAccess($userLogin);
if ($userLogin == 'anonymous'
&& $access == 'admin'
) {
throw new Exception(Piwik::translate("UsersManager_ExceptionAdminAnonymous"));
}
// in case idSites is all we grant access to all the websites on which the current connected user has an 'admin' access
if ($idSites === 'all') {
$idSites = \Piwik\Plugins\SitesManager\API::getInstance()->getSitesIdWithAdminAccess();
} // in case the idSites is an integer we build an array
else {
$idSites = Site::getIdSitesFromIdSitesString($idSites);
}
if (empty($idSites)) {
throw new Exception('Specify at least one website ID in &idSites=');
}
// it is possible to set user access on websites only for the websites admin
// basically an admin can give the view or the admin access to any user for the websites he manages
Piwik::checkUserHasAdminAccess($idSites);
$this->model->deleteUserAccess($userLogin, $idSites);
// if the access is noaccess then we don't save it as this is the default value
// when no access are specified
if ($access != 'noaccess') {
$this->model->addUserAccess($userLogin, $access, $idSites);
}
// we reload the access list which doesn't yet take in consideration this new user access
Access::getInstance()->reloadAccess();
Cache::deleteTrackerCache();
}
/**
* Throws an exception is the user login doesn't exist
*
* @param string $userLogin user login
* @throws Exception if the user doesn't exist
*/
private function checkUserExists($userLogin)
{
if (!$this->userExists($userLogin)) {
throw new Exception(Piwik::translate("UsersManager_ExceptionUserDoesNotExist", $userLogin));
}
}
/**
* Throws an exception is the user email cannot be found
*
* @param string $userEmail user email
* @throws Exception if the user doesn't exist
*/
private function checkUserEmailExists($userEmail)
{
if (!$this->userEmailExists($userEmail)) {
throw new Exception(Piwik::translate("UsersManager_ExceptionUserDoesNotExist", $userEmail));
}
}
private function checkUserIsNotAnonymous($userLogin)
{
if ($userLogin == 'anonymous') {
throw new Exception(Piwik::translate("UsersManager_ExceptionEditAnonymous"));
}
}
private function checkUserHasNotSuperUserAccess($userLogin)
{
if (Piwik::hasTheUserSuperUserAccess($userLogin)) {
throw new Exception(Piwik::translate("UsersManager_ExceptionSuperUserAccess"));
}
}
private function checkAccessType($access)
{
$accessList = Access::getListAccess();
// do not allow to set the superUser access
unset($accessList[array_search("superuser", $accessList)]);
if (!in_array($access, $accessList)) {
throw new Exception(Piwik::translate("UsersManager_ExceptionAccessValues", implode(", ", $accessList)));
}
}
private function isUserTheOnlyUserHavingSuperUserAccess($userLogin)
{
$superUsers = $this->getUsersHavingSuperUserAccess();
return 1 >= count($superUsers) && Piwik::hasTheUserSuperUserAccess($userLogin);
}
/**
* Generates a unique MD5 for the given login & password
*
* @param string $userLogin Login
* @param string $md5Password MD5ied string of the password
* @throws Exception
* @return string
*/
public function getTokenAuth($userLogin, $md5Password)
{
if (strlen($md5Password) != 32) {
throw new Exception(Piwik::translate('UsersManager_ExceptionPasswordMD5HashExpected'));
}
return md5($userLogin . $md5Password);
}
}

View file

@ -0,0 +1,340 @@
<?php
/**
* Piwik - Open source web analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\UsersManager;
use Exception;
use Piwik\API\ResponseBuilder;
use Piwik\Common;
use Piwik\Piwik;
use Piwik\Plugins\LanguagesManager\LanguagesManager;
use Piwik\Plugins\SitesManager\API as APISitesManager;
use Piwik\Plugins\UsersManager\API as APIUsersManager;
use Piwik\Plugins\LanguagesManager\API as APILanguagesManager;
use Piwik\Site;
use Piwik\Tracker\IgnoreCookie;
use Piwik\Url;
use Piwik\View;
use Piwik\MetricsFormatter;
/**
*
*/
class Controller extends \Piwik\Plugin\ControllerAdmin
{
static function orderByName($a, $b)
{
return strcmp($a['name'], $b['name']);
}
/**
* The "Manage Users and Permissions" Admin UI screen
*/
function index()
{
Piwik::checkUserIsNotAnonymous();
$view = new View('@UsersManager/index');
$IdSitesAdmin = APISitesManager::getInstance()->getSitesIdWithAdminAccess();
$idSiteSelected = 1;
if (count($IdSitesAdmin) > 0) {
$defaultWebsiteId = $IdSitesAdmin[0];
$idSiteSelected = Common::getRequestVar('idSite', $defaultWebsiteId);
}
if ($idSiteSelected === 'all') {
$usersAccessByWebsite = array();
$defaultReportSiteName = Piwik::translate('UsersManager_ApplyToAllWebsites');
} else {
$usersAccessByWebsite = APIUsersManager::getInstance()->getUsersAccessFromSite($idSiteSelected);
$defaultReportSiteName = Site::getNameFor($idSiteSelected);
}
// we dont want to display the user currently logged so that the user can't change his settings from admin to view...
$currentlyLogged = Piwik::getCurrentUserLogin();
$usersLogin = APIUsersManager::getInstance()->getUsersLogin();
foreach ($usersLogin as $login) {
if (!isset($usersAccessByWebsite[$login])) {
$usersAccessByWebsite[$login] = 'noaccess';
}
}
unset($usersAccessByWebsite[$currentlyLogged]);
// $usersAccessByWebsite is not supposed to contain unexistant logins, but it does when upgrading from some old Piwik version
foreach ($usersAccessByWebsite as $login => $access) {
if (!in_array($login, $usersLogin)) {
unset($usersAccessByWebsite[$login]);
continue;
}
}
ksort($usersAccessByWebsite);
$users = array();
$superUsers = array();
$usersAliasByLogin = array();
if (Piwik::isUserHasSomeAdminAccess()) {
$view->showLastSeen = true;
$users = APIUsersManager::getInstance()->getUsers();
foreach ($users as $index => $user) {
$usersAliasByLogin[$user['login']] = $user['alias'];
$lastSeen = LastSeenTimeLogger::getLastSeenTimeForUser($user['login']);
$users[$index]['last_seen'] = $lastSeen == 0
? false : MetricsFormatter::getPrettyTimeFromSeconds(time() - $lastSeen);
}
if (Piwik::hasUserSuperUserAccess()) {
foreach ($users as $user) {
if ($user['superuser_access']) {
$superUsers[] = $user['login'];
}
}
}
}
$view->anonymousHasViewAccess = $this->hasAnonymousUserViewAccess($usersAccessByWebsite);
$view->idSiteSelected = $idSiteSelected;
$view->defaultReportSiteName = $defaultReportSiteName;
$view->users = $users;
$view->superUserLogins = $superUsers;
$view->usersAliasByLogin = $usersAliasByLogin;
$view->usersCount = count($users) - 1;
$view->usersAccessByWebsite = $usersAccessByWebsite;
$websites = APISitesManager::getInstance()->getSitesWithAdminAccess();
uasort($websites, array('Piwik\Plugins\UsersManager\Controller', 'orderByName'));
$view->websites = $websites;
$this->setBasicVariablesView($view);
return $view->render();
}
private function hasAnonymousUserViewAccess($usersAccessByWebsite)
{
$anonymousHasViewAccess = false;
foreach ($usersAccessByWebsite as $login => $access) {
if ($login == 'anonymous'
&& $access != 'noaccess'
) {
$anonymousHasViewAccess = true;
}
}
return $anonymousHasViewAccess;
}
/**
* Returns default date for Piwik reports
*
* @param string $user
* @return string today, yesterday, week, month, year
*/
protected function getDefaultDateForUser($user)
{
return APIUsersManager::getInstance()->getUserPreference($user, APIUsersManager::PREFERENCE_DEFAULT_REPORT_DATE);
}
/**
* The "User Settings" admin UI screen view
*/
public function userSettings()
{
Piwik::checkUserIsNotAnonymous();
$view = new View('@UsersManager/userSettings');
$userLogin = Piwik::getCurrentUserLogin();
$user = APIUsersManager::getInstance()->getUser($userLogin);
$view->userAlias = $user['alias'];
$view->userEmail = $user['email'];
$defaultReport = APIUsersManager::getInstance()->getUserPreference($userLogin, APIUsersManager::PREFERENCE_DEFAULT_REPORT);
if ($defaultReport === false) {
$defaultReport = $this->getDefaultWebsiteId();
}
$view->defaultReport = $defaultReport;
if ($defaultReport == 'MultiSites') {
$view->defaultReportSiteName = Site::getNameFor($this->getDefaultWebsiteId());
} else {
$view->defaultReportSiteName = Site::getNameFor($defaultReport);
}
$view->defaultDate = $this->getDefaultDateForUser($userLogin);
$view->availableDefaultDates = array(
'today' => Piwik::translate('General_Today'),
'yesterday' => Piwik::translate('General_Yesterday'),
'previous7' => Piwik::translate('General_PreviousDays', 7),
'previous30' => Piwik::translate('General_PreviousDays', 30),
'last7' => Piwik::translate('General_LastDays', 7),
'last30' => Piwik::translate('General_LastDays', 30),
'week' => Piwik::translate('General_CurrentWeek'),
'month' => Piwik::translate('General_CurrentMonth'),
'year' => Piwik::translate('General_CurrentYear'),
);
$view->languages = APILanguagesManager::getInstance()->getAvailableLanguageNames();
$view->currentLanguageCode = LanguagesManager::getLanguageCodeForCurrentUser();
$view->ignoreCookieSet = IgnoreCookie::isIgnoreCookieFound();
$this->initViewAnonymousUserSettings($view);
$view->piwikHost = Url::getCurrentHost();
$this->setBasicVariablesView($view);
return $view->render();
}
public function setIgnoreCookie()
{
Piwik::checkUserHasSomeViewAccess();
Piwik::checkUserIsNotAnonymous();
$this->checkTokenInUrl();
IgnoreCookie::setIgnoreCookie();
Piwik::redirectToModule('UsersManager', 'userSettings', array('token_auth' => false));
}
/**
* The Super User can modify Anonymous user settings
* @param View $view
*/
protected function initViewAnonymousUserSettings($view)
{
if (!Piwik::hasUserSuperUserAccess()) {
return;
}
$userLogin = 'anonymous';
// Which websites are available to the anonymous users?
$anonymousSitesAccess = APIUsersManager::getInstance()->getSitesAccessFromUser($userLogin);
$anonymousSites = array();
foreach ($anonymousSitesAccess as $info) {
$idSite = $info['site'];
$site = APISitesManager::getInstance()->getSiteFromId($idSite);
// Work around manual website deletion
if (!empty($site)) {
$anonymousSites[$idSite] = $site;
}
}
$view->anonymousSites = $anonymousSites;
// Which report is displayed by default to the anonymous user?
$anonymousDefaultReport = APIUsersManager::getInstance()->getUserPreference($userLogin, APIUsersManager::PREFERENCE_DEFAULT_REPORT);
if ($anonymousDefaultReport === false) {
if (empty($anonymousSites)) {
$anonymousDefaultReport = Piwik::getLoginPluginName();
} else {
// we manually imitate what would happen, in case the anonymous user logs in
// and is redirected to the first website available to him in the list
// @see getDefaultWebsiteId()
reset($anonymousSites);
$anonymousDefaultReport = key($anonymousSites);
}
}
$view->anonymousDefaultReport = $anonymousDefaultReport;
$view->anonymousDefaultDate = $this->getDefaultDateForUser($userLogin);
}
/**
* Records settings for the anonymous users (default report, default date)
*/
public function recordAnonymousUserSettings()
{
$response = new ResponseBuilder(Common::getRequestVar('format'));
try {
Piwik::checkUserHasSuperUserAccess();
$this->checkTokenInUrl();
$anonymousDefaultReport = Common::getRequestVar('anonymousDefaultReport');
$anonymousDefaultDate = Common::getRequestVar('anonymousDefaultDate');
$userLogin = 'anonymous';
APIUsersManager::getInstance()->setUserPreference($userLogin,
APIUsersManager::PREFERENCE_DEFAULT_REPORT,
$anonymousDefaultReport);
APIUsersManager::getInstance()->setUserPreference($userLogin,
APIUsersManager::PREFERENCE_DEFAULT_REPORT_DATE,
$anonymousDefaultDate);
$toReturn = $response->getResponse();
} catch (Exception $e) {
$toReturn = $response->getResponseException($e);
}
return $toReturn;
}
/**
* Records settings from the "User Settings" page
* @throws Exception
*/
public function recordUserSettings()
{
$response = new ResponseBuilder(Common::getRequestVar('format'));
try {
$this->checkTokenInUrl();
$defaultReport = Common::getRequestVar('defaultReport');
$defaultDate = Common::getRequestVar('defaultDate');
$language = Common::getRequestVar('language');
$userLogin = Piwik::getCurrentUserLogin();
$this->processPasswordChange($userLogin);
LanguagesManager::setLanguageForSession($language);
APILanguagesManager::getInstance()->setLanguageForUser($userLogin, $language);
APIUsersManager::getInstance()->setUserPreference($userLogin,
APIUsersManager::PREFERENCE_DEFAULT_REPORT,
$defaultReport);
APIUsersManager::getInstance()->setUserPreference($userLogin,
APIUsersManager::PREFERENCE_DEFAULT_REPORT_DATE,
$defaultDate);
$toReturn = $response->getResponse();
} catch (Exception $e) {
$toReturn = $response->getResponseException($e);
}
return $toReturn;
}
private function processPasswordChange($userLogin)
{
$alias = Common::getRequestVar('alias');
$email = Common::getRequestVar('email');
$newPassword = false;
$password = Common::getRequestvar('password', false);
$passwordBis = Common::getRequestvar('passwordBis', false);
if (!empty($password)
|| !empty($passwordBis)
) {
if ($password != $passwordBis) {
throw new Exception(Piwik::translate('Login_PasswordsDoNotMatch'));
}
$newPassword = $password;
}
// UI disables password change on invalid host, but check here anyway
if (!Url::isValidHost()
&& $newPassword !== false
) {
throw new Exception("Cannot change password with untrusted hostname!");
}
APIUsersManager::getInstance()->updateUser($userLogin, $newPassword, $email, $alias);
if ($newPassword !== false) {
$newPassword = Common::unsanitizeInputValue($newPassword);
}
// logs the user in with the new password
if ($newPassword !== false) {
\Piwik\Registry::get('auth')->initSession($userLogin, md5($newPassword), $rememberMe = false);
}
}
}

View file

@ -0,0 +1,72 @@
<?php
/**
* Piwik - Open source web analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\UsersManager;
use Piwik\Piwik;
use Piwik\Common;
use Piwik\Option;
/**
* Class that logs the time the current user is accessing the current resource (which
* is 'now') so it can be retrieved later.
*/
class LastSeenTimeLogger
{
const OPTION_PREFIX = 'UsersManager.lastSeen.';
/**
* The amount of time in seconds that a last seen value is considered valid. We don't want
* to update the database for every request made by every user, so we only do it if the time
* has been at least this many seconds from the last known time.
*/
const LAST_TIME_SAVE_DELTA = 300;
/**
* Saves the current time for a user as an option if the current request is for something
* in the reporting UI, the current user is not anonymous and the time hasn't been saved
* in the last 5 minutes.
*/
public function logCurrentUserLastSeenTime()
{
$module = Common::getRequestVar('module', false);
$action = Common::getRequestVar('action', false);
$currentUserLogin = Piwik::getCurrentUserLogin();
// only log time for non-anonymous visits to the reporting UI
if ($module == 'API'
|| $module == 'Proxy'
|| $currentUserLogin == 'anonymous'
) {
return;
}
// get the last known time
$optionName = self::OPTION_PREFIX . $currentUserLogin;
$lastSeen = Option::get($optionName);
// do not log if last known time is less than N minutes from now (so we don't make too many
// queries)
if (time() - $lastSeen <= self::LAST_TIME_SAVE_DELTA) {
return;
}
// log last seen time (Note: autoload is important so the Option::get above does not result in
// a separate query)
Option::set($optionName, time(), $autoload = 1);
}
/**
* Returns the time a user was last seen or `false` if the user has never logged in.
*/
public static function getLastSeenTimeForUser($userName)
{
$optionName = self::OPTION_PREFIX . $userName;
return Option::get($optionName);
}
}

View file

@ -0,0 +1,274 @@
<?php
/**
* Piwik - Open source web analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\UsersManager;
use Piwik\Common;
use Piwik\Db;
use Piwik\Piwik;
/**
* The UsersManager API lets you Manage Users and their permissions to access specific websites.
*
* You can create users via "addUser", update existing users via "updateUser" and delete users via "deleteUser".
* There are many ways to list users based on their login "getUser" and "getUsers", their email "getUserByEmail",
* or which users have permission (view or admin) to access the specified websites "getUsersWithSiteAccess".
*
* Existing Permissions are listed given a login via "getSitesAccessFromUser", or a website ID via "getUsersAccessFromSite",
* or you can list all users and websites for a given permission via "getUsersSitesFromAccess". Permissions are set and updated
* via the method "setUserAccess".
* See also the documentation about <a href='http://piwik.org/docs/manage-users/' target='_blank'>Managing Users</a> in Piwik.
*/
class Model
{
/**
* Returns the list of all the users
*
* @param string[] $userLogins List of users to select. If empty, will return all users
* @return array the list of all the users
*/
public function getUsers(array $userLogins)
{
$where = '';
$bind = array();
if (!empty($userLogins)) {
$where = 'WHERE login IN (' . Common::getSqlStringFieldsArray($userLogins) . ')';
$bind = $userLogins;
}
$users = Db::get()->fetchAll("SELECT *
FROM " . Common::prefixTable("user") . "
$where
ORDER BY login ASC", $bind);
return $users;
}
/**
* Returns the list of all the users login
*
* @return array the list of all the users login
*/
public function getUsersLogin()
{
$users = Db::get()->fetchAll("SELECT login
FROM " . Common::prefixTable("user") . "
ORDER BY login ASC");
$return = array();
foreach ($users as $login) {
$return[] = $login['login'];
}
return $return;
}
public function getUsersSitesFromAccess($access)
{
$users = Db::get()->fetchAll("SELECT login,idsite
FROM " . Common::prefixTable("access")
. " WHERE access = ?
ORDER BY login, idsite", $access);
$return = array();
foreach ($users as $user) {
$return[$user['login']][] = $user['idsite'];
}
return $return;
}
public function getUsersAccessFromSite($idSite)
{
$users = Db::get()->fetchAll("SELECT login,access
FROM " . Common::prefixTable("access")
. " WHERE idsite = ?", $idSite);
$return = array();
foreach ($users as $user) {
$return[$user['login']] = $user['access'];
}
return $return;
}
public function getUsersLoginWithSiteAccess($idSite, $access)
{
$users = Db::get()->fetchAll("SELECT login
FROM " . Common::prefixTable("access")
. " WHERE idsite = ? AND access = ?", array($idSite, $access));
$logins = array();
foreach ($users as $user) {
$logins[] = $user['login'];
}
return $logins;
}
/**
* For each website ID, returns the access level of the given $userLogin.
* If the user doesn't have any access to a website ('noaccess'),
* this website will not be in the returned array.
* If the user doesn't have any access, the returned array will be an empty array.
*
* @param string $userLogin User that has to be valid
*
* @return array The returned array has the format
* array(
* idsite1 => 'view',
* idsite2 => 'admin',
* idsite3 => 'view',
* ...
* )
*/
public function getSitesAccessFromUser($userLogin)
{
$users = Db::get()->fetchAll("SELECT idsite,access
FROM " . Common::prefixTable("access")
. " WHERE login = ?", $userLogin);
$return = array();
foreach ($users as $user) {
$return[] = array(
'site' => $user['idsite'],
'access' => $user['access'],
);
}
return $return;
}
public function getUser($userLogin)
{
return Db::get()->fetchRow("SELECT *
FROM " . Common::prefixTable("user")
. " WHERE login = ?", $userLogin);
}
public function getUserByEmail($userEmail)
{
return Db::get()->fetchRow("SELECT *
FROM " . Common::prefixTable("user")
. " WHERE email = ?", $userEmail);
}
public function getUserByTokenAuth($tokenAuth)
{
return Db::get()->fetchRow('SELECT *
FROM ' . Common::prefixTable('user') . '
WHERE token_auth = ?', $tokenAuth);
}
public function addUser($userLogin, $passwordTransformed, $email, $alias, $tokenAuth, $dateRegistered)
{
$user = array(
'login' => $userLogin,
'password' => $passwordTransformed,
'alias' => $alias,
'email' => $email,
'token_auth' => $tokenAuth,
'date_registered' => $dateRegistered,
'superuser_access' => 0
);
Db::get()->insert(Common::prefixTable("user"), $user);
}
public function setSuperUserAccess($userLogin, $hasSuperUserAccess)
{
Db::get()->update(Common::prefixTable("user"),
array(
'superuser_access' => $hasSuperUserAccess ? 1 : 0
),
"login = '$userLogin'"
);
}
public function getUsersHavingSuperUserAccess()
{
$users = Db::get()->fetchAll("SELECT login, email
FROM " . Common::prefixTable("user") . "
WHERE superuser_access = 1
ORDER BY date_registered ASC");
return $users;
}
public function updateUser($userLogin, $password, $email, $alias, $tokenAuth)
{
Db::get()->update(Common::prefixTable("user"),
array(
'password' => $password,
'alias' => $alias,
'email' => $email,
'token_auth' => $tokenAuth
),
"login = '$userLogin'"
);
}
public function userExists($userLogin)
{
$count = Db::get()->fetchOne("SELECT count(*)
FROM " . Common::prefixTable("user") . "
WHERE login = ?", $userLogin);
return $count != 0;
}
public function userEmailExists($userEmail)
{
$count = Db::get()->fetchOne("SELECT count(*)
FROM " . Common::prefixTable("user") . "
WHERE email = ?", $userEmail);
return $count != 0;
}
public function addUserAccess($userLogin, $access, $idSites)
{
foreach ($idSites as $idsite) {
Db::get()->insert(Common::prefixTable("access"),
array("idsite" => $idsite,
"login" => $userLogin,
"access" => $access)
);
}
}
public function deleteUserOnly($userLogin)
{
Db::get()->query("DELETE FROM " . Common::prefixTable("user") . " WHERE login = ?", $userLogin);
/**
* Triggered after a user has been deleted.
*
* This event should be used to clean up any data that is related to the now deleted user.
* The **Dashboard** plugin, for example, uses this event to remove the user's dashboards.
*
* @param string $userLogin The login handle of the deleted user.
*/
Piwik::postEvent('UsersManager.deleteUser', array($userLogin));
}
public function deleteUserAccess($userLogin, $idSites = null)
{
if (is_null($idSites)) {
Db::get()->query("DELETE FROM " . Common::prefixTable("access") .
" WHERE login = ?",
array($userLogin));
} else {
foreach ($idSites as $idsite) {
Db::get()->query("DELETE FROM " . Common::prefixTable("access") .
" WHERE idsite = ? AND login = ?",
array($idsite, $userLogin)
);
}
}
}
}

View file

@ -0,0 +1,154 @@
<?php
/**
* Piwik - Open source web analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\UsersManager;
use Exception;
use Piwik\Db;
use Piwik\Menu\MenuAdmin;
use Piwik\Option;
use Piwik\Piwik;
use Piwik\SettingsPiwik;
/**
* Manage Piwik users
*
*/
class UsersManager extends \Piwik\Plugin
{
const PASSWORD_MIN_LENGTH = 6;
const PASSWORD_MAX_LENGTH = 26;
/**
* @see Piwik\Plugin::getListHooksRegistered
*/
public function getListHooksRegistered()
{
return array(
'Menu.Admin.addItems' => 'addMenu',
'AssetManager.getJavaScriptFiles' => 'getJsFiles',
'AssetManager.getStylesheetFiles' => 'getStylesheetFiles',
'SitesManager.deleteSite.end' => 'deleteSite',
'Tracker.Cache.getSiteAttributes' => 'recordAdminUsersInCache',
'Translate.getClientSideTranslationKeys' => 'getClientSideTranslationKeys',
'Platform.initialized' => 'onPlatformInitialized'
);
}
public function onPlatformInitialized()
{
$lastSeenTimeLogger = new LastSeenTimeLogger();
$lastSeenTimeLogger->logCurrentUserLastSeenTime();
}
/**
* Hooks when a website tracker cache is flushed (website/user updated, cache deleted, or empty cache)
* Will record in the tracker config file the list of Admin token_auth for this website. This
* will be used when the Tracking API is used with setIp(), setForceDateTime(), setVisitorId(), etc.
*
* @param $attributes
* @param $idSite
* @return void
*/
public function recordAdminUsersInCache(&$attributes, $idSite)
{
// add the 'hosts' entry in the website array
$users = API::getInstance()->getUsersWithSiteAccess($idSite, 'admin');
$tokens = array();
foreach ($users as $user) {
$tokens[] = $user['token_auth'];
}
$attributes['admin_token_auth'] = $tokens;
}
/**
* Delete user preferences associated with a particular site
*/
public function deleteSite($idSite)
{
Option::deleteLike('%\_' . API::PREFERENCE_DEFAULT_REPORT, $idSite);
}
/**
* Return list of plug-in specific JavaScript files to be imported by the asset manager
*
* @see Piwik\AssetManager
*/
public function getJsFiles(&$jsFiles)
{
$jsFiles[] = "plugins/UsersManager/javascripts/usersManager.js";
$jsFiles[] = "plugins/UsersManager/javascripts/usersSettings.js";
}
/**
* Get CSS files
*/
function getStylesheetFiles(&$stylesheets)
{
$stylesheets[] = "plugins/UsersManager/stylesheets/usersManager.less";
}
/**
* Add admin menu items
*/
function addMenu()
{
MenuAdmin::getInstance()->add('CoreAdminHome_MenuManage', 'UsersManager_MenuUsers',
array('module' => 'UsersManager', 'action' => 'index'),
Piwik::isUserHasSomeAdminAccess(),
$order = 2);
MenuAdmin::getInstance()->add('CoreAdminHome_MenuManage', 'UsersManager_MenuUserSettings',
array('module' => 'UsersManager', 'action' => 'userSettings'),
Piwik::isUserHasSomeViewAccess(),
$order = 3);
}
/**
* Returns true if the password is complex enough (at least 6 characters and max 26 characters)
*
* @param $input string
* @return bool
*/
public static function isValidPasswordString($input)
{
if (!SettingsPiwik::isUserCredentialsSanityCheckEnabled()
&& !empty($input)
) {
return true;
}
$l = strlen($input);
return $l >= self::PASSWORD_MIN_LENGTH && $l <= self::PASSWORD_MAX_LENGTH;
}
public static function checkPassword($password)
{
if (!self::isValidPasswordString($password)) {
throw new Exception(Piwik::translate('UsersManager_ExceptionInvalidPassword', array(self::PASSWORD_MIN_LENGTH,
self::PASSWORD_MAX_LENGTH)));
}
}
public static function getPasswordHash($password)
{
// if change here, should also edit the installation process
// to change how the root pwd is saved in the config file
return md5($password);
}
public function getClientSideTranslationKeys(&$translationKeys)
{
$translationKeys[] = "General_OrCancel";
$translationKeys[] = "General_Save";
$translationKeys[] = "General_Done";
$translationKeys[] = "UsersManager_DeleteConfirm";
$translationKeys[] = "UsersManager_ConfirmGrantSuperUserAccess";
$translationKeys[] = "UsersManager_ConfirmProhibitOtherUsersSuperUserAccess";
$translationKeys[] = "UsersManager_ConfirmProhibitMySuperUserAccess";
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 653 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 851 B

View file

@ -0,0 +1,302 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
function sendUpdateUserAJAX(row) {
var parameters = {};
parameters.userLogin = $(row).children('#userLogin').html();
var password = $(row).find('input#password').val();
if (password != '-') parameters.password = password;
parameters.email = $(row).find('input#email').val();
parameters.alias = $(row).find('input#alias').val();
var ajaxHandler = new ajaxHelper();
ajaxHandler.addParams({
module: 'API',
format: 'json',
method: 'UsersManager.updateUser'
}, 'GET');
ajaxHandler.addParams(parameters, 'POST');
ajaxHandler.redirectOnSuccess();
ajaxHandler.setLoadingElement();
ajaxHandler.send(true);
}
function sendDeleteUserAJAX(login) {
var ajaxHandler = new ajaxHelper();
ajaxHandler.addParams({
module: 'API',
format: 'json',
method: 'UsersManager.deleteUser'
}, 'GET');
ajaxHandler.addParams({userLogin: login}, 'POST');
ajaxHandler.redirectOnSuccess();
ajaxHandler.setLoadingElement('#ajaxLoadingUsersManagement');
ajaxHandler.setErrorElement('#ajaxErrorUsersManagement');
ajaxHandler.send(true);
}
function sendAddUserAJAX(row) {
var parameters = {};
parameters.userLogin = $(row).find('input#useradd_login').val();
parameters.password = $(row).find('input#useradd_password').val();
parameters.email = $(row).find('input#useradd_email').val();
parameters.alias = $(row).find('input#useradd_alias').val();
var ajaxHandler = new ajaxHelper();
ajaxHandler.addParams({
module: 'API',
format: 'json',
method: 'UsersManager.addUser'
}, 'GET');
ajaxHandler.addParams(parameters, 'POST');
ajaxHandler.redirectOnSuccess();
ajaxHandler.setLoadingElement('#ajaxLoadingUsersManagement');
ajaxHandler.setErrorElement('#ajaxErrorUsersManagement');
ajaxHandler.send(true);
}
function getIdSites() {
return $('#usersManagerSiteSelect').attr('siteid');
}
function sendUpdateUserAccess(login, access, successCallback) {
var parameters = {};
parameters.userLogin = login;
parameters.access = access;
parameters.idSites = getIdSites();
var ajaxHandler = new ajaxHelper();
ajaxHandler.addParams({
module: 'API',
format: 'json',
method: 'UsersManager.setUserAccess'
}, 'GET');
ajaxHandler.addParams(parameters, 'POST');
ajaxHandler.setCallback(successCallback);
ajaxHandler.setLoadingElement('#ajaxLoadingUsersManagement');
ajaxHandler.setErrorElement('#ajaxErrorUsersManagement');
ajaxHandler.send(true);
}
function submitOnEnter(e) {
var key = e.keyCode || e.which;
if (key == 13) {
$(this).find('.adduser').click();
$(this).find('.updateuser').click();
}
}
function launchAjaxRequest(self, successCallback) {
sendUpdateUserAccess(
$(self).parent().parent().find('#login').html(), //if changed change also the modal
$(self).parent().attr('id'),
successCallback
);
}
function updateSuperUserAccess(login, hasSuperUserAccess)
{
var parameters = {};
parameters.userLogin = login;
parameters.hasSuperUserAccess = hasSuperUserAccess ? 1: 0;
var ajaxHandler = new ajaxHelper();
ajaxHandler.addParams({
module: 'API',
format: 'json',
method: 'UsersManager.setSuperUserAccess'
}, 'GET');
ajaxHandler.addParams(parameters, 'POST');
ajaxHandler.setCallback(function () {
var UI = require('piwik/UI');
var notification = new UI.Notification();
notification.show(_pk_translate('General_Done'), {
placeat: '#superUserAccessUpdated',
context: 'success',
noclear: true,
type: 'toast',
style: {display: 'inline-block', marginTop: '10px', marginBottom: '30px'},
id: 'usersManagerSuperUserAccessUpdated'
});
notification.scrollToNotification();
piwikHelper.redirect();
});
ajaxHandler.setLoadingElement('#ajaxErrorSuperUsersManagement');
ajaxHandler.setErrorElement('#ajaxErrorSuperUsersManagement');
ajaxHandler.send(true);
}
function bindUpdateSuperUserAccess() {
var login = $(this).parents('td').data('login');
var hasAccess = parseInt($(this).data('hasaccess'), 10);
var message = 'UsersManager_ConfirmGrantSuperUserAccess';
if (hasAccess && login == piwik.userLogin) {
message = 'UsersManager_ConfirmProhibitMySuperUserAccess';
} else if (hasAccess) {
message = 'UsersManager_ConfirmProhibitOtherUsersSuperUserAccess';
}
message = _pk_translate(message);
message = message.replace('%s', login);
$('#superUserAccessConfirm h2').text(message);
piwikHelper.modalConfirm('#superUserAccessConfirm', {yes: function () {
updateSuperUserAccess(login, !hasAccess);
}});
}
function bindUpdateAccess() {
var self = this;
// callback called when the ajax request Update the user permissions is successful
function successCallback(response) {
var mainDiv = $(self).parent().parent();
var login = $('#login', mainDiv).text();
mainDiv.find('.accessGranted')
.attr("src", "plugins/UsersManager/images/no-access.png")
.attr("class", "updateAccess")
.click(bindUpdateAccess)
;
$(self)
.attr('src', "plugins/UsersManager/images/ok.png")
.attr('class', "accessGranted")
;
var UI = require('piwik/UI');
var notification = new UI.Notification();
notification.show(_pk_translate('General_Done'), {
placeat: '#accessUpdated',
context: 'success',
noclear: true,
type: 'toast',
style: {display: 'inline-block', marginTop: '10px'},
id: 'usersManagerAccessUpdated'
});
// reload if user anonymous was updated, since we display a Notice message when anon has view access
if (login == 'anonymous') {
window.location.reload();
}
}
var idSite = getIdSites();
if (idSite == 'all') {
var target = this;
//ask confirmation
var userLogin = $(this).parent().parent().find('#login').text();
$('#confirm').find('#login').text(userLogin); // if changed here change also the launchAjaxRequest
function onValidate() {
launchAjaxRequest(target, successCallback);
}
piwikHelper.modalConfirm('#confirm', {yes: onValidate})
}
else {
launchAjaxRequest(this, successCallback);
}
}
$(document).ready(function () {
var alreadyEdited = [];
// when click on edituser, the cells become editable
$('.edituser')
.click(function () {
piwikHelper.hideAjaxError();
var idRow = $(this).attr('id');
if (alreadyEdited[idRow] == 1) return;
alreadyEdited[idRow] = 1;
$('tr#' + idRow + ' .editable').each(
// make the fields editable
// change the EDIT button to VALID button
function (i, n) {
var contentBefore = $(n).text();
var idName = $(n).attr('id');
if (idName != 'userLogin') {
var contentAfter = '<input id="' + idName + '" value="' + piwikHelper.htmlEntities(contentBefore) + '" size="25" />';
$(n).html(contentAfter);
}
}
);
$(this)
.toggle()
.parent()
.prepend($('<input type="submit" class="submit updateuser" value="' + _pk_translate('General_Save') + '" />')
.click(function () {
var onValidate = function () {
sendUpdateUserAJAX($('tr#' + idRow));
};
if ($('tr#' + idRow).find('input#password').val() != '-') {
piwikHelper.modalConfirm('#confirmPasswordChange', {yes: onValidate});
} else {
onValidate();
}
})
);
});
$('.editable').keypress(submitOnEnter);
$('td.editable')
.click(function () { $(this).parent().find('.edituser').click(); });
// when click on deleteuser, the we ask for confirmation and then delete the user
$('.deleteuser')
.click(function () {
piwikHelper.hideAjaxError();
var idRow = $(this).attr('id');
var loginToDelete = $(this).parent().parent().find('#userLogin').html();
$('#confirmUserRemove').find('h2').text(sprintf(_pk_translate('UsersManager_DeleteConfirm'), '"' + loginToDelete + '"'));
piwikHelper.modalConfirm('#confirmUserRemove', {yes: function () { sendDeleteUserAJAX(loginToDelete); }});
}
);
$('.addrow').click(function () {
piwikHelper.hideAjaxError();
$(this).toggle();
var numberOfRows = $('table#users')[0].rows.length;
var newRowId = numberOfRows + 1;
newRowId = 'row' + newRowId;
$($.parseHTML(' <tr id="' + newRowId + '">\
<td><input id="useradd_login" value="login?" size="10" /></td>\
<td><input id="useradd_password" value="password" size="10" /></td>\
<td><input id="useradd_email" value="email@domain.com" size="15" /></td>\
<td><input id="useradd_alias" value="alias" size="15" /></td>\
<td>-</td>\
<td>-</td>\
<td><input type="submit" class="submit adduser" value="' + _pk_translate('General_Save') + '" /></td>\
<td><span class="cancel">' + sprintf(_pk_translate('General_OrCancel'), "", "") + '</span></td>\
</tr>'))
.appendTo('#users')
;
$('#' + newRowId).keypress(submitOnEnter);
$('.adduser').click(function () { sendAddUserAJAX($('tr#' + newRowId)); });
$('.cancel').click(function () {
piwikHelper.hideAjaxError();
$(this).parents('tr').remove();
$('.addrow').toggle();
});
});
$('#access .updateAccess')
.click(bindUpdateAccess);
$('#superUserAccess .accessGranted, #superUserAccess .updateAccess').click(bindUpdateSuperUserAccess);
// when a site is selected, reload the page w/o showing the ajax loading element
$('#usersManagerSiteSelect').bind('change', function (e, site) {
if (site.id != piwik.idSite) {
piwik.broadcast.propagateNewPage('segment=&idSite=' + site.id, false);
}
});
});

View file

@ -0,0 +1,96 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
function sendUserSettingsAJAX() {
var params;
var defaultDate = $('input[name=defaultDate]:checked').val();
if (defaultDate == 'today' || defaultDate == 'yesterday') {
params = 'period=day&date=' + defaultDate;
} else if (defaultDate.indexOf('last') >= 0
|| defaultDate.indexOf('previous') >= 0) {
params = 'period=range&date=' + defaultDate;
} else {
params = 'date=today&period=' + defaultDate;
}
var alias = $('#alias').val();
var email = $('#email').val();
var password = $('#password').val();
var passwordBis = $('#passwordBis').val();
var defaultReport = $('input[name=defaultReport]:checked').val();
if (defaultReport == 1) {
defaultReport = $('#defaultReportSiteSelector').attr('siteid');
}
var postParams = {};
postParams.alias = alias;
postParams.email = email;
if (password) {
postParams.password = password;
}
if (passwordBis) {
postParams.passwordBis = passwordBis;
}
postParams.defaultReport = defaultReport;
postParams.defaultDate = defaultDate;
postParams.language = $('#userSettingsTable #language').val();
var ajaxHandler = new ajaxHelper();
ajaxHandler.addParams({
module: 'UsersManager',
format: 'json',
action: 'recordUserSettings'
}, 'GET');
ajaxHandler.addParams(postParams, 'POST');
ajaxHandler.redirectOnSuccess(params);
ajaxHandler.setLoadingElement('#ajaxLoadingUserSettings');
ajaxHandler.setErrorElement('#ajaxErrorUserSettings');
ajaxHandler.send(true);
}
function sendAnonymousUserSettingsAJAX() {
var anonymousDefaultReport = $('input[name=anonymousDefaultReport]:checked').val();
if (anonymousDefaultReport == 1) {
anonymousDefaultReport = $('#anonymousDefaultReportWebsite').find('option:selected').val();
}
var anonymousDefaultDate = $('input[name=anonymousDefaultDate]:checked').val();
var ajaxHandler = new ajaxHelper();
ajaxHandler.addParams({
module: 'UsersManager',
format: 'json',
action: 'recordAnonymousUserSettings'
}, 'GET');
ajaxHandler.addParams({
anonymousDefaultReport: anonymousDefaultReport,
anonymousDefaultDate: anonymousDefaultDate
}, 'POST');
ajaxHandler.redirectOnSuccess();
ajaxHandler.setLoadingElement('#ajaxLoadingAnonymousUserSettings');
ajaxHandler.setErrorElement('#ajaxErrorAnonymousUserSettings');
ajaxHandler.send(true);
}
$(document).ready(function () {
$('#userSettingsSubmit').click(function () {
if ($('#password').length > 0 && $('#password').val() != '') {
piwikHelper.modalConfirm('#confirmPasswordChange', {yes: sendUserSettingsAJAX});
} else {
sendUserSettingsAJAX();
}
});
$('#userSettingsTable').find('input').keypress(function (e) {
var key = e.keyCode || e.which;
if (key == 13) {
$('#userSettingsSubmit').click();
}
});
$('#anonymousUserSettingsSubmit').click(function () {
sendAnonymousUserSettingsAJAX();
});
});

View file

@ -0,0 +1,19 @@
#users .editable:hover,
#users .addrow:hover,
#users .updateAccess:hover,
#users .accessGranted:hover,
#users .adduser:hover, .edituser:hover,
#users .deleteuser:hover,
#users .updateuser:hover,
#users .cancel:hover {
cursor: pointer;
}
#sites.usersManager .sites_selector_title {
display:inline-block;
margin-top:5px;
}
.old-ie #sites.usersManager .sites_selector_title {
height: 30px;
}

View file

@ -0,0 +1,210 @@
{% extends 'admin.twig' %}
{% block content %}
<h2 piwik-enriched-headline
help-url="http://piwik.org/docs/manage-users/">{{ 'UsersManager_ManageAccess'|translate }}</h2>
<div id="sites" class="usersManager">
<section class="sites_selector_container">
<p>{{ 'UsersManager_MainDescription'|translate }}</p>
<div class="sites_selector_title">{{ 'SitesManager_Sites'|translate }}:</div>
{% set applyAllSitesText %}
<strong>{{ 'UsersManager_ApplyToAllWebsites'|translate }}</strong>
{% endset %}
<div piwik-siteselector
class="sites_autocomplete"
siteid="{{ idSiteSelected }}"
sitename="{{ defaultReportSiteName }}"
all-sites-text="{{ applyAllSitesText|raw }}"
all-sites-location="top"
id="usersManagerSiteSelect"
switch-site-on-select="false"></div>
</section>
</div>
{% import 'ajaxMacros.twig' as ajax %}
{{ ajax.errorDiv }}
{{ ajax.loadingDiv }}
<div class="entityContainer" style="width:600px;">
{% if anonymousHasViewAccess %}
<div style="display:inline-block;margin-top:10px;" id="usersManagerAnonymousUserHasViewAccess">
{{ ['UsersManager_AnonymousUserHasViewAccess'|translate("'anonymous'","'view'"), 'UsersManager_AnonymousUserHasViewAccess2'|translate]|join(' ')|notification({'placeAt': '#usersManagerAnonymousUserHasViewAccess', 'noclear': true}) }}
</div>
{% endif %}
<table class="entityTable dataTable" id="access" style="display:inline-table;width:500px;">
<thead>
<tr>
<th class='first'>{{ 'UsersManager_User'|translate }}</th>
<th>{{ 'UsersManager_Alias'|translate }}</th>
<th>{{ 'UsersManager_PrivNone'|translate }}</th>
<th>{{ 'UsersManager_PrivView'|translate }}</th>
<th>{{ 'UsersManager_PrivAdmin'|translate }}</th>
</tr>
</thead>
<tbody>
{% set accesValid %}<img src='plugins/UsersManager/images/ok.png' class='accessGranted' />{% endset %}
{% set accesInvalid %}<img src='plugins/UsersManager/images/no-access.png' class='updateAccess' />{% endset %}
{% set superUserAccess %}<span title="{{ 'UsersManager_ExceptionSuperUserAccess'|translate }}">N/A</span>{% endset %}
{% for login,access in usersAccessByWebsite %}
<tr>
<td id='login'>{{ login }}</td>
<td>{{ usersAliasByLogin[login]|raw }}</td>
<td id='noaccess'>
{% if login in superUserLogins %}
{{ superUserAccess }}
{% elseif access=='noaccess' and idSiteSelected != 'all' %}
{{ accesValid }}
{% else %}
{{ accesInvalid }}
{% endif %}&nbsp;</td>
<td id='view'>
{% if login in superUserLogins %}
{{ superUserAccess }}
{% elseif access == 'view' and idSiteSelected != 'all' %}
{{ accesValid }}
{% else %}
{{ accesInvalid }}
{% endif %}&nbsp;</td>
<td id='admin'>
{% if login in superUserLogins %}
{{ superUserAccess }}
{% elseif login == 'anonymous' %}
N/A
{% else %}
{% if access == 'admin' and idSiteSelected != 'all' %}{{ accesValid }}{% else %}{{ accesInvalid }}{% endif %}&nbsp;
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div id="accessUpdated" style="vertical-align:top;"></div>
</div>
<div class="ui-confirm" id="confirm">
<h2>{{ 'UsersManager_ChangeAllConfirm'|translate("<span id='login'></span>")|raw }}</h2>
<input role="yes" type="button" value="{{ 'General_Yes'|translate }}"/>
<input role="no" type="button" value="{{ 'General_No'|translate }}"/>
</div>
{% if userIsSuperUser %}
<div class="ui-confirm" id="confirmUserRemove">
<h2></h2>
<input role="yes" type="button" value="{{ 'General_Yes'|translate }}"/>
<input role="no" type="button" value="{{ 'General_No'|translate }}"/>
</div>
<div class="ui-confirm" id="confirmPasswordChange">
<h2>{{ 'UsersManager_ChangePasswordConfirm'|translate }}</h2>
<input role="yes" type="button" value="{{ 'General_Yes'|translate }}"/>
<input role="no" type="button" value="{{ 'General_No'|translate }}"/>
</div>
<br/>
<h2>{{ 'UsersManager_UsersManagement'|translate }}</h2>
<p>{{ 'UsersManager_UsersManagementMainDescription'|translate }}
{{ 'UsersManager_ThereAreCurrentlyNRegisteredUsers'|translate("<b>"~usersCount~"</b>")|raw }}</p>
{% import 'ajaxMacros.twig' as ajax %}
{{ ajax.errorDiv('ajaxErrorUsersManagement') }}
{{ ajax.loadingDiv('ajaxLoadingUsersManagement') }}
<div class="entityContainer" style="margin-bottom:50px;">
<table class="entityTable dataTable" id="users">
<thead>
<tr>
<th>{{ 'General_Username'|translate }}</th>
<th>{{ 'General_Password'|translate }}</th>
<th>{{ 'UsersManager_Email'|translate }}</th>
<th>{{ 'UsersManager_Alias'|translate }}</th>
<th>token_auth</th>
{% if showLastSeen is defined and showLastSeen %}
<th>{{ 'UsersManager_LastSeen'|translate }}</th>
{% endif %}
<th>{{ 'General_Edit'|translate }}</th>
<th>{{ 'General_Delete'|translate }}</th>
</tr>
</thead>
<tbody>
{% for i,user in users %}
{% if user.login != 'anonymous' %}
<tr class="editable" id="row{{ i }}">
<td id="userLogin" class="editable">{{ user.login }}</td>
<td id="password" class="editable">-</td>
<td id="email" class="editable">{{ user.email }}</td>
<td id="alias" class="editable">{{ user.alias|raw }}</td>
<td id="token_auth">{{ user.token_auth }}</td>
{% if user.last_seen is defined %}
<td id="last_seen">{% if user.last_seen is empty %}-{% else %}{{ 'General_TimeAgo'|translate(user.last_seen)|raw }}{% endif %}</td>
{% endif %}
<td>
<span class="edituser link_but" id="row{{ i }}">
<img title="{{ 'General_Edit'|translate }}" src='plugins/Zeitgeist/images/ico_edit.png'/>
<span>{{ 'General_Edit'|translate }}</span>
</span>
</td>
<td>
<span class="deleteuser link_but" id="row{{ i }}">
<img title="{{ 'General_Delete'|translate }}" src='plugins/Zeitgeist/images/ico_delete.png'/>
<span>{{ 'General_Delete'|translate }}</span>
</span>
</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
<div class="addrow"><img src='plugins/UsersManager/images/add.png'/> {{ 'UsersManager_AddUser'|translate }}</div>
</div>
<h2 id="super_user_access">{{ 'UsersManager_SuperUserAccessManagement'|translate }}</h2>
<p>{{ 'UsersManager_SuperUserAccessManagementMainDescription'|translate }} <br/>
{{ 'UsersManager_SuperUserAccessManagementGrantMore'|translate }}</p>
{{ ajax.errorDiv('ajaxErrorSuperUsersManagement') }}
{{ ajax.loadingDiv('ajaxLoadingSuperUsersManagement') }}
<table class="entityTable dataTable" id="superUserAccess" style="display:inline-table;width:400px;">
<thead>
<tr>
<th class='first'>{{ 'UsersManager_User'|translate }}</th>
<th>{{ 'UsersManager_Alias'|translate }}</th>
<th>{{ 'Installation_SuperUser'|translate }}</th>
</tr>
</thead>
<tbody>
{% if users|length > 1 %}
{% for login,alias in usersAliasByLogin if login != 'anonymous' %}
<tr>
<td id='login'>{{ login }}</td>
<td>{{ alias|raw }}</td>
<td id='superuser' data-login="{{ login|e('html_attr') }}">
<img src='plugins/UsersManager/images/ok.png' class='accessGranted' data-hasaccess="1" {% if not (login in superUserLogins) %}style="display:none"{% endif %} />
<img src='plugins/UsersManager/images/no-access.png' class='updateAccess' data-hasaccess="0" {% if login in superUserLogins %}style="display:none"{% endif %} />
&nbsp;
</td>
</tr>
{% endfor %}
{% else %}
<tr>
<td colspan="3">
{{ 'UsersManager_NoUsersExist'|translate }}
</td>
</tr>
{% endif %}
</tbody>
</table>
<div id="superUserAccessUpdated" style="vertical-align:top;"></div>
<div class="ui-confirm" id="superUserAccessConfirm">
<h2> </h2>
<input role="yes" type="button" value="{{ 'General_Yes'|translate }}"/>
<input role="no" type="button" value="{{ 'General_No'|translate }}"/>
</div>
{% endif %}
{% endblock %}

View file

@ -0,0 +1,181 @@
{% extends 'admin.twig' %}
{% block content %}
<h2 piwik-enriched-headline>{{ 'UsersManager_MenuUserSettings'|translate }}</h2>
<br/>
<div class="ui-confirm" id="confirmPasswordChange">
<h2>{{ 'UsersManager_ChangePasswordConfirm'|translate }}</h2>
<input role="yes" type="button" value="{{ 'General_Yes'|translate }}"/>
<input role="no" type="button" value="{{ 'General_No'|translate }}"/>
</div>
<table id='userSettingsTable' class="adminTable">
<tr>
<td><label for="username">{{ 'General_Username'|translate }} </label></td>
<td>
<input size="25" value="{{ userLogin }}" id="username" disabled="disabled"/>
<span class='form-description'>{{ 'UsersManager_YourUsernameCannotBeChanged'|translate }}</span>
</td>
</tr>
<tr>
<td><label for="alias">{{ 'UsersManager_Alias'|translate }} </label></td>
<td><input size="25" value="{{ userAlias }}" id="alias" /></td>
</tr>
<tr>
<td><label for="email">{{ 'UsersManager_Email'|translate }} </label></td>
<td><input size="25" value="{{ userEmail }}" id="email"/></td>
</tr>
<tr>
<td><div style="margin-top: 5px;margin-bottom: 10px;">{{ 'General_Language'|translate }}</div></td>
<td>
<fieldset style="margin-top: 5px;margin-bottom: 10px;">
<select name="language" id="language" onchange="if (this.value=='') window.open('?module=Proxy&amp;action=redirect&amp;url=http://piwik.org/translations/');">
<option title="" value="">{{ 'LanguagesManager_AboutPiwikTranslations'|translate }}</option>
{% for language in languages %}
<option value="{{ language.code }}" {% if language.code == currentLanguageCode %}selected="selected"{% endif %}
title="{{ language.name }} ({{ language.english_name }})">{{ language.name }}</option>
{% endfor %}
</select>
<br />
</fieldset>
</td>
</tr>
<tr>
<td>{{ 'UsersManager_ReportToLoadByDefault'|translate }}</td>
<td>
<fieldset>
<input id="defaultReportRadioAll" type="radio" value="MultiSites"
name="defaultReport"{% if defaultReport=='MultiSites' %} checked="checked"{% endif %} />
<label for="defaultReportRadioAll">{{ 'General_AllWebsitesDashboard'|translate }}</label><br/>
<input id="defaultReportSpecific" type="radio" value="1"
name="defaultReport"{% if defaultReport != 'MultiSites' %} checked="checked"{% endif %} />
<label for="defaultReportSpecific" style="padding-right:12px;">{{ 'General_DashboardForASpecificWebsite'|translate }}</label>
{% if defaultReport=='MultiSites' %}
{% set defaultReportIdSite=1 %}
{% else %}
{% set defaultReportIdSite=defaultReport %}
{% endif %}
<div piwik-siteselector
class="sites_autocomplete"
siteid="{{ defaultReportIdSite }}"
sitename="{{ defaultReportSiteName }}"
switch-site-on-select="false"
show-all-sites-item="false"
showselectedsite="true"
id="defaultReportSiteSelector"></div>
</fieldset>
</td>
</tr>
<tr>
<td>{{ 'UsersManager_ReportDateToLoadByDefault'|translate }}</td>
<td>
<fieldset>
{% for value,description in availableDefaultDates %}
<input id="defaultDate-{{ loop.index }}" type="radio"{% if defaultDate==value %} checked="checked"{% endif %} value="{{ value }}" name="defaultDate"/>
<label for="defaultDate-{{ loop.index }}">{{ description }}</label>
<br/>
{% endfor %}
</fieldset>
</td>
</tr>
{% if isValidHost is defined and isValidHost %}
<tr>
<td><label for="email">{{ 'General_ChangePassword'|translate }} </label></td>
<td><input size="25" value="" autocomplete="off" id="password" type="password"/>
<span class='form-description'>{{ 'UsersManager_IfYouWouldLikeToChangeThePasswordTypeANewOne'|translate }}</span>
<br/><br/><input size="25" value="" autocomplete="off" id="passwordBis" type="password"/>
<span class='form-description'> {{ 'UsersManager_TypeYourPasswordAgain'|translate }}</span>
</td>
</tr>
{% endif %}
</table>
{% if isValidHost is not defined or not isValidHost %}
<div id="injectedHostCannotChangePwd">
{% set injectedHostCannotChangePwd %}
{{ 'UsersManager_InjectedHostCannotChangePwd'|translate(invalidHost) }}
&nbsp;{% if not isSuperUser %}{{ 'UsersManager_EmailYourAdministrator'|translate(invalidHostMailLinkStart,'</a>')|raw }}{% endif %}
{% endset %}
{{ injectedHostCannotChangePwd|notification({'raw': true, 'context': 'error', 'placeat': '#injectedHostCannotChangePwd', 'noclear': true}) }}
</div>
<br/>
{% endif %}
{% import 'ajaxMacros.twig' as ajax %}
{{ ajax.errorDiv('ajaxErrorUserSettings') }}
{{ ajax.loadingDiv('ajaxLoadingUserSettings') }}
<input type="submit" value="{{ 'General_Save'|translate }}" id="userSettingsSubmit" class="submit"/>
<br/><br/>
<h2 id="excludeCookie">{{ 'UsersManager_ExcludeVisitsViaCookie'|translate }}</h2>
<p>
{% if ignoreCookieSet %}
{{ 'UsersManager_YourVisitsAreIgnoredOnDomain'|translate("<strong>", piwikHost, "</strong>")|raw }}
{% else %}
{{ 'UsersManager_YourVisitsAreNotIgnored'|translate("<strong>","</strong>")|raw }}
{% endif %}
</p>
<span style="margin-left:20px;">
<a href='{{ linkTo({'token_auth':token_auth, 'action':'setIgnoreCookie'}) }}#excludeCookie'>&rsaquo; {% if ignoreCookieSet %}{{ 'UsersManager_ClickHereToDeleteTheCookie'|translate }}
{% else %}{{'UsersManager_ClickHereToSetTheCookieOnDomain'|translate(piwikHost) }}{% endif %}
<br/>
</a></span>
<br/><br/>
{% if isSuperUser %}
<h2>{{ 'UsersManager_MenuAnonymousUserSettings'|translate }}</h2>
{% if anonymousSites|length == 0 %}
<h3 class='form-description'><strong>{{ 'UsersManager_NoteNoAnonymousUserAccessSettingsWontBeUsed2'|translate }}</strong></h3>
<br/>
{% else %}
<br/>
{{ ajax.errorDiv('ajaxErrorAnonymousUserSettings') }}
{{ ajax.loadingDiv('ajaxLoadingAnonymousUserSettings') }}
<table id='anonymousUserSettingsTable' class="adminTable" style='width:850px;'>
<tr>
<td style="width:400px;">{{ 'UsersManager_WhenUsersAreNotLoggedInAndVisitPiwikTheyShouldAccess'|translate }}</td>
<td>
<fieldset>
<input id="anonymousDefaultReport-login" type="radio" value="Login"
name="anonymousDefaultReport"{% if anonymousDefaultReport==loginModule %} checked="checked"{% endif %} />
<label for="anonymousDefaultReport-login">{{ 'UsersManager_TheLoginScreen'|translate }}</label><br/>
<input id="anonymousDefaultReport-multisites" {% if anonymousSites is empty %}disabled="disabled" {% endif %}type="radio" value="MultiSites"
name="anonymousDefaultReport"{% if anonymousDefaultReport=='MultiSites' %} checked="checked"{% endif %} />
<label for="anonymousDefaultReport-multisites">{{ 'General_AllWebsitesDashboard'|translate }}</label><br/>
<input id="anonymousDefaultReport-specific" {% if anonymousSites is empty %}disabled="disabled" {% endif %}type="radio" value="1"
name="anonymousDefaultReport"{% if anonymousDefaultReport>0 %} checked="checked"{% endif %} />
<label for="anonymousDefaultReport-specific">{{ 'General_DashboardForASpecificWebsite'|translate }}</label>
{% if anonymousSites is not empty %}
<select id="anonymousDefaultReportWebsite">
{% for info in anonymousSites %}
<option value="{{ info.idsite }}" {% if anonymousDefaultReport==info.idsite %} selected="selected"{% endif %}>{{ info.name|raw }}</option>
{% endfor %}
</select>
{% endif %}
</fieldset>
</td>
</tr>
<tr>
<td>{{ 'UsersManager_ForAnonymousUsersReportDateToLoadByDefault'|translate }}</td>
<td>
<fieldset>
{% for value,description in availableDefaultDates %}
<input id="anonymousDefaultDate-{{ loop.index }}" type="radio" {% if anonymousDefaultDate==value %}checked="checked" {% endif %}value="{{ value }}"
name="anonymousDefaultDate"/>
<label for="anonymousDefaultDate-{{ loop.index }}">{{ description }}</label>
<br/>
{% endfor %}
</fieldset>
</td>
</tr>
</table>
<input type="submit" value="{{ 'General_Save'|translate }}" id="anonymousUserSettingsSubmit" class="submit"/>
{% endif %}
{% endif %}
{% endblock %}