Started to write the code to handle json API

This commit is contained in:
Phyks 2013-09-06 20:07:28 +02:00
parent 69fc984bab
commit bf1b454efa
11 changed files with 338 additions and 28 deletions

3
TODO
View File

@ -1,5 +1,5 @@
* Don't cache the username * Don't cache the username
* JSON output * JSON output => do the template
inc/Invoices.class.php : inc/Invoices.class.php :
======================== ========================
@ -9,6 +9,7 @@ inc/Invoices.class.php :
Manage paybacks : Manage paybacks :
================= =================
* TODO : Payback system (class should be ok) * TODO : Payback system (class should be ok)
* TODO : Global payback
To test : To test :
========= =========

View File

@ -228,7 +228,7 @@ class Storage {
foreach($this->fields as $field=>$type) { foreach($this->fields as $field=>$type) {
if(isset($this->$field)) { if(isset($this->$field)) {
if($fields[$field] == 'date') if($type == 'date')
$value = $value->format('Y-m-d H:i:s'); $value = $value->format('Y-m-d H:i:s');
$query->bindParam(':'.$field, $this->$field); $query->bindParam(':'.$field, $this->$field);

View File

@ -3,14 +3,15 @@ require_once('data/config.php');
require_once('Storage.class.php'); require_once('Storage.class.php');
class User extends Storage { class User extends Storage {
protected $id = 0, $login, $display_name, $password, $admin; protected $id = 0, $login, $display_name, $password, $admin, $json_token;
protected $TABLE_NAME = "Users"; protected $TABLE_NAME = "Users";
protected $fields = array( protected $fields = array(
'id'=>'key', 'id'=>'key',
'login'=>'string', 'login'=>'string',
'display_name'=>'string', 'display_name'=>'string',
'password'=>'password', 'password'=>'password',
'admin'=>'bool' 'admin'=>'bool',
'json_token'=>'string',
); );
public function __construct() { public function __construct() {
@ -35,6 +36,10 @@ class User extends Storage {
return $this->admin; return $this->admin;
} }
public function getJsonToken() {
return $this->json_token;
}
// Setters // Setters
// ======= // =======
public function setId($id) { public function setId($id) {
@ -57,6 +62,10 @@ class User extends Storage {
$this->admin = (bool) $admin; $this->admin = (bool) $admin;
} }
public function setJsonToken($token) {
$this->json_token = $token;
}
// Password functions // Password functions
// ================== // ==================
public function encrypt($text) { public function encrypt($text) {
@ -67,6 +76,12 @@ class User extends Storage {
return User::encrypt($password) == $this->password; return User::encrypt($password) == $this->password;
} }
// JSON token functions
// ====================
public function newJsonToken() {
$this->json_token = md5(uniqid(mt_rand(), true));
}
// Check if a user exists by login and load it // Check if a user exists by login and load it
// =========================================== // ===========================================
public function exists() { public function exists() {
@ -83,7 +98,7 @@ class User extends Storage {
// Session storage // Session storage
// =============== // ===============
public function sessionStore() { public function sessionStore() {
return serialize(array('id'=>$this->id, 'login'=>$this->login, 'display_name'=>$this->display_name, 'password'=>$this->password, 'admin'=>$this->admin)); return serialize(array('id'=>$this->id, 'login'=>$this->login, 'display_name'=>$this->display_name, 'password'=>$this->password, 'admin'=>$this->admin, 'json_token'=>$this->json_token));
} }
public function sessionRestore($data, $serialized = false) { public function sessionRestore($data, $serialized = false) {
@ -97,6 +112,7 @@ class User extends Storage {
$this->setDisplayName($user_data['display_name']); $this->setDisplayName($user_data['display_name']);
$this->setPassword($user_data['password']); $this->setPassword($user_data['password']);
$this->setAdmin($user_data['admin']); $this->setAdmin($user_data['admin']);
$this->setJsonToken($user_data['json_token']);
} }
// Check wether a user already exists or not // Check wether a user already exists or not
@ -118,6 +134,7 @@ class User extends Storage {
$this->login = htmlspecialchars($this->login); $this->login = htmlspecialchars($this->login);
$this->display_name = htmlspecialchars($this->display_name); $this->display_name = htmlspecialchars($this->display_name);
$this->admin = (int) $this->admin; $this->admin = (int) $this->admin;
$this->json_token = htmlspecialchars($this->json_token);
return $this; return $this;
} }

View File

@ -61,7 +61,7 @@
if ($handle = opendir($dir)) { if ($handle = opendir($dir)) {
while (false !== ($entry = readdir($handle))) { while (false !== ($entry = readdir($handle))) {
if ($entry != "." && $entry != ".." && is_dir($dir.$entry)) { if ($entry != "." && $entry != ".." && $entry != 'json' && is_dir($dir.$entry)) {
$return[] = array('value'=>$entry, 'option'=>str_replace(array('_en', '_fr'), array(' (English)', ' (French)'), $entry)); $return[] = array('value'=>$entry, 'option'=>str_replace(array('_en', '_fr'), array(' (English)', ' (French)'), $entry));
} }
} }

View File

@ -20,7 +20,14 @@
require_once('inc/functions.php'); require_once('inc/functions.php');
require_once('inc/Ban.inc.php'); require_once('inc/Ban.inc.php');
require_once('inc/CSRF.inc.php'); require_once('inc/CSRF.inc.php');
if(!empty($_GET['json'])) {
raintpl::$tpl_dir = 'tpl/json/';
$get_redir = 'json=1';
}
else {
raintpl::$tpl_dir = TEMPLATE_DIR; raintpl::$tpl_dir = TEMPLATE_DIR;
$get_redir = '';
}
raintpl::$cache_dir = 'tmp/'; raintpl::$cache_dir = 'tmp/';
// Define raintpl instance // Define raintpl instance
@ -57,18 +64,36 @@
} }
$tpl->assign('current_user', secureDisplay($current_user)); $tpl->assign('current_user', secureDisplay($current_user));
if(!empty($_GET['json_token'])) {
$current_user = new User();
if($current_user->load(array('json_token'=>$_GET['json_token'], true)) === false) {
header('location: index.php?do=connect'.$get_redir);
exit();
}
else {
if(!empty($get_redir))
$get_redir .= '&';
$get_redir .= 'json_token='.$_GET['json_token'];
}
}
else {
//If json token not available
// If not connected, redirect to connection page // If not connected, redirect to connection page
if($current_user === false && (empty($_GET['do']) OR $_GET['do'] != 'connect')) { if($current_user === false && (empty($_GET['do']) OR $_GET['do'] != 'connect')) {
header('location: index.php?do=connect'); header('location: index.php?do=connect&'.$get_redir);
exit(); exit();
} }
// If IP has changed, logout // If IP has changed, logout
if($current_user !== false && user_ip() != $_SESSION['ip']) { if($current_user !== false && user_ip() != $_SESSION['ip']) {
session_destroy(); session_destroy();
header('location: index.php?do=connect'); header('location: index.php?do=connect&'.$get_redir);
exit(); exit();
} }
}
// Initialize empty $_GET['do'] if required to avoid error // Initialize empty $_GET['do'] if required to avoid error
if(empty($_GET['do'])) { if(empty($_GET['do'])) {
@ -79,7 +104,7 @@
switch($_GET['do']) { switch($_GET['do']) {
case 'connect': case 'connect':
if($current_user !== false) { if($current_user !== false) {
header('location: index.php'); header('location: index.php?'.$get_redir);
exit(); exit();
} }
if(!empty($_POST['login']) && !empty($_POST['password']) && check_token(600, 'connection')) { if(!empty($_POST['login']) && !empty($_POST['password']) && check_token(600, 'connection')) {
@ -106,7 +131,7 @@
session_set_cookie_params($_SESSION['remember_me'], $cookie_dir, $_SERVER['HTTP_HOST']); session_set_cookie_params($_SESSION['remember_me'], $cookie_dir, $_SERVER['HTTP_HOST']);
session_regenerate_id(true); session_regenerate_id(true);
header('location: index.php'); header('location: index.php?'.$get_redir);
exit(); exit();
} }
else { else {
@ -124,7 +149,7 @@
case 'disconnect': case 'disconnect':
$current_user = false; $current_user = false;
session_destroy(); session_destroy();
header('location: index.php?do=connect'); header('location: index.php?do=connect&'.$get_redir);
exit(); exit();
break; break;
@ -135,7 +160,7 @@
$current_user->setPassword($current_user->encrypt($_POST['password'])); $current_user->setPassword($current_user->encrypt($_POST['password']));
$current_user->save(); $current_user->save();
header('location: index.php'); header('location: index.php?'.$get_redir);
exit(); exit();
} }
else { else {
@ -147,6 +172,7 @@
} }
} }
$tpl->assign('view', 'password'); $tpl->assign('view', 'password');
$tpl->assign('json_token', htmlspecialchars($current_user->getJsonToken()));
$tpl->assign('token', generate_token('password')); $tpl->assign('token', generate_token('password'));
$tpl->draw('edit_users'); $tpl->draw('edit_users');
break; break;
@ -154,7 +180,7 @@
case 'edit_users': case 'edit_users':
case 'add_user': case 'add_user':
if(!$current_user->getAdmin()) { if(!$current_user->getAdmin()) {
header('location: index.php'); header('location: index.php?'.$get_redir);
exit(); exit();
} }
@ -164,6 +190,9 @@
if(!empty($_POST['user_id'])) { if(!empty($_POST['user_id'])) {
$user->setId($_POST['user_id']); $user->setId($_POST['user_id']);
} }
else {
$user->newJsonToken();
}
$user->setLogin($_POST['login']); $user->setLogin($_POST['login']);
$user->setDisplayName($_POST['display_name']); $user->setDisplayName($_POST['display_name']);
if(!empty($_POST['password'])) { if(!empty($_POST['password'])) {
@ -177,7 +206,7 @@
// Clear the cache // Clear the cache
array_map("unlink", glob(raintpl::$cache_dir."*.rtpl.php")); array_map("unlink", glob(raintpl::$cache_dir."*.rtpl.php"));
header('location: index.php?do=edit_users'); header('location: index.php?do=edit_users&'.$get_redir);
exit(); exit();
} }
else { else {
@ -213,6 +242,24 @@
$tpl->draw('edit_users'); $tpl->draw('edit_users');
break; break;
case 'new_token':
if(!empty($_GET['user_id']) && $current_user->getAdmin()) {
$user_id = (int) $_GET['user_id'];
}
else {
$user_id = $current_user->getId();
}
$user = new User();
$user = $user->load(array('id'=>$user_id), true);
$user->newJsonToken();
$user->save();
$_SESSION['current_user'] = $user->sessionStore();
header('location: index.php'.$get_redir);
exit();
break;
case 'delete_user': case 'delete_user':
if($_GET['user_id'] != $current_user->getId()) { if($_GET['user_id'] != $current_user->getId()) {
$user = new User(); $user = new User();
@ -222,7 +269,7 @@
// Clear the cache // Clear the cache
array_map("unlink", glob(raintpl::$cache_dir."*.rtpl.php")); array_map("unlink", glob(raintpl::$cache_dir."*.rtpl.php"));
header('location: index.php?do=edit_users'); header('location: index.php?do=edit_users&'.$get_redir);
exit(); exit();
} }
break; break;
@ -234,7 +281,7 @@
// Clear the cache // Clear the cache
array_map("unlink", glob(raintpl::$cache_dir."*.rtpl.php")); array_map("unlink", glob(raintpl::$cache_dir."*.rtpl.php"));
header('location: index.php'); header('location: index.php?'.$get_redir);
exit(); exit();
} }
@ -286,7 +333,7 @@
// Clear the cache // Clear the cache
array_map("unlink", glob(raintpl::$cache_dir."*.rtpl.php")); array_map("unlink", glob(raintpl::$cache_dir."*.rtpl.php"));
header('location: index.php'); header('location: index.php?'.$get_redir);
exit(); exit();
} }
else { else {
@ -366,7 +413,7 @@
// Clear the cache // Clear the cache
array_map("unlink", glob(raintpl::$cache_dir."*.rtpl.php")); array_map("unlink", glob(raintpl::$cache_dir."*.rtpl.php"));
header('location: index.php'); header('location: index.php?'.$get_redir);
exit(); exit();
} }
} }
@ -405,7 +452,7 @@
// Clear the cache // Clear the cache
array_map("unlink", glob(raintpl::$cache_dir."*.rtpl.php")); array_map("unlink", glob(raintpl::$cache_dir."*.rtpl.php"));
header('location: index.php'); header('location: index.php?'.$get_redir);
exit(); exit();
} }
break; break;

View File

@ -54,6 +54,11 @@
</p> </p>
</form> </form>
{if condition="$user_id != -1"}
<h2>Personal token for this user</h2>
<p>The personal token for this user to be used with the API is : {$user_data->getJsonToken()}.<br/>If you think it might be compromised, you can <a href="index.php?do=new_token&user_id={$user_data->getId()}">generate a new one</a>.</p>
{/if}
{elseif condition="$view == 'password'"} {elseif condition="$view == 'password'"}
<h2>Edit your password</h2> <h2>Edit your password</h2>
<form method="post" action="index.php?do=password" id="edit_password_form"> <form method="post" action="index.php?do=password" id="edit_password_form">
@ -61,4 +66,8 @@
<p><label for="password_confirm" class="label-block">Confirm new password : </label><input type="password" id="password_confirm" name="password_confirm"/> <a href="" onclick="toggle_password('password_confirm'); return false;"><img src="img/toggleVisible.png" alt="Toggle visible"/></a></p> <p><label for="password_confirm" class="label-block">Confirm new password : </label><input type="password" id="password_confirm" name="password_confirm"/> <a href="" onclick="toggle_password('password_confirm'); return false;"><img src="img/toggleVisible.png" alt="Toggle visible"/></a></p>
<p class="center"><input type="submit" value="Update"/><input type="hidden" name="token" value="{$token}"</p> <p class="center"><input type="submit" value="Update"/><input type="hidden" name="token" value="{$token}"</p>
</form> </form>
<h2>Your personal token to use the API</h2>
<p>Your personal token to use the API is : {$json_token}.<br/>If you think it might be compromised, you can <a href="index.php?do=new_token">generate a new one</a>.</p>
{/if} {/if}
{include="footer"}

5
tpl/json/connection.html Normal file
View File

@ -0,0 +1,5 @@
{if condition="LANG=='fr'"}
Vous devez utiliser votre token unique d'identification pour l'utilisation de l'API JSON. Vous pouvez le trouver dans le menu "Modifier votre mot de passe".
{else}
You must use your unique id token to use JSON API. You can find it under the "Edit password" menu.
{/if}

62
tpl/json/edit_users.html Normal file
View File

@ -0,0 +1,62 @@
{if condition="$error != ''"}
<p class="error">{$error}</p>
{/if}
{if condition="$view == 'list_users'"}
<h2>List of users</h2>
<p class="center">You can also <a href="?do=add_user">add a user</a>.</p>
<table id="edit_users">
<tr>
<th>Id</th>
<th>Login</th>
<th>Display Name</th>
<th>Is admin ?</th>
<th>Edit</th>
<th>Delete</th>
</tr>
{loop="users"}
<tr>
<td>{$value->getId()}</td>
<td>{$value->getLogin()}</td>
<td>{$value->getDisplayName()}</td>
<td>{$value->getAdmin() ? "Yes" : "No"}</td>
<td><a href="index.php?do=edit_users&user_id={$value->getId()}">Edit</a></td>
<td>{if condition="$value->getId() != $current_user->getId()"}<a href="index.php?do=delete_user&user_id={$value->getId()}">Delete</a>{/if}</td>
</tr>
{/loop}
</table>
{elseif condition="$view == 'edit_user'"}
<h2>{$user_id != -1 ? 'Edit' : 'Add'} a user</h2>
<form method="post" action="index.php?do={$user_id != -1 ? 'edit_users' : 'add_user'}" id="edit_user_form">
<p>
<label for="login" class="label-block">Login : </label><input type="text" name="login" id="login" {if condition="$login_post != ''"} value="{$login_post}" {else} {$user_id != -1 ? 'value="'.$user_data->getLogin().'"' : ''} {/if}/>
</p>
<p>
<label for="display_name" class="label-block">Displayed name : </label><input type="text" name="display_name" id="display_name" {if condition="$display_name_post != ''"} value="{$display_name_post}" {else} {$user_id != -1 ? 'value="'.$user_data->getDisplayName().'"' : ''} {/if}/>
</p>
<p>
<label for="password" class="label-block">Password : </label><input type="password" name="password" id="password"/> <a href="" onclick="toggle_password('password'); return false;"><img src="img/toggleVisible.png" alt="Toggle visible"/></a>
{if condition="$user_id != -1"}
<br/><em>Note :</em> Leave blank this field if you don't want to edit password.
{/if}
</p>
<p id="edit_user_admin_rights">
Give admin rights to this user ?<br/>
<input type="radio" id="admin_yes" value="1" name="admin" {if condition="$admin_post == 1 || ($admin_post == -1 && $user_id != -1 && $user_data->getAdmin())"} checked{/if}/><label for="admin_yes">Yes</label><br/>
<input type="radio" id="admin_no" value="0" name="admin" {if condition="$admin_post == 0 || ($admin_post == -1 && ($user_id == -1 || !$user_data->getAdmin()))"} checked{/if}/><label for="admin_no">No</label>
</p>
<p class="center">
<input type="submit" value="{$user_id != -1 ? 'Edit' : 'Add'}"/>
{if condition="$user_id != -1"}<input type="hidden" name="user_id" value="{$user_id}"/>{/if}
<input type="hidden" name="token" value="{$token}"/>
</p>
</form>
{elseif condition="$view == 'password'"}
<h2>Edit your password</h2>
<form method="post" action="index.php?do=password" id="edit_password_form">
<p><label for="password" class="label-block">New password : </label><input type="password" id="password" name="password"/></p>
<p><label for="password_confirm" class="label-block">Confirm new password : </label><input type="password" id="password_confirm" name="password_confirm"/> <a href="" onclick="toggle_password('password_confirm'); return false;"><img src="img/toggleVisible.png" alt="Toggle visible"/></a></p>
<p class="center"><input type="submit" value="Update"/><input type="hidden" name="token" value="{$token}"</p>
</form>
{/if}

54
tpl/json/index.html Executable file
View File

@ -0,0 +1,54 @@
{if condition="$notice != ''"}
<div id="notice"><p>{$notice}</p></div>
{/if}
<div id="quick_summary">
<h2>Balance</h2>
<p class="center">Read <em>line</em> owes <em>case</em> {$currency} to <em>column</em>. You can click on links to confirm the payback.
<table id="balance_table">
<tr>
<th>Owes\To</th>
{loop="users"}
<th>{$value->getDisplayName()}</th>
{/loop}
</tr>
{loop="users"}
<tr>
<th>{$value->getDisplayName()}</th>
{loop="users"}
<td><a href="">{$value->getDisplayName()}</a></td>
{/loop}
</tr>
{/loop}
</table>
</div>
<div id="detailed_summary">
<h2>Detailed list of bills for last month</h2>
{if condition="count($invoices)>1"}
<table id="list_expenses">
<tr>
<th>Date</th>
<th>Paid by</th>
<th>Users in</th>
<th>Amount</th>
<th>What ?</th>
<th>Edit</th>
<th>Delete</th>
</tr>
{loop="invoices"}
<tr>
<td>{$value->getDate()}</td>
<td>{$value->getBuyer()->getDisplayName()}</td>
<td>{$value->getUsersIn()}</td>
<td>{$value->getAmount()}</td>
<td>{$value->getWhat()}</td>
<td><a href="index.php?do=edit_invoice&id={$value->getId()}">Edit</a></td>
<td><a href="index.php?do=delete_invoice&id={$value->getId()}">Delete</a></td>
</tr>
{/loop}
</table>
{else}
<p class="center">No bills added.</p>
{/if}
</div>

52
tpl/json/new_invoice.html Executable file
View File

@ -0,0 +1,52 @@
{if condition="$error != ''"}
<p class="error">{$error}</p>
{/if}
<h2>Add a bill</h2>
<form method="post" action="index.php?do=new_invoice" id="invoice_form">
<fieldset>
<legend>Expense</legend>
<p>
<label for="what">What ? </label>
</p>
<textarea name="what" id="what" rows="10">{$what_post}</textarea>
<p>
<label for="amount">Amount : </label>
<input type="text" name="amount" id="amount" {if condition="$amount_post != 0"} value="{$amount_post}" {/if} size="5"/> {$currency}
</p>
<p>
<label for="date_day">Date : </label>
<select name="date_day" id="date_day">
{loop="days"}
<option value="{$value}" {if condition="$value == $day_post"}selected{/if}>{$value}</option>
{/loop}
</select> /
<select name="date_month" id="date_month" onchange="set_days_month_year();">
{loop="months"}
<option value="{$value}" {if condition="$value == $month_post"}selected{/if}>{$value}</option>
{/loop}
</select> /
<select name="date_year" id="date_year" onchange="set_days_month_year();">
{loop="years"}
<option value="{$value}" {if condition="$value == $year_post"}selected{/if}>{$value}</option>
{/loop}
</select>
<select name="date_hour" id="date_hour">
<option value="0" {if condition="$hour_post == 0"}selected{/if}>AM</option>
<option value="1" {if condition="$hour_post == 1"}selected{/if}>PM</option>
</select>
</p>
</fieldset>
<fieldset>
<legend>Users in ?</legend>
{loop="users"}
<br/><input type="checkbox" name="users_in[]" value="{$value->getId()}" id="users_in_{$value->getId()}" {if condition="$current_user->getId() == $value->getId() || in_array($value->getId(), $users_in)"} checked {/if}/> <label for="users_in_{$value->getId()}">{$value->getDisplayName()}</label> and <input type="text" name="guest_user_{$value->getId()}" id="guest_user_{$value->getId()}" size="1" {if condition="in_array($value->getId(), $users_in)"} value="{$guests[$value->getId()]}" {else} value="0" {/if} onkeyup="guest_user_label({$value->getId()});"/><label for="guest_user_{$value->getId()}" id="guest_user_{$value->getId()}_label"> guest</label>.
{/loop}
</fieldset>
<p class="center">
<input type="submit" value="Add"/>
{if condition="$id != 0"}<input type="hidden" name="id" value="{$id}"/>{/if}
<input type="hidden" name="token" value="{$token}"/>
</p>
</form>

63
tpl/json/settings.html Normal file
View File

@ -0,0 +1,63 @@
{if condition="!$show_settings"}
<h2>Edit homepage notice</h2>
{if condition="$error"}<p class="error">{$error}</p>{/if}
<form method="post" id="notice_form" action="index.php?do=edit_notice">
<p>
<label for="textarea_notice">Homepage notice :</label><br/>
<textarea name="notice" rows="15" id="textarea_notice">{$notice}</textarea>
</p>
<p><em>Note :</em> You can use HTML formatting in this form.</p>
<p class="center">
<input type="submit" value="Submit"/>
<input type="hidden" name="token" value="{$token}"/>
</p>
</form>
{else}
<h2>Change settings of your Bouffe@Ulm installation</h2>
{if condition="$error"}<p class="error">{$error}</p>{/if}
<form method="post" action="index.php?do=settings" id="settings_form">
<fieldset>
<legend>Database</legend>
<p><em>Note :</em> Use these settings carefully. Your database won't be updated by the script as it was during install and you'll have to manually update it.</p>
<p><label for="mysql_host">MySQL host : </label><input type="text" name="mysql_host" id="mysql_host" value="{$mysql_host}"/></p>
<p><label for="mysql_login">MySQL login : </label><input type="text" name="mysql_login" id="mysql_login" value="{$mysql_login}"/></p>
<p>
<label for="mysql_password">MySQL password : </label><input type="password" name="mysql_password" id="mysql_password"/> <a href="" onclick="toggle_password('mysql_password'); return false;"><img src="img/toggleVisible.png" alt="Toggle visible"/></a><br/>
<em>Note :</em> Leave the above field blank if you don't want to change your password.
</p>
<p>
<label for="mysql_db">Name of the MySQL database to use : </label><input type="text" name="mysql_db" id="mysql_db" value="{$mysql_db}"/><br/>
<em>Note :</em> You <em>must</em> create this database first.
</p>
<p>
<label for="mysql_prefix">Prefix for the created tables : </label><input type="text" name="mysql_prefix" id="mysql_prefix" value="{$mysql_prefix}"/><br/>
<em>Note :</em> Leave the field blank to not use any.</p>
</fieldset>
<fieldset>
<legend>General options</legend>
<p><label for="instance_title">Title to display in pages : </label><input type="text" name="instance_title" id="instance_title" value="{$instance_title}"/></p>
<p>
<label for="base_url">Base URL : </label><input type="text" size="30" name="base_url" id="base_url" value="{$base_url}"/><br/>
<em>Note :</em> This is the base URL from which you access this page. You must keep the trailing "/" in the above address.
</p>
<p><label for="currency">Currency : </label><input type="text" name="currency" id="currency" size="3" value="{$currency}"/></p>
<p>
<label for="timezone">Timezone : </label><input type="text" name="timezone" id="timezone" value="{$timezone}"/><br/>
<em>For example :</em> Europe/Paris. See the doc for more info.
</p>
<p>
<label for="template">Template : </label>
<select name="template" id="template">
{loop="templates"}
<option value="{$value['value']}" {if condition="$value == $current_template"} selected="selected" {/if}>{$value['option']}</option>
{/loop}
</select
</p>
<p><label for="email_webmaster">Webmaster's email : </label><input type="text" name="email_webmaster" id="email_webmaster" value="{$email_webmaster}"/></p>
</fieldset>
<p class="center"><input type="submit" value="Update settings"><input type="hidden" name="token" value="{$token}"/></p>
</form>
{/if}