Bläddra i källkod

Ajout fonctionnalité connexion avec Persona (à améliorer sans doute)

Marien Fressinaud 13 år sedan
förälder
incheckning
3ff51a59ba

+ 2 - 4
app/App_FrontController.php

@@ -11,8 +11,8 @@ class App_FrontController extends FrontController {
 		$this->loadModels ();
 		
 		Session::init (); // lancement de la session doit se faire après chargement des modèles sinon bug (pourquoi ?)
-		$this->loadStylesAndScripts ();
 		$this->loadParamsView ();
+		$this->loadStylesAndScripts ();
 	}
 	
 	private function loadLibs () {
@@ -29,10 +29,8 @@ class App_FrontController extends FrontController {
 	
 	private function loadStylesAndScripts () {
 		View::prependStyle (Url::display ('/theme/base.css'));
+		View::appendScript ('https://login.persona.org/include.js');
 		View::appendScript (Url::display ('/scripts/jquery.js'));
-		View::appendScript (Url::display ('/scripts/smoothscroll.js'));
-		View::appendScript (Url::display ('/scripts/shortcut.js'));
-		View::appendScript (Url::display (array ('c' => 'javascript', 'a' => 'main')));
 	}
 	
 	private function loadParamsView () {

+ 13 - 0
app/controllers/configureController.php

@@ -1,6 +1,15 @@
 <?php
 
 class configureController extends ActionController {
+	public function firstAction () {
+		if (login_is_conf ($this->view->conf) && !is_logged ()) {
+			Error::error (
+				403,
+				array ('error' => array ('Vous n\'avez pas le droit d\'accéder à cette page'))
+			);
+		}
+	}
+
 	public function categorizeAction () {
 		$catDAO = new CategoryDAO ();
 		
@@ -69,12 +78,14 @@ class configureController extends ActionController {
 			$display = Request::param ('display_posts', 'no');
 			$sort = Request::param ('sort_order', 'low_to_high');
 			$old = Request::param ('old_entries', 3);
+			$mail = Request::param ('mail_login', false);
 		
 			$this->view->conf->_postsPerPage (intval ($nb));
 			$this->view->conf->_defaultView ($view);
 			$this->view->conf->_displayPosts ($display);
 			$this->view->conf->_sortOrder ($sort);
 			$this->view->conf->_oldEntries ($old);
+			$this->view->conf->_mailLogin ($mail);
 		
 			$values = array (
 				'posts_per_page' => $this->view->conf->postsPerPage (),
@@ -82,11 +93,13 @@ class configureController extends ActionController {
 				'display_posts' => $this->view->conf->displayPosts (),
 				'sort_order' => $this->view->conf->sortOrder (),
 				'old_entries' => $this->view->conf->oldEntries (),
+				'mail_login' => $this->view->conf->mailLogin (),
 			);
 		
 			$confDAO = new RSSConfigurationDAO ();
 			$confDAO->update ($values);
 			Session::_param ('conf', $this->view->conf);
+			Session::_param ('mail', $this->view->conf->mailLogin ());
 		}
 	}
 	

+ 7 - 0
app/controllers/entryController.php

@@ -2,6 +2,13 @@
 
 class entryController extends ActionController {
 	public function firstAction () {
+		if (login_is_conf ($this->view->conf) && !is_logged ()) {
+			Error::error (
+				403,
+				array ('error' => array ('Vous n\'avez pas le droit d\'accéder à cette page'))
+			);
+		}
+		
 		$ajax = Request::param ('ajax');
 		if ($ajax) {
 			$this->view->_useLayout (false);

+ 101 - 80
app/controllers/feedController.php

@@ -2,46 +2,53 @@
 
 class feedController extends ActionController {
 	public function addAction () {
-		if (Request::isPost ()) {
-			$url = Request::param ('url_rss');
+		if (login_is_conf ($this->view->conf) && !is_logged ()) {
+			Error::error (
+				403,
+				array ('error' => array ('Vous n\'avez pas le droit d\'accéder à cette page'))
+			);
+		} else {
+			if (Request::isPost ()) {
+				$url = Request::param ('url_rss');
 			
-			try {
-				$feed = new Feed ($url);
-				$feed->load ();
-				
-				$feedDAO = new FeedDAO ();
-				$values = array (
-					'id' => $feed->id (),
-					'url' => $feed->url (),
-					'category' => null,
-					'name' => $feed->name (),
-					'website' => $feed->website (),
-					'description' => $feed->description (),
-				);
-				$feedDAO->addFeed ($values);
+				try {
+					$feed = new Feed ($url);
+					$feed->load ();
 				
-				$entryDAO = new EntryDAO ();
-				$entries = $feed->entries ();
-				foreach ($entries as $entry) {
+					$feedDAO = new FeedDAO ();
 					$values = array (
-						'id' => $entry->id (),
-						'guid' => $entry->guid (),
-						'title' => $entry->title (),
-						'author' => $entry->author (),
-						'content' => $entry->content (),
-						'link' => $entry->link (),
-						'date' => $entry->date (true),
-						'is_read' => $entry->isRead (),
-						'is_favorite' => $entry->isFavorite (),
-						'id_feed' => $feed->id ()
+						'id' => $feed->id (),
+						'url' => $feed->url (),
+						'category' => null,
+						'name' => $feed->name (),
+						'website' => $feed->website (),
+						'description' => $feed->description (),
 					);
-					$entryDAO->addEntry ($values);
+					$feedDAO->addFeed ($values);
+				
+					$entryDAO = new EntryDAO ();
+					$entries = $feed->entries ();
+					foreach ($entries as $entry) {
+						$values = array (
+							'id' => $entry->id (),
+							'guid' => $entry->guid (),
+							'title' => $entry->title (),
+							'author' => $entry->author (),
+							'content' => $entry->content (),
+							'link' => $entry->link (),
+							'date' => $entry->date (true),
+							'is_read' => $entry->isRead (),
+							'is_favorite' => $entry->isFavorite (),
+							'id_feed' => $feed->id ()
+						);
+						$entryDAO->addEntry ($values);
+					}
+				} catch (Exception $e) {
+					// TODO ajouter une erreur : url non valide
 				}
-			} catch (Exception $e) {
-				// TODO ajouter une erreur : url non valide
-			}
 			
-			Request::forward (array (), true);
+				Request::forward (array (), true);
+			}
 		}
 	}
 	
@@ -78,64 +85,78 @@ class feedController extends ActionController {
 	}
 	
 	public function massiveImportAction () {
-		$entryDAO = new EntryDAO ();
-		$feedDAO = new FeedDAO ();
-		$catDAO = new CategoryDAO ();
+		if (login_is_conf ($this->view->conf) && !is_logged ()) {
+			Error::error (
+				403,
+				array ('error' => array ('Vous n\'avez pas le droit d\'accéder à cette page'))
+			);
+		} else {
+			$entryDAO = new EntryDAO ();
+			$feedDAO = new FeedDAO ();
+			$catDAO = new CategoryDAO ();
 		
-		$categories = Request::param ('categories', array ());
-		$feeds = Request::param ('feeds', array ());
+			$categories = Request::param ('categories', array ());
+			$feeds = Request::param ('feeds', array ());
 		
-		foreach ($categories as $cat) {
-			$values = array (
-				'id' => $cat->id (),
-				'name' => $cat->name (),
-				'color' => $cat->color ()
-			);
-			$catDAO->addCategory ($values);
-		}
+			foreach ($categories as $cat) {
+				$values = array (
+					'id' => $cat->id (),
+					'name' => $cat->name (),
+					'color' => $cat->color ()
+				);
+				$catDAO->addCategory ($values);
+			}
 		
-		foreach ($feeds as $feed) {
-			$feed->load ();
-			$entries = $feed->entries ();
+			foreach ($feeds as $feed) {
+				$feed->load ();
+				$entries = $feed->entries ();
 			
-			// Chargement du flux
-			foreach ($entries as $entry) {
+				// Chargement du flux
+				foreach ($entries as $entry) {
+					$values = array (
+						'id' => $entry->id (),
+						'guid' => $entry->guid (),
+						'title' => $entry->title (),
+						'author' => $entry->author (),
+						'content' => $entry->content (),
+						'link' => $entry->link (),
+						'date' => $entry->date (true),
+						'is_read' => $entry->isRead (),
+						'is_favorite' => $entry->isFavorite (),
+						'id_feed' => $feed->id ()
+					);
+					$entryDAO->addEntry ($values);
+				}
+			
+				// Enregistrement du flux
 				$values = array (
-					'id' => $entry->id (),
-					'guid' => $entry->guid (),
-					'title' => $entry->title (),
-					'author' => $entry->author (),
-					'content' => $entry->content (),
-					'link' => $entry->link (),
-					'date' => $entry->date (true),
-					'is_read' => $entry->isRead (),
-					'is_favorite' => $entry->isFavorite (),
-					'id_feed' => $feed->id ()
+					'id' => $feed->id (),
+					'url' => $feed->url (),
+					'category' => $feed->category (),
+					'name' => $feed->name (),
+					'website' => $feed->website (),
+					'description' => $feed->description (),
 				);
-				$entryDAO->addEntry ($values);
+				$feedDAO->addFeed ($values);
 			}
-			
-			// Enregistrement du flux
-			$values = array (
-				'id' => $feed->id (),
-				'url' => $feed->url (),
-				'category' => $feed->category (),
-				'name' => $feed->name (),
-				'website' => $feed->website (),
-				'description' => $feed->description (),
-			);
-			$feedDAO->addFeed ($values);
-		}
 	
-		Request::forward (array ('c' => 'configure', 'a' => 'importExport'));
+			Request::forward (array ('c' => 'configure', 'a' => 'importExport'));
+		}
 	}
 	
 	public function deleteAction () {
-		$id = Request::param ('id');
+		if (login_is_conf ($this->view->conf) && !is_logged ()) {
+			Error::error (
+				403,
+				array ('error' => array ('Vous n\'avez pas le droit d\'accéder à cette page'))
+			);
+		} else {
+			$id = Request::param ('id');
 		
-		$feedDAO = new FeedDAO ();
-		$feedDAO->deleteFeed ($id);
+			$feedDAO = new FeedDAO ();
+			$feedDAO->deleteFeed ($id);
 		
-		Request::forward (array ('c' => 'configure', 'a' => 'flux'));
+			Request::forward (array ('c' => 'configure', 'a' => 'flux'));
+		}
 	}
 }

+ 39 - 0
app/controllers/indexController.php

@@ -2,6 +2,10 @@
 
 class indexController extends ActionController {
 	public function indexAction () {
+		View::appendScript (Url::display ('/scripts/smoothscroll.js'));
+		View::appendScript (Url::display ('/scripts/shortcut.js'));
+		View::appendScript (Url::display (array ('c' => 'javascript', 'a' => 'main')));
+		
 		$entryDAO = new EntryDAO ();
 		$catDAO = new CategoryDAO ();
 		
@@ -42,4 +46,39 @@ class indexController extends ActionController {
 		
 		Request::forward (array (), true);
 	}
+	
+	public function loginAction () {
+			$this->view->_useLayout (false);
+		
+			$url = 'https://verifier.login.persona.org/verify';
+			$assert = Request::param ('assertion');
+			$params = 'assertion=' . $assert . '&audience=' .
+				  urlencode (Url::display () . ':80');
+			$ch = curl_init ();
+			$options = array (
+				CURLOPT_URL => $url,
+				CURLOPT_RETURNTRANSFER => TRUE,
+				CURLOPT_POST => 2,
+				CURLOPT_POSTFIELDS => $params
+			);
+			curl_setopt_array ($ch, $options);
+			$result = curl_exec ($ch);
+			curl_close ($ch);
+		
+			$res = json_decode ($result, true);
+			if ($res['status'] == 'okay' && $res['email'] == $this->view->conf->mailLogin ()) {
+				Session::_param ('mail', $res['email']);
+			} else {
+				$res = array ();
+				$res['status'] = 'failure';
+				$res['reason'] = 'L\'identifiant est invalide';
+			}
+		
+			$this->view->res = json_encode ($res);
+	}
+	
+	public function logoutAction () {
+		$this->view->_useLayout (false);
+		Session::_param ('mail');
+	}
 }

+ 14 - 2
app/layout/aside.phtml

@@ -19,16 +19,28 @@
 			</ul>
 			<?php } ?>
 		</li>
+		<?php if (!login_is_conf ($this->conf) || is_logged ()) { ?>
 		<li <?php echo Request::controllerName () == 'configure' ? 'class="active"' : ''; ?>>
 			<a href="<?php echo Url::display (array ('c' => 'configure', 'a' => 'flux')); ?>">Configurer</a>
 		</li>
+		<?php } ?>
 		<li>
 			<a href="<?php echo Url::display (array ('c' => 'feed', 'a' => 'actualize')); ?>">Mettre les flux à jour</a>
 		</li>
+		<?php if (login_is_conf ($this->conf)) { ?>
+		<li>
+			<?php if (!is_logged ()) { ?>
+			<a id="signin" href="#">Connexion</a>
+			<?php } else { ?>
+			<a id="signout" href="#">Déconnexion</a>
+			<?php } ?>
+		</li>
+		<?php } ?>
 	</ul>
 </div>
 
-<?php if (Request::controllerName () == 'configure') { ?>
+<?php if (Request::controllerName () == 'configure' &&
+         (!login_is_conf ($this->conf) || is_logged ())) { ?>
 <div class="aside">
 	<ul id="menu">
 		<li><h2>Configuration</h2></li>
@@ -39,7 +51,7 @@
 			<a href="<?php echo Url::display (array ('c' => 'configure', 'a' => 'categorize')); ?>">Catégories</a>
 		</li>
 		<li <?php echo Request::actionName () == 'display' ? 'class="active"' : ''; ?>>
-			<a href="<?php echo Url::display (array ('c' => 'configure', 'a' => 'display')); ?>">Affichage</a>
+			<a href="<?php echo Url::display (array ('c' => 'configure', 'a' => 'display')); ?>">Général et affichage</a>
 		</li>
 		<li <?php echo Request::actionName () == 'importExport' ? 'class="active"' : ''; ?>>
 			<a href="<?php echo Url::display (array ('c' => 'configure', 'a' => 'importExport')); ?>">Import / Export OPML</a>

+ 3 - 0
app/layout/layout.phtml

@@ -14,5 +14,8 @@
 		<?php $this->render (); ?>
 	</div>
 </div>
+
+<?php $this->partial ('persona'); ?>
+
 	</body>
 </html>

+ 73 - 0
app/layout/persona.phtml

@@ -0,0 +1,73 @@
+<?php if (login_is_conf ($this->conf)) { ?>
+<?php
+	$mail = Session::param ('mail', 'null');
+	if ($mail != 'null') {
+		$mail = '\'' . $mail . '\'';
+	}
+?>
+
+<script type="text/javascript">
+url = "<?php echo Url::display (); ?>"
+login_url = "<?php echo Url::display (array ('a' => 'login')); ?>";
+logout_url = "<?php echo Url::display (array ('a' => 'logout')); ?>";
+currentUser = <?php echo $mail; ?>;
+
+var signinLink = $('a#signin');
+if (signinLink) {
+	signinLink.click(function() {
+		navigator.id.request();
+		return false;
+	});
+};
+ 
+var signoutLink = $('a#signout');
+if (signoutLink) {
+	signoutLink.click(function() {
+		navigator.id.logout();
+		return false;
+	});
+};
+
+navigator.id.watch({
+	loggedInUser: currentUser,
+	onlogin: function(assertion) {
+		// A user has logged in! Here you need to:
+		// 1. Send the assertion to your backend for verification and to create a session.
+		// 2. Update your UI.
+		$.ajax ({
+			type: 'POST',
+			url: login_url,
+			data: {assertion: assertion},
+			success: function(res, status, xhr) {
+				var res_obj = jQuery.parseJSON(res);
+				
+				if (res_obj.status == 'failure') {
+					//alert (res_obj.reason);
+				} else if (res_obj.status == 'okay') {
+					location.href = url;
+				}
+			},
+			error: function(res, status, xhr) {
+				alert("login failure : " + res);
+			}
+		});
+	},
+	onlogout: function() {
+		// A user has logged out! Here you need to:
+		// Tear down the user's session by redirecting the user or making a call to your backend.
+		// Also, make sure loggedInUser will get set to null on the next page load.
+		// (That's a literal JavaScript null. Not false, 0, or undefined. null.)
+		$.ajax ({
+			type: 'POST',
+			url: logout_url,
+			success: function(res, status, xhr) {
+				location.href = url;
+			},
+			error: function(res, status, xhr) {
+				//alert("logout failure" + res);
+			}
+		});
+	}
+});
+</script>
+<?php } ?>

+ 16 - 0
app/models/RSSConfiguration.php

@@ -7,6 +7,7 @@ class RSSConfiguration extends Model {
 	private $sort_order;
 	private $old_entries;
 	private $shortcuts = array ();
+	private $mail_login = '';
 	
 	public function __construct () {
 		$confDAO = new RSSConfigurationDAO ();
@@ -16,6 +17,7 @@ class RSSConfiguration extends Model {
 		$this->_sortOrder ($confDAO->sort_order);
 		$this->_oldEntries ($confDAO->old_entries);
 		$this->_shortcuts ($confDAO->shortcuts);
+		$this->_mailLogin ($confDAO->mail_login);
 	}
 	
 	public function postsPerPage () {
@@ -36,6 +38,9 @@ class RSSConfiguration extends Model {
 	public function shortcuts () {
 		return $this->shortcuts;
 	}
+	public function mailLogin () {
+		return $this->mail_login;
+	}
 	
 	public function _postsPerPage ($value) {
 		if (is_int ($value)) {
@@ -77,6 +82,13 @@ class RSSConfiguration extends Model {
 			$this->shortcuts[$key] = $value;
 		}
 	}
+	public function _mailLogin ($value) {
+		if (filter_var ($value, FILTER_VALIDATE_EMAIL)) {
+			$this->mail_login = $value;
+		} elseif ($value == false) {
+			$this->mail_login = false;
+		}
+	}
 }
 
 class RSSConfigurationDAO extends Model_array {
@@ -94,6 +106,7 @@ class RSSConfigurationDAO extends Model_array {
 		'next_page' => 'right',
 		'prev_page' => 'left',
 	);
+	public $mail_login = '';
 
 	public function __construct () {
 		parent::__construct (PUBLIC_PATH . '/data/db/Configuration.array.php');
@@ -116,6 +129,9 @@ class RSSConfigurationDAO extends Model_array {
 		if (isset ($this->array['shortcuts'])) {
 			$this->shortcuts = $this->array['shortcuts'];
 		}
+		if (isset ($this->array['mail_login'])) {
+			$this->mail_login = $this->array['mail_login'];
+		}
 	}
 	
 	public function update ($values) {

+ 11 - 3
app/views/configure/display.phtml

@@ -1,12 +1,20 @@
 <form method="post" action="">
+	<h1>Configuration générale</h1>
+	
+	<label for="old_entries">Supprimer les articles au bout de (mois)</label>
+	<input type="number" id="old_entries" name="old_entries" value="<?php echo $this->conf->oldEntries (); ?>" />
+	
+	<label for="mail_login">Adresse mail de connexion (utilise <a href="https://persona.org/">Persona</a>)</label>
+	<p><noscript>nécessite que javascript soit activé</noscript></p>
+	<?php $mail = $this->conf->mailLogin (); ?>
+	<input type="email" id="mail_login" name="mail_login" value="<?php echo $mail ? $mail : ''; ?>" placeholder="Laissez vide pour désactiver" />
+	<br />
+	
 	<h1>Configuration de l'affichage</h1>
 	
 	<label for="posts_per_page">Nombre d'articles par page</label>
 	<input type="number" id="posts_per_page" name="posts_per_page" value="<?php echo $this->conf->postsPerPage (); ?>" />
 	
-	<label for="old_entries">Supprimer les articles au bout de (mois)</label>
-	<input type="number" id="old_entries" name="old_entries" value="<?php echo $this->conf->oldEntries (); ?>" />
-	
 	<label>Vue par défaut</label>
 	<div class="radio_group">
 		<input type="radio" name="default_view" id="radio_all" value="all"<?php echo $this->conf->defaultView () == 'all' ? ' checked="checked"' : ''; ?> />

+ 6 - 12
app/views/error/index.phtml

@@ -1,13 +1,7 @@
-<h1><?php echo Translate::t ('an error occured'); ?></h1>
+<div class="table">
+	<div class="nothing">
+		<h2><?php echo $this->code; ?></h2>
 
-<h2><?php echo $this->code; ?></h2>
-
-<?php if (!empty ($this->logs)) { ?>
-<ul>
-	<?php foreach ($this->logs as $log) { ?>
-	<li><?php echo $log; ?></li>
-	<?php } ?>
-</ul>
-<?php } ?>
-
-<p><a href="<?php echo Url::display (); ?>"><?php echo Translate::t ('go back home'); ?></a></p>
+		<p><a href="<?php echo Url::display (); ?>">Revenir à l'accueil</a></p>
+	</div>
+</div>

+ 4 - 1
app/views/index/index.phtml

@@ -1,7 +1,8 @@
 <?php $items = $this->entryPaginator->items (); ?>
 <?php if (!empty ($items)) { ?>
 <div id="top">
-	<a class="read_all" href="<?php echo Url::display (array ('c' => 'entry', 'a' => 'read', 'params' => array ('is_read' => 1))); ?>">Tout marquer comme lu</a><!--
+	<?php if (!login_is_conf ($this->conf) || is_logged ()) { ?>
+	<a class="read_all" href="<?php echo Url::display (array ('c' => 'entry', 'a' => 'read', 'params' => array ('is_read' => 1))); ?>">Tout marquer comme lu</a><?php } ?><!--
 	<?php if (Session::param ('mode', 'all') == 'not_read') { ?>
 	--><a class="print_all" href="<?php echo Url::display (array ('a' => 'changeMode', 'params' => array ('mode' => 'all'))); ?>">Tout afficher</a>
 	<?php } else { ?>
@@ -26,6 +27,7 @@
 		<div class="content"><?php echo $item->content (); ?></div>
 		
 		<div class="after">
+			<?php if (!login_is_conf ($this->conf) || is_logged ()) { ?>
 			<?php if (!$item->isRead ()) { ?>
 			<a class="read" href="<?php echo Url::display (array ('c' => 'entry', 'a' => 'read', 'params' => array ('id' => $item->id (), 'is_read' => 1))); ?>">J'ai fini de lire l'article</a><!--
 			<?php } else { ?>
@@ -37,6 +39,7 @@
 			<?php } else { ?>
 			--><a class="bookmark" href="<?php echo Url::display (array ('c' => 'entry', 'a' => 'bookmark', 'params' => array ('id' => $item->id (), 'is_favorite' => 0))); ?>">Retirer l'article de mes favoris</a>
 			<?php } ?>
+			<?php } ?>
 		</div>
 	</div>
 	<?php } ?>

+ 1 - 0
app/views/index/login.phtml

@@ -0,0 +1 @@
+<?php print_r ($this->res); ?>

+ 19 - 0
app/views/javascript/main.phtml

@@ -35,6 +35,10 @@ function slide (new_active, old_active) {
 }
 
 function mark_read (active) {
+	if (active[0] === undefined) {
+		return false;
+	}
+
 	url =  active.find ("a.read").attr ("href");
 	
 	$.ajax ({
@@ -56,6 +60,10 @@ function mark_read (active) {
 }
 
 function mark_favorite (active) {
+	if (active[0] === undefined) {
+		return false;
+	}
+
 	url =  active.find ("a.bookmark").attr ("href");
 	
 	$.ajax ({
@@ -87,6 +95,12 @@ $(document).ready (function () {
 		
 		if (old_active[0] != new_active[0]) {
 			slide (new_active, old_active);
+		} else {
+			old_active.removeClass ("active");
+	
+			if (hide_posts) {
+				old_active.children (".content").slideUp (200);
+			}
 		}
 	});
 	
@@ -109,6 +123,11 @@ $(document).ready (function () {
 		active = $(".post.flux.active");
 		mark_read (active);
 	});
+	shortcut.add("shift+<?php echo $s['mark_read']; ?>", function () {
+		// on marque tout comme lu
+		url = $("#top a.read_all").attr ("href");
+		redirect (url);
+	});
 	shortcut.add("<?php echo $s['mark_favorite']; ?>", function () {
 		// on marque comme favori ou non favori
 		active = $(".post.flux.active");

+ 10 - 0
lib/lib_rss.php

@@ -1,4 +1,14 @@
 <?php
+// vérifie qu'on est connecté
+function is_logged () {
+	return Session::param ('mail') != false;
+}
+
+// vérifie que le système d'authentification est configuré
+function login_is_conf ($conf) {
+	return $conf->mailLogin () != false;
+}
+
 // tiré de Shaarli de Seb Sauvage
 function small_hash ($txt) {
 	$t = rtrim (base64_encode (hash ('crc32', $txt, true)), '=');

+ 2 - 1
public/theme/base.css

@@ -55,7 +55,7 @@ form {
 		color: #3366cc !important;
 		border: 1px solid #3366cc !important;
 	}
-	input[type="text"], input[type="url"], input[type="number"], textarea {
+	input[type="text"], input[type="url"], input[type="email"], input[type="number"], textarea {
 		display: block;
 		width: 430px;
 		max-width: 95%;
@@ -196,6 +196,7 @@ form {
 		max-width: 800px;
 		line-height: 180%;
 		background: #fafafa;
+		vertical-align: top;
 	}
 		#top {
 			width: 100%;