Marien Fressinaud 13 лет назад
Сommit
fb57be5a5a
47 измененных файлов с 12482 добавлено и 0 удалено
  1. 3 0
      .gitignore
  2. 1 0
      README
  3. 29 0
      app/App_FrontController.php
  4. 9 0
      app/configuration/application.ini
  5. 5 0
      app/configuration/routes.php
  6. 33 0
      app/controllers/configureController.php
  7. 58 0
      app/controllers/entryController.php
  8. 26 0
      app/controllers/errorController.php
  9. 89 0
      app/controllers/feedController.php
  10. 34 0
      app/controllers/indexController.php
  11. 35 0
      app/layout/aside.phtml
  12. 17 0
      app/layout/layout.phtml
  13. 178 0
      app/models/Entry.php
  14. 148 0
      app/models/Feed.php
  15. 76 0
      app/models/RSSConfiguration.php
  16. 3 0
      app/views/configure/categorize.phtml
  17. 28 0
      app/views/configure/display.phtml
  18. 3 0
      app/views/configure/flux.phtml
  19. 13 0
      app/views/error/index.phtml
  20. 34 0
      app/views/helpers/pagination.phtml
  21. 49 0
      app/views/index/index.phtml
  22. 42 0
      lib/ActionController.php
  23. 116 0
      lib/Cache.php
  24. 219 0
      lib/Configuration.php
  25. 144 0
      lib/Dispatcher.php
  26. 87 0
      lib/Error.php
  27. 104 0
      lib/FrontController.php
  28. 22 0
      lib/Helper.php
  29. 78 0
      lib/Log.php
  30. 12 0
      lib/Model.php
  31. 184 0
      lib/Paginator.php
  32. 185 0
      lib/Request.php
  33. 60 0
      lib/Response.php
  34. 244 0
      lib/Router.php
  35. 73 0
      lib/Session.php
  36. 68 0
      lib/Translate.php
  37. 102 0
      lib/Url.php
  38. 188 0
      lib/View.php
  39. 122 0
      lib/dao/Model_array.php
  40. 39 0
      lib/dao/Model_pdo.php
  41. 77 0
      lib/dao/Model_txt.php
  42. 94 0
      lib/exceptions/MinzException.php
  43. 67 0
      lib/lib_rss.php
  44. 8945 0
      lib/lib_simplepie.php
  45. 6 0
      public/.htaccess
  46. 38 0
      public/index.php
  47. 295 0
      public/theme/base.css

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+log/application.log
+public/data/db
+cache

+ 1 - 0
README

@@ -0,0 +1 @@
+Un simple agrégateur de flux rss

+ 29 - 0
app/App_FrontController.php

@@ -0,0 +1,29 @@
+<?php
+/** 
+ * MINZ - Copyright 2011 Marien Fressinaud
+ * Sous licence AGPL3 <http://www.gnu.org/licenses/>
+*/
+require ('FrontController.php');
+
+class App_FrontController extends FrontController {
+	public function init () {
+		$this->loadLibs ();
+		$this->loadModels ();
+		
+		Session::init ();
+		
+		View::prependStyle (Url::display ('/theme/base.css'));
+		View::_param ('conf', Session::param ('conf', new RSSConfiguration ()));
+	}
+	
+	private function loadLibs () {
+		require (LIB_PATH . '/lib_rss.php');
+		require (LIB_PATH . '/lib_simplepie.php');
+	}
+	
+	private function loadModels () {
+		include (APP_PATH . '/models/RSSConfiguration.php');
+		include (APP_PATH . '/models/Feed.php');
+		include (APP_PATH . '/models/Entry.php');
+	}
+}

+ 9 - 0
app/configuration/application.ini

@@ -0,0 +1,9 @@
+[general]
+environment = "development"
+use_url_rewriting = false
+sel_application = "flux rss lalala ~~~"
+
+base_url = "/~marien/rss/public"
+title = "Flux RSS"
+language = "fr"
+cache_enabled = false ; n'influe pas sur le cache de SimplePie

+ 5 - 0
app/configuration/routes.php

@@ -0,0 +1,5 @@
+<?php
+
+return array (
+
+);

+ 33 - 0
app/controllers/configureController.php

@@ -0,0 +1,33 @@
+<?php
+
+class configureController extends ActionController {
+	public function categorizeAction () {
+	
+	}
+	
+	public function fluxAction () {
+	
+	}
+	
+	public function displayAction () {
+		if (Request::isPost ()) {
+			$nb = Request::param ('posts_per_page', 10);
+			$view = Request::param ('default_view', 'all');
+			$display = Request::param ('display_posts', 'no');
+		
+			$this->view->conf->_postsPerPage (intval ($nb));
+			$this->view->conf->_defaultView ($view);
+			$this->view->conf->_displayPosts ($display);
+		
+			$values = array (
+				'posts_per_page' => $this->view->conf->postsPerPage (),
+				'default_view' => $this->view->conf->defaultView (),
+				'display_posts' => $this->view->conf->displayPosts ()
+			);
+		
+			$confDAO = new RSSConfigurationDAO ();
+			$confDAO->save ($values);
+			Session::_param ('conf', $this->view->conf);
+		}
+	}
+}

+ 58 - 0
app/controllers/entryController.php

@@ -0,0 +1,58 @@
+<?php
+
+class entryController extends ActionController {
+	public function readAction () {
+		$id = Request::param ('id');
+		$is_read = Request::param ('is_read');
+		
+		if ($is_read) {
+			$is_read = true;
+		} else {
+			$is_read = false;
+		}
+		
+		$entryDAO = new EntryDAO ();
+		if ($id == false) {
+			$entries = $entryDAO->listNotReadEntries ();
+		} else {
+			$entry = $entryDAO->searchById ($id);
+			$entries = $entry !== false ? array ($entry) : array ();
+		}
+		
+		foreach ($entries as $entry) {
+			$values = array (
+				'is_read' => $is_read,
+			);
+			
+			$entryDAO->updateEntry ($entry->id (), $values);
+		}
+		
+		Request::forward (array (), true);
+	}
+	
+	public function bookmarkAction () {
+		$id = Request::param ('id');
+		$is_fav = Request::param ('is_favorite');
+		
+		if ($is_fav) {
+			$is_fav = true;
+		} else {
+			$is_fav = false;
+		}
+		
+		$entryDAO = new EntryDAO ();
+		if ($id != false) {
+			$entry = $entryDAO->searchById ($id);
+			
+			if ($entry != false) {
+				$values = array (
+					'is_favorite' => $is_fav,
+				);
+			
+				$entryDAO->updateEntry ($entry->id (), $values);
+			}
+		}
+		
+		Request::forward (array (), true);
+	}
+}

+ 26 - 0
app/controllers/errorController.php

@@ -0,0 +1,26 @@
+<?php
+
+class ErrorController extends ActionController {
+	public function indexAction () {
+		View::prependTitle (Translate::t ('error') . ' - ');
+		
+		switch (Request::param ('code')) {
+		case 403:
+			$this->view->code = 'Error 403 - Forbidden';
+			break;
+		case 404:
+			$this->view->code = 'Error 404 - Not found';
+			break;
+		case 500:
+			$this->view->code = 'Error 500 - Internal Server Error';
+			break;
+		case 503:
+			$this->view->code = 'Error 503 - Service Unavailable';
+			break;
+		default:
+			$this->view->code = 'Error 404 - Not found';
+		}
+		
+		$this->view->logs = Request::param ('logs');
+	}
+}

+ 89 - 0
app/controllers/feedController.php

@@ -0,0 +1,89 @@
+<?php
+
+class feedController extends ActionController {
+	public function addAction () {
+		if (Request::isPost ()) {
+			$url = Request::param ('url_rss');
+			
+			try {
+				$feed = new Feed ($url);
+				$entries = $feed->loadEntries ();
+				$feed_entries = array ();
+				
+				if ($entries !== false) {
+					$entryDAO = new EntryDAO ();
+					
+					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 (),
+						);
+						$entryDAO->addEntry ($values);
+						
+						$feed_entries[] = $entry->id ();
+					}
+				}
+				
+				$feedDAO = new FeedDAO ();
+				$values = array (
+					'id' => $feed->id (),
+					'url' => $feed->url (),
+					'categories' => $feed->categories (),
+					'entries' => $feed_entries
+				);
+				$feedDAO->addFeed ($values);
+			} catch (Exception $e) {
+				// TODO ajouter une erreur : url non valide
+			}
+			
+			Request::forward (array (), true);
+		}
+	}
+	
+	public function actualizeAction () {
+		$feedDAO = new FeedDAO ();
+		$entryDAO = new EntryDAO ();
+		
+		$feeds = $feedDAO->listFeeds ();
+		
+		foreach ($feeds as $feed) {
+			$entries = $feed->loadEntries ();
+			$feed_entries = $feed->entries ();
+				
+			if ($entries !== false) {
+				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 (),
+					);
+					$entryDAO->addEntry ($values);
+					
+					if (!in_array ($entry->id (), $feed_entries)) {
+						$feed_entries[] = $entry->id ();
+					}
+				}
+			}
+			
+			$values = array (
+				'entries' => $feed_entries
+			);
+			$feedDAO->updateFeed ($values);
+		}
+		
+		Request::forward (array (), true);
+	}
+}

+ 34 - 0
app/controllers/indexController.php

@@ -0,0 +1,34 @@
+<?php
+
+class indexController extends ActionController {
+	public function indexAction () {
+		$entryDAO = new EntryDAO ();
+		
+		$mode = Session::param ('mode', $this->view->conf->defaultView ());
+		if ($mode == 'not_read') {
+			$entries = $entryDAO->listNotReadEntries ();
+		} elseif ($mode == 'all') {
+			$entries = $entryDAO->listEntries ();
+		}
+		
+		usort ($entries, 'sortEntriesByDate');
+		
+		//gestion pagination
+		$page = Request::param ('page', 1);
+		$this->view->entryPaginator = new Paginator ($entries);
+		$this->view->entryPaginator->_nbItemsPerPage ($this->view->conf->postsPerPage ());
+		$this->view->entryPaginator->_currentPage ($page);
+	}
+	
+	public function changeModeAction () {
+		$mode = Request::param ('mode');
+		
+		if ($mode == 'not_read') {
+			Session::_param ('mode', 'not_read');
+		} else {
+			Session::_param ('mode', 'all');
+		}
+		
+		Request::forward (array (), true);
+	}
+}

+ 35 - 0
app/layout/aside.phtml

@@ -0,0 +1,35 @@
+<div id="main_aside" class="aside">
+	<form id="add_rss" method="post" action="<?php echo Url::display (array ('c' => 'feed', 'a' => 'add')); ?>">
+		<input type="url" name="url_rss" placeholder="Ajouter un flux RSS" />
+		<input type="submit" value="+" />
+	</form>
+	
+	<ul id="menu">
+		<li <?php echo Request::controllerName () == 'index' ? 'class="active"' : ''; ?>>
+			<a href="<?php echo Url::display (array ()); ?>">Flux RSS</a>
+		</li>
+		<li <?php echo Request::controllerName () == 'configure' ? 'class="active"' : ''; ?>>
+			<a href="<?php echo Url::display (array ('c' => 'configure', 'a' => 'categorize')); ?>">Configurer</a>
+		</li>
+		<li>
+			<a href="<?php echo Url::display (array ('c' => 'feed', 'a' => 'actualize')); ?>">Mettre les flux à jour</a>
+		</li>
+	</ul>
+</div>
+
+<?php if (Request::controllerName () == 'configure') { ?>
+<div class="aside">
+	<ul id="menu">
+		<li><h2>Configuration</h2></li>
+		<li <?php echo Request::actionName () == 'categorize' ? 'class="active"' : ''; ?>>
+			<a href="<?php echo Url::display (array ('c' => 'configure', 'a' => 'categorize')); ?>">Catégories</a>
+		</li>
+		<li <?php echo Request::actionName () == 'flux' ? 'class="active"' : ''; ?>>
+			<a href="<?php echo Url::display (array ('c' => 'configure', 'a' => 'flux')); ?>">Flux RSS</a>
+		</li>
+		<li <?php echo Request::actionName () == 'display' ? 'class="active"' : ''; ?>>
+			<a href="<?php echo Url::display (array ('c' => 'configure', 'a' => 'display')); ?>">Affichage</a>
+		</li>
+	</ul>
+</div>
+<?php } ?>

+ 17 - 0
app/layout/layout.phtml

@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="fr">
+	<head>
+		<meta charset="utf-8">
+		<?php echo self::headTitle(); ?>
+		<?php echo self::headStyle(); ?>
+	</head>
+	<body>
+<div id="global">
+	<?php $this->partial ('aside'); ?>
+	
+	<div id="main">
+		<?php $this->render (); ?>
+	</div>
+</div>
+	</body>
+</html>

+ 178 - 0
app/models/Entry.php

@@ -0,0 +1,178 @@
+<?php
+
+class Entry extends Model {
+	private $guid;
+	private $title;
+	private $author;
+	private $content;
+	private $link;
+	private $date;
+	private $is_read;
+	private $is_favorite;
+	
+	public function __construct ($guid = '', $title = '', $author = '', $content = '',
+	                             $link = '', $pubdate = 0, $is_read = false, $is_favorite = false) {
+		$this->_guid ($guid);
+		$this->_title ($title);
+		$this->_author ($author);
+		$this->_content ($content);
+		$this->_link ($link);
+		$this->_date ($pubdate);
+		$this->_isRead ($is_read);
+		$this->_isFavorite ($is_favorite);
+	}
+	
+	public function id () {
+		return small_hash ($this->guid . Configuration::selApplication ());
+	}
+	public function guid () {
+		return $this->guid;
+	}
+	public function title () {
+		return $this->title;
+	}
+	public function author () {
+		return $this->author;
+	}
+	public function content () {
+		return $this->content;
+	}
+	public function link () {
+		return $this->link;
+	}
+	public function date ($raw = false) {
+		if ($raw) {
+			return $this->date;
+		} else {
+			return timestamptodate ($this->date);
+		}
+	}
+	public function isRead () {
+		return $this->is_read;
+	}
+	public function isFavorite () {
+		return $this->is_favorite;
+	}
+	
+	public function _guid ($value) {
+		$this->guid = $value;
+	}
+	public function _title ($value) {
+		$this->title = $value;
+	}
+	public function _author ($value) {
+		$this->author = $value;
+	}
+	public function _content ($value) {
+		$this->content = $value;
+	}
+	public function _link ($value) {
+		$this->link = $value;
+	}
+	public function _date ($value) {
+		$this->date = $value;
+	}
+	public function _isRead ($value) {
+		$this->is_read = $value;
+	}
+	public function _isFavorite ($value) {
+		$this->is_favorite = $value;
+	}
+}
+
+class EntryDAO extends Model_array {
+	public function __construct () {
+		parent::__construct (PUBLIC_PATH . '/data/db/Entries.array.php');
+	}
+	
+	public function addEntry ($values) {
+		$id = $values['id'];
+		unset ($values['id']);
+	
+		if (!isset ($this->array[$id])) {
+			$this->array[$id] = array ();
+		
+			foreach ($values as $key => $value) {
+				$this->array[$id][$key] = $value;
+			}
+		
+			$this->writeFile ($this->array);
+		} else {
+			return false;
+		}
+	}
+	
+	public function updateEntry ($id, $values) {
+		foreach ($values as $key => $value) {
+			$this->array[$id][$key] = $value;
+		}
+		
+		$this->writeFile($this->array);
+	}
+	
+	public function searchById ($id) {
+		$list = HelperEntry::daoToEntry ($this->array);
+		
+		if (isset ($list[$id])) {
+			return $list[$id];
+		} else {
+			return false;
+		}
+	}
+	
+	public function listEntries () {
+		$list = $this->array;
+		
+		if (!is_array ($list)) {
+			$list = array ();
+		}
+		
+		return HelperEntry::daoToEntry ($list);
+	}
+	
+	public function listNotReadEntries () {
+		$list = $this->array;
+		$list_not_read = array ();
+		
+		if (!is_array ($list)) {
+			$list = array ();
+		}
+		
+		foreach ($list as $key => $entry) {
+			if (!$entry['is_read']) {
+				$list_not_read[$key] = $entry;
+			}
+		}
+		
+		return HelperEntry::daoToEntry ($list_not_read);
+	}
+	
+	public function count () {
+		return count ($this->array);
+	}
+}
+
+class HelperEntry {
+	public static function daoToEntry ($listDAO) {
+		$list = array ();
+
+		if (!is_array ($listDAO)) {
+			$listDAO = array ($listDAO);
+		}
+
+		foreach ($listDAO as $key => $dao) {
+			$list[$key] = new Entry (
+				$dao['guid'],
+				$dao['title'],
+				$dao['author'],
+				$dao['content'],
+				$dao['link'],
+				$dao['date'],
+				$dao['is_read'],
+				$dao['is_favorite']
+			);
+		}
+
+		return $list;
+	}
+}

+ 148 - 0
app/models/Feed.php

@@ -0,0 +1,148 @@
+<?php
+
+class Feed extends Model {
+	private $url;
+	private $categories;
+	private $entries_list;
+	
+	public function __construct ($url = null) {
+		$this->_url ($url);
+		$this->_categories (array ());
+		$this->_entries (array ());
+	}
+	
+	public function id () {
+		return small_hash ($this->url . Configuration::selApplication ());
+	}
+	public function url () {
+		return $this->url;
+	}
+	public function categories () {
+		return $this->categories;
+	}
+	public function entries () {
+		return $this->entries_list;
+	}
+	
+	public function _url ($value) {
+		if (!is_null ($value) && filter_var ($value, FILTER_VALIDATE_URL)) {
+			$this->url = $value;
+		} else {
+			throw new Exception ();
+		}
+	}
+	public function _categories ($value) {
+		if (!is_array ($value)) {
+			$value = array ($value);
+		}
+		
+		$this->categories = $value;
+	}
+	public function _entries ($value) {
+		if (!is_array ($value)) {
+			$value = array ($value);
+		}
+		
+		$this->entries_list = $value;
+	}
+	
+	public function loadEntries () {
+		if (!is_null ($this->url)) {
+			$feed = new SimplePie ();
+			$feed->set_feed_url ($this->url);
+			$feed->set_cache_location (CACHE_PATH);
+			$feed->init ();
+			
+			$entries = array ();
+    			if ($feed->data) {
+    				foreach ($feed->get_items () as $item) {
+    					$title = $item->get_title ();
+    					$author = $item->get_author ();
+    					$content = $item->get_content ();
+    					$link = $item->get_permalink ();
+    					$date = strtotime ($item->get_date ());
+    				
+    					$entry = new Entry (
+    						$item->get_id (),
+    						!is_null ($title) ? $title : '',
+    						!is_null ($author) ? $author->name : '',
+    						!is_null ($content) ? $content : '',
+    						!is_null ($link) ? $link : '',
+    						$date ? $date : time ()
+    					);
+    					
+    					$entries[$entry->id ()] = $entry;
+        			}
+				
+				return $entries;
+			} else {
+				return false;
+			}
+		} else {
+			return false;
+		}
+	}
+}
+
+class FeedDAO extends Model_array {
+	public function __construct () {
+		parent::__construct (PUBLIC_PATH . '/data/db/Feeds.array.php');
+	}
+	
+	public function addFeed ($values) {
+		$id = $values['id'];
+		unset ($values['id']);
+	
+		if (!isset ($this->array[$id])) {
+			$this->array[$id] = array ();
+		
+			foreach ($values as $key => $value) {
+				$this->array[$id][$key] = $value;
+			}
+		
+			$this->writeFile ($this->array);
+		} else {
+			return false;
+		}
+	}
+	
+	public function updateFeed ($id, $values) {
+		foreach ($values as $key => $value) {
+			$this->array[$id][$key] = $value;
+		}
+		
+		$this->writeFile($this->array);
+	}
+	
+	public function listFeeds () {
+		$list = $this->array;
+		
+		if (!is_array ($list)) {
+			$list = array ();
+		}
+		
+		return HelperFeed::daoToFeed ($list);
+	}
+	
+	public function count () {
+		return count ($this->array);
+	}
+}
+
+class HelperFeed {
+	public static function daoToFeed ($listDAO) {
+		$list = array ();
+
+		if (!is_array ($listDAO)) {
+			$listDAO = array ($listDAO);
+		}
+
+		foreach ($listDAO as $key => $dao) {
+			$list[$key] = new Feed ($dao['url']);
+			$list[$key]->_categories ($dao['categories']);
+			$list[$key]->_entries ($dao['entries']);
+		}
+
+		return $list;
+	}
+}

+ 76 - 0
app/models/RSSConfiguration.php

@@ -0,0 +1,76 @@
+<?php
+
+class RSSConfiguration extends Model {
+	private $posts_per_page;
+	private $default_view;
+	private $display_posts;
+	
+	public function __construct () {
+		$confDAO = new RSSConfigurationDAO ();
+		$this->_postsPerPage ($confDAO->posts_per_page);
+		$this->_defaultView ($confDAO->default_view);
+		$this->_displayPosts ($confDAO->display_posts);
+	}
+	
+	public function postsPerPage () {
+		return $this->posts_per_page;
+	}
+	public function defaultView () {
+		return $this->default_view;
+	}
+	public function displayPosts () {
+		return $this->display_posts;
+	}
+	
+	public function _postsPerPage ($value) {
+		if (is_int ($value)) {
+			$this->posts_per_page = $value;
+		} else {
+			$this->posts_per_page = 10;
+		}
+	}
+	public function _defaultView ($value) {
+		if ($value == 'not_read') {
+			$this->default_view = 'not_read';
+		} else {
+			$this->default_view = 'all';
+		}
+	}
+	public function _displayPosts ($value) {
+		if ($value == 'yes') {
+			$this->display_posts = 'yes';
+		} else {
+			$this->display_posts = 'no';
+		}
+	}
+}
+
+class RSSConfigurationDAO extends Model_array {
+	public $posts_per_page = 10;
+	public $default_view = 'all';
+	public $display_posts = 'no';
+
+	public function __construct () {
+		parent::__construct (PUBLIC_PATH . '/data/db/Configuration.array.php');
+		
+		if (isset ($this->array['posts_per_page'])) {
+			$this->posts_per_page = $this->array['posts_per_page'];
+		}
+		if (isset ($this->array['default_view'])) {
+			$this->default_view = $this->array['default_view'];
+		}
+		if (isset ($this->array['display_posts'])) {
+			$this->display_posts = $this->array['display_posts'];
+		}
+	}
+	
+	public function save ($values) {
+		$this->array[0] = array ();
+	
+		foreach ($values as $key => $value) {
+			$this->array[0][$key] = $value;
+		}
+	
+		$this->writeFile($this->array);
+	}
+}

+ 3 - 0
app/views/configure/categorize.phtml

@@ -0,0 +1,3 @@
+<div class="post">
+	Fonctionnalité non implémentée (pour le moment)
+</div>

+ 28 - 0
app/views/configure/display.phtml

@@ -0,0 +1,28 @@
+<div class="post">
+	<form method="post" action="">
+		<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>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"' : ''; ?> />
+			<label for="radio_all">Tout afficher</label>
+			<br />
+			<input type="radio" name="default_view" id="radio_not_read" value="not_read"<?php echo $this->conf->defaultView () == 'not_read' ? ' checked="checked"' : ''; ?> />
+			<label for="radio_not_read">Afficher les non lus</label>
+		</div>
+		
+		<label>Afficher les articles dépliés par défaut</label>
+		<div class="radio_group">
+			<input type="radio" name="display_posts" id="radio_yes" value="yes"<?php echo $this->conf->displayPosts () ? ' checked="checked"' : ''; ?> />
+			<label for="radio_yes">Oui</label>
+			<br />
+			<input type="radio" name="display_posts" id="radio_no" value="no"<?php echo !$this->conf->displayPosts () ? ' checked="checked"' : ''; ?> />
+			<label for="radio_no">Non</label>
+		</div>
+		
+		<input type="submit" value="Valider" />
+	</form>
+</div>

+ 3 - 0
app/views/configure/flux.phtml

@@ -0,0 +1,3 @@
+<div class="post">
+	Fonctionnalité non implémentée (pour le moment)
+</div>

+ 13 - 0
app/views/error/index.phtml

@@ -0,0 +1,13 @@
+<h1><?php echo Translate::t ('an error occured'); ?></h1>
+
+<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>

+ 34 - 0
app/views/helpers/pagination.phtml

@@ -0,0 +1,34 @@
+<?php
+	$c = Request::controllerName ();
+	$a = Request::actionName ();
+	$params = Request::params ();
+?>
+
+<?php if ($this->nbPage > 1) { ?>
+<ul class="pagination">
+	<?php if ($this->currentPage > 1) { ?>
+	<?php $params[$getteur] = 1; ?>
+	<li class="pager-first"><a href="<?php echo Url::display (array ('c' => $c, 'a' => $a, 'params' => $params)); ?>">« Début</a></li>
+	<?php $params[$getteur] = $this->currentPage - 1; ?>
+	<li class="pager-previous"><a href="<?php echo Url::display (array ('c' => $c, 'a' => $a, 'params' => $params)); ?>">‹ Précédent</a></li>
+	<?php } ?>
+	
+	<?php for ($i = $this->currentPage - 2; $i <= $this->currentPage + 2; $i++) { ?>
+		<?php if($i > 0 && $i <= $this->nbPage) { ?>
+			<?php if ($i != $this->currentPage) { ?>
+			<?php $params[$getteur] = $i; ?>
+			<li class="pager-item"><a href="<?php echo Url::display (array ('c' => $c, 'a' => $a, 'params' => $params)); ?>"><?php echo $i; ?></a></li>
+			<?php } else { ?>
+			<li class="pager-current"><?php echo $i; ?></li>
+			<?php } ?>
+		<?php } ?>
+	<?php } ?>
+	
+	<?php if ($this->currentPage < $this->nbPage) { ?>
+	<?php $params[$getteur] = $this->currentPage + 1; ?>
+	<li class="pager-next"><a href="<?php echo Url::display (array ('c' => $c, 'a' => $a, 'params' => $params)); ?>">Suivant ›</a></li>
+	<?php $params[$getteur] = $this->nbPage; ?>
+	<li class="pager-last"><a href="<?php echo Url::display (array ('c' => $c, 'a' => $a, 'params' => $params)); ?>">Fin »</a></li>
+	<?php } ?>
+</ul>
+<?php } ?>

+ 49 - 0
app/views/index/index.phtml

@@ -0,0 +1,49 @@
+<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 (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 { ?>
+	--><a class="print_non_read" href="<?php echo Url::display (array ('a' => 'changeMode', 'params' => array ('mode' => 'not_read'))); ?>">Afficher les non lus</a>
+	<?php } ?>
+</div>
+
+<div id="stream">
+<?php $items = $this->entryPaginator->items (); ?>
+<?php if (!empty ($items)) { ?>
+	<?php $this->entryPaginator->render ('pagination.phtml', 'page'); ?>
+	
+	<?php foreach ($items as $item) { ?>
+	<div class="post flux<?php echo !$item->isRead () ? ' not_read' : ''; ?><?php echo $item->isFavorite () ? ' favorite' : ''; ?>">
+		<div class="before"><?php echo $item->author (); ?> a écrit le <?php echo $item->date (); ?>,</div>
+		
+		<h1><a target="_blank" href="<?php echo $item->link (); ?>"> <?php echo $item->title (); ?></a></h1>
+		<div class="content"><?php echo $item->content (); ?></div>
+		
+		<div class="after">
+			<?php if (!$item->isRead ()) { ?>
+			<a 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 { ?>
+			<a href="<?php echo Url::display (array ('c' => 'entry', 'a' => 'read', 'params' => array ('id' => $item->id (), 'is_read' => 0))); ?>">Marquer comme non lu</a><!--
+			<?php } ?>
+			
+			<?php if (!$item->isFavorite ()) { ?>
+			--><a href="<?php echo Url::display (array ('c' => 'entry', 'a' => 'bookmark', 'params' => array ('id' => $item->id (), 'is_favorite' => 1))); ?>">Ajouter l'article à mes favoris</a>
+			<?php } else { ?>
+			--><a 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 } ?>
+		</div>
+	</div>
+	<?php } ?>
+	
+	<?php $this->entryPaginator->render ('pagination.phtml', 'page'); ?>
+<?php } else { ?>
+	<div class="post flux">
+		<p>
+			Il n'y a aucun flux à afficher.
+		<?php if (Session::param ('mode', 'all') == 'not_read') { ?>
+		<a class="print_all" href="<?php echo Url::display (array ('a' => 'changeMode', 'params' => array ('mode' => 'all'))); ?>">Afficher tous les articles ?</a>
+		<?php }	?>
+		</p>
+	</div>
+<?php } ?>
+</div>

+ 42 - 0
lib/ActionController.php

@@ -0,0 +1,42 @@
+<?php
+/** 
+ * MINZ - Copyright 2011 Marien Fressinaud
+ * Sous licence AGPL3 <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * La classe ActionController représente le contrôleur de l'application
+ */
+class ActionController {
+	protected $router;
+	protected $view;
+
+	/**
+	 * Constructeur
+	 * @param $controller nom du controller
+	 * @param $action nom de l'action à lancer
+	 */
+	public function __construct ($router) {
+		$this->router = $router;
+		$this->view = new View ();
+		$this->view->attributeParams ();
+	}
+
+	/**
+	 * Getteur
+	 */
+	public function view () {
+		return $this->view;
+	}
+	
+	/**
+	 * Méthodes à redéfinir (ou non) par héritage
+	 * firstAction est la première méthode exécutée par le Dispatcher
+	 * lastAction est la dernière
+	 */
+	public function init () { }
+	public function firstAction () { }
+	public function lastAction () { }
+}
+
+

+ 116 - 0
lib/Cache.php

@@ -0,0 +1,116 @@
+<?php
+/** 
+ * MINZ - Copyright 2011 Marien Fressinaud
+ * Sous licence AGPL3 <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * La classe Cache permet de gérer facilement les pages en cache
+ */
+class Cache {
+	/**
+	 * $expire timestamp auquel expire le cache de $url
+	 */
+	private $expire = 0;
+	
+	/**
+	 * $file est le nom du fichier de cache
+	 */
+	private $file = '';
+	
+	/**
+	 * $enabled permet de déterminer si le cache est activé
+	 */
+	private static $enabled = true;
+	
+	/**
+	 * Constructeur
+	 */
+	public function __construct () {
+		$this->_fileName ();
+		$this->_expire ();
+	}
+	
+	/**
+	 * Setteurs
+	 */
+	public function _fileName () {
+		$file = md5 (Request::getURI ());
+		
+		$this->file = CACHE_PATH . '/'.$file;
+	}
+	
+	public function _expire () {
+		if ($this->exist ()) {
+			$this->expire = filemtime ($this->file)
+			              + Configuration::delayCache ();
+		}
+	}
+	
+	/**
+	 * Permet de savoir si le cache est activé
+	 * @return true si activé, false sinon
+	 */
+	public static function isEnabled () {
+		return Configuration::cacheEnabled () && self::$enabled;
+	}
+	
+	/**
+	 * Active / désactive le cache
+	 */
+	public static function switchOn () {
+		self::$enabled = true;
+	}
+	public static function switchOff () {
+		self::$enabled = false;
+	}
+	
+	/**
+	 * Détermine si le cache de $url a expiré ou non
+	 * @return true si il a expiré, false sinon
+	 */
+	public function expired () {
+		return time () > $this->expire;
+	}
+	
+	/**
+	 * Affiche le contenu du cache
+	 * @print le code html du cache
+	 */
+	public function render () {
+		if ($this->exist ()) {
+			include ($this->file);
+		}
+	}
+	
+	/**
+	 * Enregistre $html en cache
+	 * @param $html le html à mettre en cache
+	 */
+	public function cache ($html) {
+		file_put_contents ($this->file, $html);
+	}
+	
+	/**
+	 * Permet de savoir si le cache existence
+	 * @return true si il existe, false sinon
+	 */
+	public function exist () {
+		return file_exists ($this->file);
+	}
+	
+	/**
+	 * Nettoie le cache en supprimant tous les fichiers
+	 */
+	public static function clean () {
+		$files = opendir (CACHE_PATH);
+		
+		while ($fic = readdir ($files)) {
+			if ($fic != '.' && $fic != '..') {
+				unlink (CACHE_PATH.'/'.$fic);
+			}
+		}
+		
+		closedir ($files);
+	}
+}

+ 219 - 0
lib/Configuration.php

@@ -0,0 +1,219 @@
+<?php
+/** 
+ * MINZ - Copyright 2011 Marien Fressinaud
+ * Sous licence AGPL3 <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * La classe Configuration permet de gérer la configuration de l'application
+ */
+class Configuration {
+	const CONF_PATH_NAME = '/configuration/application.ini';
+	
+	/**
+	 * VERSION est la version actuelle de MINZ
+	 */
+	const VERSION = '1.1.0';
+	
+	/**
+	 * valeurs possibles pour l'"environment"
+	 * SILENT rend l'application muette (pas de log)
+	 * PRODUCTION est recommandée pour une appli en production
+	 *			(log les erreurs critiques)
+	 * DEVELOPMENT log toutes les erreurs
+	 */
+	const SILENT = 0;
+	const PRODUCTION = 1;
+	const DEVELOPMENT = 2;
+	
+	/**
+	 * définition des variables de configuration
+	 * $sel_application une chaîne de caractères aléatoires (obligatoire)
+	 * $environment gère le niveau d'affichage pour log et erreurs
+	 * $use_url_rewriting indique si on utilise l'url_rewriting
+	 * $base_url le chemin de base pour accéder à l'application
+	 * $title le nom de l'application
+	 * $language la langue par défaut de l'application
+	 * $cacheEnabled permet de savoir si le cache doit être activé
+	 * $delayCache la limite de cache
+	 * $db paramètres pour la base de données (tableau)
+	 *     - host le serveur de la base
+	 *     - user nom d'utilisateur
+	 *     - password mot de passe de l'utilisateur
+	 *     - base le nom de la base de données
+	 */
+	private static $sel_application = '';
+	private static $environment = Configuration::PRODUCTION;
+	private static $base_url = '';
+	private static $use_url_rewriting = false;
+	private static $title = '';
+	private static $language = 'en';
+	private static $cache_enabled = true;
+	private static $delay_cache = 3600;
+	
+	private static $db = array (
+		'host' => false,
+		'user' => false,
+		'password' => false,
+		'base' => false
+	);
+	
+	/*
+	 * Getteurs
+	 */
+	public static function selApplication () {
+		return self::$sel_application;
+	}
+	public static function environment () {
+		return self::$environment;
+	}
+	public static function baseUrl () {
+		return self::$base_url;
+	}
+	public static function useUrlRewriting () {
+		return self::$use_url_rewriting;
+	}
+	public static function title () {
+		return self::$title;
+	}
+	public static function language () {
+		return self::$language;
+	}
+	public static function cacheEnabled () {
+		return self::$cache_enabled;
+	}
+	public static function delayCache () {
+		return self::$delay_cache;
+	}
+	public static function dataBase () {
+		return self::$db;
+	}
+	
+	/**
+	 * Initialise les variables de configuration
+	 * @exception FileNotExistException si le CONF_PATH_NAME n'existe pas
+	 * @exception BadConfigurationException si CONF_PATH_NAME mal formaté
+	 */
+	public static function init () {
+		try {
+			self::parseFile ();
+		} catch (BadConfigurationException $e) {
+			throw $e;
+		} catch (FileNotExistException $e) {
+			throw $e;
+		}
+	}
+	
+	/**
+	 * Parse un fichier de configuration de type ".ini"
+	 * @exception FileNotExistException si le CONF_PATH_NAME n'existe pas
+	 * @exception BadConfigurationException si CONF_PATH_NAME mal formaté
+	 */
+	private static function parseFile () {
+		if (!file_exists (APP_PATH . self::CONF_PATH_NAME)) {
+			throw new FileNotExistException (
+				APP_PATH . self::CONF_PATH_NAME,
+				MinzException::ERROR
+			);
+		}
+		$ini_array = parse_ini_file (
+			APP_PATH . self::CONF_PATH_NAME,
+			true
+		);
+		
+		// [general] est obligatoire
+		if (!isset ($ini_array['general'])) {
+			throw new BadConfigurationException (
+				'[general]',
+				MinzException::ERROR
+			);
+		}
+		$general = $ini_array['general'];
+		
+		
+		// sel_application est obligatoire
+		if (!isset ($general['sel_application'])) {
+			throw new BadConfigurationException (
+				'sel_application',
+				MinzException::ERROR
+			);
+		}
+		self::$sel_application = $general['sel_application'];
+		
+		if (isset ($general['environment'])) {
+			switch ($general['environment']) {
+			case 'silent':
+				self::$environment = Configuration::SILENT;
+				break;
+			case 'development':
+				self::$environment = Configuration::DEVELOPMENT;
+				break;
+			case 'production':
+				self::$environment = Configuration::PRODUCTION;
+				break;
+			default:
+				throw new BadConfigurationException (
+					'environment',
+					MinzException::ERROR
+				);
+			}
+			
+		}
+		if (isset ($general['base_url'])) {
+			self::$base_url = $general['base_url'];
+		}
+		if (isset ($general['use_url_rewriting'])) {
+			self::$use_url_rewriting = $general['use_url_rewriting'];
+		}
+		
+		if (isset ($general['title'])) {
+			self::$title = $general['title'];
+		}
+		if (isset ($general['language'])) {
+			self::$language = $general['language'];
+		}
+		if (isset ($general['cache_enabled'])) {
+			self::$cache_enabled = $general['cache_enabled'];
+		}
+		if (isset ($general['delay_cache'])) {
+			self::$delay_cache = $general['delay_cache'];
+		}
+		
+		// Base de données
+		$db = false;
+		if (isset ($ini_array['db'])) {
+			$db = $ini_array['db'];
+		}
+		if ($db) {
+			if (!isset ($db['host'])) {
+				throw new BadConfigurationException (
+					'host',
+					MinzException::ERROR
+				);
+			}
+			if (!isset ($db['user'])) {
+				throw new BadConfigurationException (
+					'user',
+					MinzException::ERROR
+				);
+			}
+			if (!isset ($db['password'])) {
+				throw new BadConfigurationException (
+					'password',
+					MinzException::ERROR
+				);
+			}
+			if (!isset ($db['base'])) {
+				throw new BadConfigurationException (
+					'base',
+					MinzException::ERROR
+				);
+			}
+			
+			self::$db['host'] = $db['host'];
+			self::$db['user'] = $db['user'];
+			self::$db['password'] = $db['password'];
+			self::$db['base'] = $db['base'];
+		}
+	}
+}

+ 144 - 0
lib/Dispatcher.php

@@ -0,0 +1,144 @@
+<?php
+/** 
+ * MINZ - Copyright 2011 Marien Fressinaud
+ * Sous licence AGPL3 <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * Le Dispatcher s'occupe d'initialiser le Controller et d'executer l'action
+ * déterminée dans la Request
+ * C'est un singleton
+ */
+class Dispatcher {
+	const CONTROLLERS_PATH_NAME = '/controllers';
+	
+	/* singleton */
+	private static $instance = null;
+	
+	private $router;
+	private $controller;
+	
+	/**
+	 * Récupère l'instance du Dispatcher
+	 */
+	public static function getInstance ($router) {
+		if (is_null (self::$instance)) {
+			self::$instance = new Dispatcher ($router);
+		}
+		return self::$instance;
+	}
+
+	/**
+	 * Constructeur
+	 */
+	private function __construct ($router) {
+		$this->router = $router;
+	}
+	
+	/**
+	 * Lance le controller indiqué dans Request
+	 * Remplit le body de Response à partir de la Vue
+	 * @exception MinzException
+	 */
+	public function run () {
+		$cache = new Cache();
+		
+		if (Cache::isEnabled () && !$cache->expired ()) {
+			ob_start ();
+			$cache->render ();
+			$text = ob_get_clean();
+		} else {
+			while (Request::$reseted) {
+				Request::$reseted = false;
+				
+				try {
+					$this->createController (
+						Request::controllerName ()
+						. 'Controller'
+					);
+				
+					$this->controller->init ();
+					$this->controller->firstAction ();
+					$this->launchAction (
+						Request::actionName ()
+						. 'Action'
+					);
+					$this->controller->lastAction ();
+					
+					if (!Request::$reseted) {
+						ob_start ();
+						$this->controller->view ()->build ();
+						$text = ob_get_clean();
+					}
+				} catch (MinzException $e) {
+					throw $e;
+				}
+			}
+		}
+		
+		Response::setBody ($text);
+	}
+	
+	
+	/**
+	 * Instancie le Controller
+	 * @param $controller_name le nom du controller à instancier
+	 * @exception FileNotExistException le fichier correspondant au
+	 *          > controller n'existe pas
+	 * @exception ControllerNotExistException le controller n'existe pas
+	 * @exception ControllerNotActionControllerException controller n'est
+	 *          > pas une instance de ActionController
+	 */
+	private function createController ($controller_name) {
+		$filename = APP_PATH . self::CONTROLLERS_PATH_NAME . '/'
+		          . $controller_name . '.php';
+		
+		if (!file_exists ($filename)) {
+			throw new FileNotExistException (
+				$filename,
+				MinzException::ERROR
+			);
+		}
+		require_once ($filename);
+		
+		if (!class_exists ($controller_name)) {
+			throw new ControllerNotExistException (
+				$controller_name,
+				MinzException::ERROR
+			);
+		}
+		$this->controller = new $controller_name ($this->router);
+		
+		if (! ($this->controller instanceof ActionController)) {
+			throw new ControllerNotActionControllerException (
+				$controller_name,
+				MinzException::ERROR
+			);
+		}
+	}
+	
+	/**
+	 * Lance l'action sur le controller du dispatcher
+	 * @param $action_name le nom de l'action
+	 * @exception ActionException si on ne peut pas exécuter l'action sur
+	 *          > le controller
+	 */
+	private function launchAction ($action_name) {
+		if (!Request::$reseted) {
+			if (!is_callable (array (
+				$this->controller,
+				$action_name
+			))) {
+				throw new ActionException (
+					get_class ($this->controller),
+					$action_name,
+					MinzException::ERROR
+				);
+			}
+			call_user_func (array (
+				$this->controller,
+				$action_name
+			));
+		}
+	}
+}

+ 87 - 0
lib/Error.php

@@ -0,0 +1,87 @@
+<?php
+/** 
+ * MINZ - Copyright 2011 Marien Fressinaud
+ * Sous licence AGPL3 <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * La classe Error permet de lancer des erreurs HTTP
+ */
+class Error {
+	public function __construct () { }
+
+	/**
+	* Permet de lancer une erreur
+	* @param $code le type de l'erreur, par défaut 404 (page not found)
+	* @param $logs logs d'erreurs découpés de la forme
+	*      > $logs['error']
+	*      > $logs['warning']
+	*      > $logs['notice']
+	*/
+	public static function error ($code = 404, $logs = array ()) {
+		$logs = self::processLogs ($logs);
+		$error_filename = APP_PATH . '/controllers/errorController.php';
+		
+		if (file_exists ($error_filename)) {
+			$params = array (
+				'code' => $code,
+				'logs' => $logs
+			);
+			
+			Response::setHeader ($code);
+			Request::forward (array (
+				'c' => 'error',
+				'params' => $params
+			));
+		} else {
+			$text = '<h1>An error occured</h1>'."\n";
+			
+			if (!empty ($logs)) {
+				$text .= '<ul>'."\n";
+				foreach ($logs as $log) {
+					$text .= '<li>' . $log . '</li>'."\n";
+				}
+				$text .= '</ul>'."\n";
+			}
+			
+			Response::setHeader ($code);
+			Response::setBody ($text);
+			Response::send ();
+			exit ();
+		}
+	}
+	
+	/**
+	 * Permet de retourner les logs de façon à n'avoir que
+	 * ceux que l'on veut réellement
+	 * @param $logs les logs rangés par catégories (error, warning, notice)
+	 * @return la liste des logs, sans catégorie,
+	 *       > en fonction de l'environment
+	 */
+	private static function processLogs ($logs) {
+		$env = Configuration::environment ();
+		$logs_ok = array ();
+		$error = array ();
+		$warning = array ();
+		$notice = array ();
+		
+		if (isset ($logs['error'])) {
+			$error = $logs['error'];
+		}
+		if (isset ($logs['warning'])) {
+			$warning = $logs['warning'];
+		}
+		if (isset ($logs['notice'])) {
+			$notice = $logs['notice'];
+		}
+		
+		if ($env == Configuration::PRODUCTION) {
+			$logs_ok = $error;
+		}
+		if ($env == Configuration::DEVELOPMENT) {
+			$logs_ok = array_merge ($error, $warning, $notice);
+		}
+		
+		return $logs_ok;
+	}
+}

+ 104 - 0
lib/FrontController.php

@@ -0,0 +1,104 @@
+<?php
+# ***** BEGIN LICENSE BLOCK *****
+# MINZ - a free PHP Framework like Zend Framework
+# Copyright (C) 2011 Marien Fressinaud
+# 
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+# 
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+# 
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# ***** END LICENSE BLOCK *****
+
+/**
+ * La classe FrontController est le noyau du framework, elle lance l'application
+ * Elle est appelée en général dans le fichier index.php à la racine du serveur
+ */
+class FrontController {
+	protected $dispatcher;
+	protected $router;
+	
+	/**
+	 * Constructeur
+	 * Initialise le router et le dispatcher
+	 */
+	public function __construct () {
+		$this->loadLib ();
+		
+		try {
+			Configuration::init ();
+			Request::init ();
+			
+			$this->router = new Router ();
+			$this->router->init ();
+		} catch (RouteNotFoundException $e) {
+			Log::record ($e->getMessage (), Log::ERROR);
+			Error::error (
+				404,
+				array ('error' => array ($e->getMessage ()))
+			);
+		} catch (MinzException $e) {
+			Log::record ($e->getMessage (), Log::ERROR);
+			$this->killApp ();
+		}
+		
+		$this->dispatcher = Dispatcher::getInstance ($this->router);
+	}
+	
+	/**
+	 * Inclue les fichiers de la librairie
+	 */
+	private function loadLib () {
+		require ('ActionController.php');
+		require ('Cache.php');
+		require ('Configuration.php');
+		require ('Dispatcher.php');
+		require ('Error.php');
+		require ('Helper.php');
+		require ('Log.php');
+		require ('Model.php');
+		require ('Paginator.php');
+		require ('Request.php');
+		require ('Response.php');
+		require ('Router.php');
+		require ('Session.php');
+		require ('Translate.php');
+		require ('Url.php');
+		require ('View.php');
+		
+		require ('dao/Model_pdo.php');
+		require ('dao/Model_txt.php');
+		require ('dao/Model_array.php');
+		
+		require ('exceptions/MinzException.php');
+	}
+	
+	/**
+	 * Démarre l'application (lance le dispatcher et renvoie la réponse
+	 */
+	public function run () {
+		try {
+			$this->dispatcher->run ();
+			Response::send ();
+		} catch (MinzException $e) {
+			Log::record ($e->getMessage (), Log::ERROR);
+			$this->killApp ();
+		}
+	}
+	
+	/**
+	* Permet d'arrêter le programme en urgence
+	*/
+	private function killApp () {
+		exit ('### Application problem ###'."\n".
+		      'See logs files');
+	}
+}

+ 22 - 0
lib/Helper.php

@@ -0,0 +1,22 @@
+<?php
+/** 
+ * MINZ - Copyright 2011 Marien Fressinaud
+ * Sous licence AGPL3 <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * La classe Helper représente une aide pour des tâches récurrentes
+ */
+class Helper {
+	/**
+	 * Annule les effets des magic_quotes pour une variable donnée
+	 * @param $var variable à traiter (tableau ou simple variable)
+	 */
+	public static function stripslashes_r ($var) {
+		if (is_array ($var)){
+			return array_map (array ('Helper', 'stripslashes_r'), $var);
+		} else {
+			return stripslashes($var);
+		}
+	}
+}

+ 78 - 0
lib/Log.php

@@ -0,0 +1,78 @@
+<?php
+/** 
+ * MINZ - Copyright 2011 Marien Fressinaud
+ * Sous licence AGPL3 <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * La classe Log permet de logger des erreurs
+ */
+class Log {
+	/**
+	 * Les différents niveau de log
+	 * ERROR erreurs bloquantes de l'application
+	 * WARNING erreurs pouvant géner le bon fonctionnement, mais non bloquantes
+	 * NOTICE messages d'informations, affichés pour le déboggage
+	 */
+	const ERROR = 0;
+	const WARNING = 10;
+	const NOTICE = 20;
+	
+	/**
+	 * Enregistre un message dans un fichier de log spécifique
+	 * Message non loggué si
+	 * 	- environment = SILENT
+	 * 	- level = WARNING et environment = PRODUCTION
+	 * 	- level = NOTICE et environment = PRODUCTION
+	 * @param $information message d'erreur / information à enregistrer
+	 * @param $level niveau d'erreur
+	 * @param $file_name fichier de log, par défaut LOG_PATH/application.log
+	 */
+	public static function record ($information, $level, $file_name = null) {
+		$env = Configuration::environment ();
+		
+		if (! ($env == Configuration::SILENT
+		       || ($env == Configuration::PRODUCTION
+		       && ($level == Log::WARNING || $level == Log::NOTICE)))) {
+			if (is_null ($file_name)) {
+				$file_name = LOG_PATH . '/application.log';
+			}
+			
+			switch ($level) {
+			case Log::ERROR :
+				$level_label = 'error';
+				break;
+			case Log::WARNING :
+				$level_label = 'warning';
+				break;
+			case Log::NOTICE :
+				$level_label = 'notice';
+				break;
+			default :
+				$level_label = 'unknown';
+			}
+			
+			if ($env == Configuration::PRODUCTION) {
+				$file = fopen ($file_name, 'a');
+			} else {
+				$file = @fopen ($file_name, 'a');
+			}
+			
+			if ($file !== false) {
+				$log = '[' . date('r') . ']';
+				$log .= ' [' . $level_label . ']';
+				$log .= ' ' . $information . "\n";
+				fwrite ($file, $log); 
+				fclose ($file);
+			} else {
+				Error::error (
+					500,
+					array ('error' => array (
+						'Permission is denied for `'
+						. $file_name . '`')
+					)
+				);
+			}
+		}
+	}
+}

+ 12 - 0
lib/Model.php

@@ -0,0 +1,12 @@
+<?php
+/** 
+ * MINZ - Copyright 2011 Marien Fressinaud
+ * Sous licence AGPL3 <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * La classe Model représente un modèle de l'application (représentation MVC)
+ */
+class Model {
+
+}

+ 184 - 0
lib/Paginator.php

@@ -0,0 +1,184 @@
+<?php
+/** 
+ * MINZ - Copyright 2011 Marien Fressinaud
+ * Sous licence AGPL3 <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * La classe Paginator permet de gérer la pagination de l'application facilement
+ */
+class Paginator {
+	/**
+	 * $items tableau des éléments à afficher/gérer
+	 */
+	private $items = array ();
+
+	/**
+	 * $nbItemsPerPage le nombre d'éléments par page
+	 */
+	private $nbItemsPerPage = 10;
+
+	/**
+	 * $currentPage page actuelle à gérer
+	 */
+	private $currentPage = 1;
+
+	/**
+	 * $nbPage le nombre de pages de pagination
+	 */
+	private $nbPage = 1;
+
+	/**
+	 * Constructeur
+	 * @param $items les éléments à gérer
+	 */
+	public function __construct ($items) {
+		$this->_items ($items);
+		$this->_nbItemsPerPage ($this->nbItemsPerPage);
+		$this->_currentPage ($this->currentPage);
+	}
+
+	/**
+	 * Permet d'afficher la pagination
+	 * @param $view nom du fichier de vue situé dans /app/views/helpers/
+	 * @param $getteur variable de type $_GET[] permettant de retrouver la page
+	 */
+	public function render ($view, $getteur) {
+		$view = APP_PATH . '/views/helpers/'.$view;
+		
+		if (file_exists ($view)) {
+			include ($view);
+		}
+	}
+
+	/**
+	 * Permet de retrouver la page d'un élément donné
+	 * @param $item l'élément à retrouver
+	 * @return la page à laquelle se trouve l'élément (false si non trouvé)
+	 */
+	public function pageByItem ($item) {
+		$page = false;
+		$i = 0;
+
+		do {
+			if ($item == $this->items[$i]) {
+				$page = ceil (($i + 1) / $this->nbItemsPerPage);
+			}
+
+			$i++;
+		} while (!$page && $i < count ($this->items));
+
+		return $page;
+	}
+
+	/**
+	 * Permet de retrouver la position d'un élément donné (à partir de 0)
+	 * @param $item l'élément à retrouver
+	 * @return la position à laquelle se trouve l'élément (false si non trouvé)
+	 */
+	public function positionByItem ($item) {
+		$find = false;
+		$i = 0;
+
+		do {
+			if ($item == $this->items[$i]) {
+				$find = true;
+			} else {
+				$i++;
+			}
+		} while (!$find && $i < count ($this->items));
+
+		return $i;
+	}
+
+	/**
+	 * Permet de récupérer un item par sa position
+	 * @param $pos la position de l'élément
+	 * @return l'item situé à $pos (dernier item si $pos<0, 1er si $pos>=count($items))
+	 */
+	public function itemByPosition ($pos) {
+		if ($pos < 0) {
+			$pos = count ($this->items) - 1;
+		}
+		if ($pos >= count($this->items)) {
+			$pos = 0;
+		}
+
+		return $this->items[$pos];
+	}
+
+	/**
+	 * GETTEURS
+	 */
+	/**
+	 * @param $all si à true, retourne tous les éléments sans prendre en compte la pagination
+	 */
+	public function items ($all = false) {
+		$array = array ();
+		$nbItems = count ($this->items);
+
+		if ($nbItems <= $this->nbItemsPerPage || $all) {
+			$array = $this->items;
+		} else {
+			$begin = ($this->currentPage - 1) * $this->nbItemsPerPage;
+			$counter = 0;
+			$i = 0;
+			
+			foreach ($this->items as $key => $item) {
+				if ($i >= $begin) {
+					$array[$key] = $item;
+					$counter++;
+				}
+				if ($counter >= $this->nbItemsPerPage) {
+					break;
+				}
+				$i++;
+			}
+		}
+
+		return $array;
+	}
+	public function nbItemsPerPage  () {
+		return $this->nbItemsPerPage;
+	}
+	public function currentPage () {
+		return $this->currentPage;
+	}
+	public function nbPage () {
+		return $this->nbPage;
+	}
+
+	/**
+	 * SETTEURS
+	 */
+	public function _items ($items) {
+		if (is_array ($items)) {
+			$this->items = $items;
+		}
+		
+		$this->_nbPage ();
+	}
+	public function _nbItemsPerPage ($nbItemsPerPage) {
+		if ($nbItemsPerPage > count ($this->items)) {
+			$nbItemsPerPage = count ($this->items);
+		}
+		if ($nbItemsPerPage < 0) {
+			$nbItemsPerPage = 0;
+		}
+
+		$this->nbItemsPerPage = $nbItemsPerPage;
+		$this->_nbPage ();
+	}
+	public function _currentPage ($page) {
+		if($page < 1 || ($page > $this->nbPage && $this->nbPage > 0)) {
+			throw new CurrentPagePaginationException ($page);
+		}
+
+		$this->currentPage = $page;
+	}
+	private function _nbPage () {
+		if ($this->nbItemsPerPage > 0) {
+			$this->nbPage = ceil (count ($this->items) / $this->nbItemsPerPage);
+		}
+	}
+}

+ 185 - 0
lib/Request.php

@@ -0,0 +1,185 @@
+<?php
+/** 
+ * MINZ - Copyright 2011 Marien Fressinaud
+ * Sous licence AGPL3 <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * Request représente la requête http
+ */
+class Request {
+	private static $controller_name = '';
+	private static $action_name = '';
+	private static $params = array ();
+	
+	private static $default_controller_name = 'index';
+	private static $default_action_name = 'index';
+	
+	public static $reseted = true;
+	
+	/**
+	 * Getteurs
+	 */
+	public static function controllerName () {
+		return self::$controller_name;
+	}
+	public static function actionName () {
+		return self::$action_name;
+	}
+	public static function params () {
+		return self::$params;
+	}
+	public static function param ($key, $default = false) {
+		if (isset (self::$params[$key])) {
+			return self::$params[$key];
+		} else {
+			return $default;
+		}
+	}
+	public static function defaultControllerName () {
+		return self::$default_controller_name;
+	}
+	public static function defaultActionName () {
+		return self::$default_action_name;
+	}
+	
+	/**
+	 * Setteurs
+	 */
+	public static function _controllerName ($controller_name) {
+		self::$controller_name = $controller_name;
+	}
+	public static function _actionName ($action_name) {
+		self::$action_name = $action_name;
+	}
+	public static function _params ($params) {
+		if (!is_array($params)) {
+			$params = array ($params);
+		}
+		
+		self::$params = $params;
+	}
+	public static function _param ($key, $value) {
+		self::$params[$key] = $value;
+	}
+	
+	/**
+	 * Initialise la Request
+	 */
+	public static function init () {
+		self::magicQuotesOff ();
+	}
+	
+	/**
+	 * Retourn le nom de domaine du site
+	 */
+	public static function getDomainName () {
+		return $_SERVER['HTTP_HOST'];
+	}
+	
+	/**
+	 * Détermine la base de l'url
+	 * @return la base de l'url
+	 */
+	public static function getBaseUrl () {
+		return Configuration::baseUrl ();
+	}
+	
+	/**
+	 * Récupère l'URI de la requête
+	 * @return l'URI
+	 */
+	public static function getURI () {
+		if (isset ($_SERVER['REQUEST_URI'])) {
+			$base_url = self::getBaseUrl ();
+			$uri = $_SERVER['REQUEST_URI'];
+			
+			$len_base_url = strlen ($base_url);
+			$real_uri = substr ($uri, $len_base_url); 
+		} else {
+			$real_uri = '';
+		}
+		
+		return $real_uri;
+	}
+	
+	/**
+	 * Relance une requête
+	 * @param $url l'url vers laquelle est relancée la requête
+	 * @param $redirect si vrai, force la redirection http
+	 *                > sinon, le dispatcher recharge en interne
+	 */
+	public static function forward ($url = array (), $redirect = false) {
+		$url = Url::checkUrl ($url);
+		
+		if ($redirect) {
+			header ('Location: ' . Url::display ($url));
+			exit ();
+		} else {
+			self::$reseted = true;
+		
+			self::_controllerName ($url['c']);
+			self::_actionName ($url['a']);
+			self::_params (array_merge (
+				self::$params,
+				$url['params']
+			));
+		}
+	}
+	
+	/**
+	 * Permet de récupérer une variable de type $_GET
+	 * @param $param nom de la variable
+	 * @param $default valeur par défaut à attribuer à la variable
+	 * @return $_GET[$param]
+	 *         $_GET si $param = false
+	 *         $default si $_GET[$param] n'existe pas
+	 */
+	public static function fetchGET ($param = false, $default = false) {
+		if ($param === false) {
+			return $_GET;
+		} elseif (isset ($_GET[$param])) {
+			return $_GET[$param];
+		} else {
+			return $default;
+		}
+	}
+	
+	/**
+	 * Permet de récupérer une variable de type $_POST
+	 * @param $param nom de la variable
+	 * @param $default valeur par défaut à attribuer à la variable
+	 * @return $_POST[$param]
+	 *         $_POST si $param = false
+	 *         $default si $_POST[$param] n'existe pas
+	 */
+	public static function fetchPOST ($param = false, $default = false) {
+		if ($param === false) {
+			return $_POST;
+		} elseif (isset ($_POST[$param])) {
+			return $_POST[$param];
+		} else {
+			return $default;
+		}
+	}
+	
+	/**
+	 * Méthode désactivant les magic_quotes pour les variables
+	 *   $_GET
+	 *   $_POST
+	 *   $_COOKIE
+	 */
+	private static function magicQuotesOff () {
+		if (get_magic_quotes_gpc ()) {
+			$_GET = Helper::stripslashes_r ($_GET);
+			$_POST = Helper::stripslashes_r ($_POST);
+			$_COOKIE = Helper::stripslashes_r ($_COOKIE);
+		}
+	}
+	
+	public static function isPost () {
+		return !empty ($_POST) || !empty ($_FILES);
+	}
+}
+
+

+ 60 - 0
lib/Response.php

@@ -0,0 +1,60 @@
+<?php
+/** 
+ * MINZ - Copyright 2011 Marien Fressinaud
+ * Sous licence AGPL3 <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * Response représente la requête http renvoyée à l'utilisateur
+ */
+class Response {
+	private static $header = 'HTTP/1.0 200 OK';
+	private static $body = '';
+	
+	/**
+	 * Mets à jour le body de la Response
+	 * @param $text le texte à incorporer dans le body
+	 */
+	public static function setBody ($text) {
+		self::$body = $text;
+	}
+	
+	/**
+	 * Mets à jour le header de la Response
+	 * @param $code le code HTTP, valeurs possibles
+	 *	- 200 (OK)
+	 *	- 403 (Forbidden)
+	 *	- 404 (Forbidden)
+	 *	- 500 (Forbidden) -> par défaut si $code erroné
+	 *	- 503 (Forbidden)
+	 */
+	public static function setHeader ($code) {
+		switch ($code) {
+		case 200 :
+			self::$header = 'HTTP/1.0 200 OK';
+			break;
+		case 403 :
+			self::$header = 'HTTP/1.0 403 Forbidden';
+			break;
+		case 404 :
+			self::$header = 'HTTP/1.0 404 Not Found';
+			break;
+		case 500 :
+			self::$header = 'HTTP/1.0 500 Internal Server Error';
+			break;
+		case 503 :
+			self::$header = 'HTTP/1.0 503 Service Unavailable';
+			break;
+		default :
+			self::$header = 'HTTP/1.0 500 Internal Server Error';
+		}
+	}
+
+	/**
+	 * Envoie la Response à l'utilisateur
+	 */
+	public static function send () {
+		header (self::$header);
+		echo self::$body;
+	}
+}

+ 244 - 0
lib/Router.php

@@ -0,0 +1,244 @@
+<?php
+/** 
+ * MINZ - Copyright 2011 Marien Fressinaud
+ * Sous licence AGPL3 <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * La classe Router gère le routage de l'application
+ * Les routes sont définies dans APP_PATH.'/configuration/routes.php'
+ */
+class Router {
+	const ROUTES_PATH_NAME = '/configuration/routes.php';
+
+	private $routes = array ();
+	
+	/**
+	 * Constructeur
+	 * @exception FileNotExistException si ROUTES_PATH_NAME n'existe pas
+	 *            et que l'on utilise l'url rewriting
+	 */
+	public function __construct () {
+		if (Configuration::useUrlRewriting ()) {
+			if (file_exists (APP_PATH . self::ROUTES_PATH_NAME)) {
+				$routes = include (
+					APP_PATH . self::ROUTES_PATH_NAME
+				);
+		
+				if (!is_array ($routes)) {
+					$routes = array ();
+				}
+				
+				$this->routes = array_map (
+					array ('Url', 'checkUrl'),
+					$routes
+				);
+			} else {
+				throw new FileNotExistException (
+					self::ROUTES_PATH_NAME,
+					MinzException::ERROR
+				);
+			}
+		}
+	}
+	
+	/**
+	 * Initialise le Router en déterminant le couple Controller / Action
+	 * Mets à jour la Request
+	 * @exception RouteNotFoundException si l'uri n'est pas présente dans
+	 *          > la table de routage
+	 */
+	public function init () {
+		$url = array ();
+		
+		if (Configuration::useUrlRewriting ()) {
+			try {
+				$url = $this->buildWithRewriting ();
+			} catch (RouteNotFoundException $e) {
+				throw $e;
+			}
+		} else {
+			$url = $this->buildWithoutRewriting ();
+		}
+		
+		$url['params'] = array_merge (
+			$url['params'],
+			Request::fetchPOST ()
+		);
+		
+		Request::forward ($url);
+	}
+	
+	/**
+	 * Retourne un tableau représentant l'url passée par la barre d'adresses
+	 * Ne se base PAS sur la table de routage
+	 * @return tableau représentant l'url
+	 */
+	public function buildWithoutRewriting () {
+		$url = array ();
+		
+		$url['c'] = Request::fetchGET (
+			'c',
+			Request::defaultControllerName ()
+		);
+		$url['a'] = Request::fetchGET (
+			'a',
+			Request::defaultActionName ()
+		);
+		$url['params'] = Request::fetchGET ();
+		
+		// post-traitement
+		unset ($url['params']['c']);
+		unset ($url['params']['a']);
+		
+		return $url;
+	}
+	
+	/**
+	 * Retourne un tableau représentant l'url passée par la barre d'adresses
+	 * Se base sur la table de routage
+	 * @return tableau représentant l'url
+	 * @exception RouteNotFoundException si l'uri n'est pas présente dans
+	 *          > la table de routage
+	 */
+	public function buildWithRewriting () {
+		$url = array ();
+		$uri = Request::getURI ();
+		$find = false;
+		
+		foreach ($this->routes as $route) {
+			$regex = '*^' . $route['route'] . '$*';
+			if (preg_match ($regex, $uri, $matches)) {
+				$url['c'] = $route['controller'];
+				$url['a'] = $route['action'];
+				$url['params'] = $this->getParams (
+					$route['params'],
+					$matches
+				);
+				$find = true;
+				break;
+			}
+		}
+		
+		if (!$find && $uri != '/') {
+			throw new RouteNotFoundException (
+				$uri,
+				MinzException::ERROR
+			);
+		}
+		
+		// post-traitement
+		$url = Url::checkUrl ($url);
+		
+		return $url;
+	}
+	
+	/**
+	 * Retourne l'uri d'une url en se basant sur la table de routage
+	 * @param l'url sous forme de tableau
+	 * @return l'uri formatée (string) selon une route trouvée
+	 */
+	public function printUriRewrited ($url) {
+		$route = $this->searchRoute ($url);
+		
+		if ($route !== false) {
+			return $this->replaceParams ($route, $url);
+		}
+		
+		return '';
+	}
+	
+	/**
+	 * Recherche la route correspondante à une url
+	 * @param l'url sous forme de tableau
+	 * @return la route telle que spécifiée dans la table de routage,
+	 *         false si pas trouvée
+	 */
+	public function searchRoute ($url) {
+		foreach ($this->routes as $route) {
+			if ($route['controller'] == $url['c']
+			 && $route['action'] == $url['a']) {
+				// calcule la différence des tableaux de params
+				$params = array_flip ($route['params']);
+				$difference_params = array_diff_key (
+					$params,
+					$url['params']
+				);
+				
+				// vérifie que pas de différence
+				// et le cas où $params est vide et pas $url['params']
+				if (empty ($difference_params)
+				&& (!empty ($params) || empty ($url['params']))) {
+					return $route;
+				}
+			}
+		}
+		
+		return false;
+	}
+	
+	/**
+	 * Récupère un tableau dont
+	 * 	- les clés sont définies dans $params_route
+	 *	- les valeurs sont situées dans $matches
+	 * Le tableau $matches est décalé de +1 par rapport à $params_route
+	 */
+	private function getParams($params_route, $matches) {
+		$params = array ();
+		
+		for ($i = 0; $i < count ($params_route); $i++) {
+			$param = $params_route[$i];
+			$params[$param] = $matches[$i + 1];
+		}
+	
+		return $params;
+	}
+	
+	/**
+	 * Remplace les éléments de la route par les valeurs contenues dans $url
+	 * TODO Fonction très sale ! À revoir (preg_replace ?)
+	 */
+	private function replaceParams ($route, $url) {
+		$uri = '';
+		$in_brackets = false;
+		$backslash = false;
+		$num_param = 0;
+		
+		// parcourt caractère par caractère
+	 	for ($i = 0; $i < strlen ($route['route']); $i++) {
+			// on détecte qu'on rentre dans des parenthèses
+			// on va devoir changer par la valeur d'un paramètre
+	 		if ($route['route'][$i] == '(' && !$backslash) {
+	 			$in_brackets = true;
+	 		}
+			// on sort des parenthèses
+			// ok, on change le paramètre maintenant
+	 		if ($route['route'][$i] == ')' && !$backslash) {
+	 			$in_brackets = false;
+	 			$param = $route['params'][$num_param];
+ 				$uri .= $url['params'][$param];
+ 				$num_param++;
+	 		}
+	 		
+	 		if (!$in_brackets
+	 		 && ($route['route'][$i] != '\\' || $backslash)
+	 		 && ($route['route'][$i] != '(' || $backslash)
+	 		 && ($route['route'][$i] != ')' || $backslash)
+	 		 && ($route['route'][$i] != '?' || $backslash)) {
+				// on est pas dans les parenthèses
+				// on recopie simplement le caractère
+ 				$uri .= $route['route'][$i];
+	 		}
+	 		
+	 		// on détecte un backslash, on n'en veut pas
+	 		// sauf si on en avait déjà un juste avant
+	 		if ($route['route'][$i] == '\\' && !$backslash) {
+	 			$backslash = true;
+	 		} else {
+	 			$backslash = false;
+	 		}
+	 	}
+	 	
+	 	return $uri;
+	 }
+}

+ 73 - 0
lib/Session.php

@@ -0,0 +1,73 @@
+<?php
+
+/**
+ * La classe Session gère la session utilisateur
+ * C'est un singleton
+ */
+class Session {
+	/**
+	 * $session stocke les variables de session
+	 */
+	private static $session = array ();
+	
+	/**
+	 * Initialise la session
+	 */
+	public static function init () {
+		// démarre la session
+		session_name (md5 (Configuration::selApplication ()));
+		session_start ();
+		
+		if (isset ($_SESSION)) {
+			self::$session = $_SESSION;
+		}
+	}
+	
+	
+	/**
+	 * Permet de récupérer une variable de session
+	 * @param $p le paramètre à récupérer
+	 * @return la valeur de la variable de session, false si n'existe pas
+	 */
+	public static function param ($p, $default = false) {
+		if (isset (self::$session[$p])) {
+			$return = self::$session[$p];
+		} else {
+			$return = $default;
+		}
+		
+		return $return;
+	}
+	
+	
+	/**
+	 * Permet de créer ou mettre à jour une variable de session
+	 * @param $p le paramètre à créer ou modifier
+	 * @param $v la valeur à attribuer, false pour supprimer
+	 */
+	public static function _param ($p, $v = false) {
+		if ($v === false) {
+			unset ($_SESSION[$p]);
+			unset (self::$session[$p]);
+		} else {
+			$_SESSION[$p] = $v;
+			self::$session[$p] = $v;
+		}
+	}
+	
+	
+	/**
+	 * Permet d'effacer une session
+	 * @param $force si à false, n'efface pas le paramètre de langue
+	 */
+	public static function unset_session ($force = false) {
+		$language = self::param ('language');
+		
+		session_unset ();
+		self::$session = array ();
+		
+		if (!$force) {
+			self::_param ('language', $language);
+		}
+	}
+}

+ 68 - 0
lib/Translate.php

@@ -0,0 +1,68 @@
+<?php
+/** 
+ * MINZ - Copyright 2011 Marien Fressinaud
+ * Sous licence AGPL3 <http://www.gnu.org/licenses/>
+ */
+
+/**
+ * La classe Translate se charge de la traduction
+ * Utilise les fichiers du répertoire /app/i18n/
+ */
+class Translate {
+	/**
+	 * $language est la langue à afficher
+	 */
+	private static $language;
+	
+	/**
+	 * $translates est le tableau de correspondance
+	 * 	$key => $traduction
+	 */
+	private static $translates = array ();
+	
+	/**
+	 * Inclus le fichier de langue qui va bien
+	 * l'enregistre dans $translates
+	 */
+	public static function init () {
+		$l = Configuration::language ();
+		self::$language = Session::param ('language', $l);
+		
+		$l_path = APP_PATH . '/i18n/' . self::$language . '.php';
+		
+		if (file_exists ($l_path)) {
+			self::$translates = include ($l_path);
+		}
+	}
+	
+	/**
+	 * Alias de init
+	 */
+	public static function reset () {
+		self::init ();
+	}
+	
+	/**
+	 * Traduit une clé en sa valeur du tableau $translates
+	 * @param $key la clé à traduire
+	 * @return la valeur correspondante à la clé
+	 *       > si non présente dans le tableau, on retourne la clé elle-même
+	 */ 
+	public static function t ($key) {
+		$translate = $key;
+		
+		if (isset (self::$translates[$key])) {
+			$translate = self::$translates[$key];
+		}
+		
+		return $translate;
+	}
+	
+	/**
+	 * Retourne la langue utilisée actuellement
+	 * @return la langue
+	 */
+	public static function language () {
+		return self::$language;
+	}
+}

+ 102 - 0
lib/Url.php

@@ -0,0 +1,102 @@
+<?php
+
+/**
+ * La classe Url permet de gérer les URL à travers MINZ
+ */
+class Url {
+	/**
+	 * Affiche une Url formatée selon que l'on utilise l'url_rewriting ou non
+	 * si oui, on cherche dans la table de routage la correspondance pour formater
+	 * @param $url l'url à formater définie comme un tableau :
+	 *                    $url['c'] = controller
+	 *                    $url['a'] = action
+	 *                    $url['params'] = tableau des paramètres supplémentaires
+	 *                    $url['protocol'] = protocole à utiliser (http par défaut)
+	 *             ou comme une chaîne de caractère
+	 * @return l'url formatée
+	 */
+	public static function display ($url = array ()) {
+		$url = self::checkUrl ($url);
+		
+		$url_string = '';
+		
+		if (is_array ($url) && isset ($url['protocol'])) {
+			$protocol = $url['protocol'];
+		} else {
+			$protocol = 'http';
+		}
+		$url_string .= $protocol . '://';
+		
+		$url_string .= Request::getDomainName ();
+		
+		$url_string .= Request::getBaseUrl ();
+		
+		if (is_array ($url)) {
+			$router = new Router ();
+			
+			if (Configuration::useUrlRewriting ()) {
+				$url_string .= $router->printUriRewrited ($url);
+			} else {
+				$url_string .= self::printUri ($url);
+			}
+		} else {
+			$url_string .= $url;
+		}
+		
+		return $url_string;
+	}
+	
+	/**
+	 * Construit l'URI d'une URL sans url rewriting
+	 * @param l'url sous forme de tableau
+	 * @return l'uri sous la forme ?key=value&key2=value2
+	 */
+	private static function printUri ($url) {
+		$uri = '';
+		$separator = '/?';
+		
+		if (isset ($url['c'])
+		 && $url['c'] != Request::defaultControllerName ()) {
+			$uri .= $separator . 'c=' . $url['c'];
+			$separator = '&';
+		}
+		
+		if (isset ($url['a'])
+		 && $url['a'] != Request::defaultActionName ()) {
+			$uri .= $separator . 'a=' . $url['a'];
+			$separator = '&';
+		}
+		
+		if (isset ($url['params'])) {
+			foreach ($url['params'] as $key => $param) {
+				$uri .= $separator . $key . '=' . $param;
+				$separator = '&';
+			}
+		}
+		
+		return $uri;
+	}
+	
+	/**
+	 * Vérifie que les éléments du tableau représentant une url soit ok
+	 * @param l'url sous forme de tableau (sinon renverra directement $url)
+	 * @return l'url vérifié
+	 */
+	public static function checkUrl ($url) {
+		$url_checked = $url;
+		
+		if (is_array ($url)) {
+			if (!isset ($url['c'])) {
+				$url_checked['c'] = Request::defaultControllerName ();
+			}
+			if (!isset ($url['a'])) {
+				$url_checked['a'] = Request::defaultActionName ();
+			}
+			if (!isset ($url['params'])) {
+				$url_checked['params'] = array ();
+			}
+		}
+		
+		return $url_checked;
+	}
+}

+ 188 - 0
lib/View.php

@@ -0,0 +1,188 @@
+<?php
+/** 
+ * MINZ - Copyright 2011 Marien Fressinaud
+ * Sous licence AGPL3 <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * La classe View représente la vue de l'application
+ */
+class View {
+	const VIEWS_PATH_NAME = '/views';
+	const LAYOUT_PATH_NAME = '/layout';
+	const LAYOUT_FILENAME = '/layout.phtml';
+	
+	private $view_filename = '';
+	private $use_layout = false;
+	
+	private static $title = '';
+	private static $styles = array ();
+	private static $scripts = array ();
+	
+	private static $params = array ();
+	
+	/**
+	 * Constructeur
+	 * Détermine si on utilise un layout ou non
+	 */
+	public function __construct () {
+		$this->view_filename = APP_PATH
+		                     . self::VIEWS_PATH_NAME . '/'
+		                     . Request::controllerName () . '/'
+		                     . Request::actionName () . '.phtml';
+		
+		if (file_exists (APP_PATH
+		               . self::LAYOUT_PATH_NAME
+		               . self::LAYOUT_FILENAME)) {
+			$this->use_layout = true;
+		}
+		
+		self::$title = Configuration::title ();
+	}
+	
+	/**
+	 * Construit la vue
+	 */
+	public function build () {
+		if ($this->use_layout) {
+			$this->buildLayout ();
+		} else {
+			$this->render ();
+		}
+	}
+	
+	/**
+	 * Construit le layout
+	 */
+	public function buildLayout () {
+		include (
+			APP_PATH
+			. self::LAYOUT_PATH_NAME
+			. self::LAYOUT_FILENAME
+		);
+	}
+	
+	/**
+	 * Affiche la Vue en elle-même
+	 */
+	public function render () {
+		if (file_exists ($this->view_filename)) {
+			include ($this->view_filename);
+		} else {
+			Log::record ('File doesn\'t exist : `'
+			            . $this->view_filename . '`',
+			            Log::WARNING);
+		}
+	}
+	
+	/**
+	 * Ajoute un élément du layout
+	 * @param $part l'élément partial à ajouter
+	 */
+	public function partial ($part) {
+		$fic_partial = APP_PATH
+		             . self::LAYOUT_PATH_NAME . '/'
+		             . $part . '.phtml';
+		
+		if (file_exists ($fic_partial)) {
+			include ($fic_partial);
+		} else {
+			Log::record ('File doesn\'t exist : `'
+			            . $fic_partial . '`',
+			            Log::WARNING);
+		}
+	}
+	
+	/**
+	 * Permet de choisir si on souhaite utiliser le layout
+	 * @param $use true si on souhaite utiliser le layout, false sinon
+	 */
+	public function _useLayout ($use) {
+		$this->use_layout = $use;
+	}
+	
+	/**
+	 * Gestion du titre
+	 */
+	public static function title () {
+		return self::$title;
+	}
+	public static function headTitle () {
+		return '<title>' . self::$title . '</title>' . "\n";
+	}
+	public static function _title ($title) {
+		self::$title = $title;
+	}
+	public static function prependTitle ($title) {
+		self::$title = $title . self::$title;
+	}
+	public static function appendTitle ($title) {
+		self::$title = self::$title . $title;
+	}
+	
+	/**
+	 * Gestion des feuilles de style
+	 */
+	public static function headStyle () {
+		$styles = '';
+
+		foreach(self::$styles as $style) {
+			$styles .= '<link rel="stylesheet" type="text/css"';
+			$styles .= ' media="' . $style['media'] . '"';
+			$styles .= ' href="' . $style['url'] . '" />' . "\n";
+		}
+
+		return $styles;
+	}
+	public static function prependStyle ($url, $media = 'all') {
+		array_unshift (self::$styles, array (
+			'url' => $url,
+			'media' => $media
+		));
+	}
+	public static function appendStyle ($url, $media = 'all') {
+		self::$styles[] = array (
+			'url' => $url,
+			'media' => $media
+		);
+	}
+	
+	/**
+	 * Gestion des scripts JS
+	 */
+	public static function headScript () {
+		$scripts = '';
+
+		foreach (self::$scripts as $script) {
+			$scripts .= '<script type="text/javascript"';
+			$scripts .= ' src="' . $script['url'] . '">';
+			$scripts .= '</script>' . "\n";
+		}
+
+		return $scripts;
+	}
+	public static function prependScript ($url) {
+		array_unshift(self::$scripts, array (
+			'url' => $url
+		));
+	}
+	public static function appendScript ($url) {
+		self::$scripts[] = array (
+			'url' => $url
+		);
+	}
+	
+	/**
+	 * Gestion des paramètres ajoutés à la vue
+	 */
+	public static function _param ($key, $value) {
+		self::$params[$key] = $value;
+	}
+	public function attributeParams () {
+		foreach (View::$params as $key => $value) {
+			$this->$key = $value;
+		}
+	}
+}
+
+

+ 122 - 0
lib/dao/Model_array.php

@@ -0,0 +1,122 @@
+<?php
+/** 
+ * MINZ - Copyright 2011 Marien Fressinaud
+ * Sous licence AGPL3 <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * La classe Model_array représente le modèle interragissant avec les fichiers de type texte gérant des tableaux php
+ */
+class Model_array extends Model_txt {
+	/**
+	 * $array Le tableau php contenu dans le fichier $nameFile
+	 */
+	protected $array = array ();
+	
+	/**
+	 * Ouvre le fichier indiqué, charge le tableau dans $array et le $nameFile
+	 * @param $nameFile le nom du fichier à ouvrir contenant un tableau
+	 * Remarque : $array sera obligatoirement un tableau
+	 */
+	public function __construct ($nameFile) {
+		parent::__construct ($nameFile);
+		
+		if (!$this->getLock ('read')) {
+			throw new PermissionDeniedException ($this->filename);
+		} else {
+			$this->array = include ($this->filename);
+			$this->releaseLock ();
+		
+			if (!is_array ($this->array)) {
+				$this->array = array ();
+			}
+		
+			$this->array = $this->decodeArray ($this->array);
+		}
+	}	
+	
+	/**
+	 * Écrit un tableau dans le fichier $nameFile
+	 * @param $array le tableau php à enregistrer
+	 **/
+	public function writeFile ($array) {
+		if (!$this->getLock ('write')) {
+			throw new PermissionDeniedException ($this->namefile);
+		} else {
+			$this->erase ();
+		
+			$this->writeLine ('<?php');
+			$this->writeLine ('return ', false);
+			$this->writeArray ($array);
+			$this->writeLine (';');
+		
+			$this->releaseLock ();
+		}
+	}
+	
+	private function writeArray ($array, $profondeur = 0) {
+		$tab = '';
+		for ($i = 0; $i < $profondeur; $i++) {
+			$tab .= "\t";
+		}
+		$this->writeLine ('array (');
+		
+		foreach ($array as $key => $value) {
+			if (is_int ($key)) {
+				$this->writeLine ($tab . "\t" . $key . ' => ', false);
+			} else {
+				$this->writeLine ($tab . "\t" . '\'' . $key . '\'' . ' => ', false);
+			}
+			
+			if (is_array ($value)) {
+				$this->writeArray ($value, $profondeur + 1);
+				$this->writeLine (',');
+			} else {
+				if (is_numeric ($value)) {
+					$this->writeLine ($value . ',');
+				} else {
+					$this->writeLine ('\'' . addslashes ($value) . '\',');
+				}
+			}
+		}
+		
+		$this->writeLine ($tab . ')', false);
+	}
+	
+	private function decodeArray ($array) {
+		$new_array = array ();
+		
+		foreach ($array as $key => $value) {
+			if (is_array ($value)) {
+				$new_array[$key] = $this->decodeArray ($value);
+			} else {
+				$new_array[$key] = stripslashes ($value);
+			}
+		}
+		
+		return $new_array;
+	}
+	
+	private function getLock ($type) {
+		if ($type == 'write') {
+			$lock = LOCK_EX;
+		} else {
+			$lock = LOCK_SH;
+		}
+	
+		$count = 1;
+		while (!flock ($this->file, $lock) && $count <= 50) {
+			$count++;
+		}
+		
+		if ($count >= 50) {
+			return false;
+		} else {
+			return true;
+		}
+	}
+	
+	private function releaseLock () {
+		flock ($this->file, LOCK_UN);
+	}
+}

+ 39 - 0
lib/dao/Model_pdo.php

@@ -0,0 +1,39 @@
+<?php
+/** 
+ * MINZ - Copyright 2011 Marien Fressinaud
+ * Sous licence AGPL3 <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * La classe Model_sql représente le modèle interragissant avec les bases de données
+ * Seul la connexion MySQL est prise en charge pour le moment
+ */
+class Model_pdo {
+	/**
+	 * $bd variable représentant la base de données
+	 */
+	protected $bd;
+	
+	/**
+	 * Créé la connexion à la base de données à l'aide des variables
+	 * HOST, BASE, USER et PASS définies dans le fichier de configuration
+	 */
+	public function __construct ($type = 'mysql') {
+		$db = Configuration::dataBase ();
+		try {
+			$string = $type
+			        . ':host=' . $db['host']
+			        . ';dbname=' . $db['base'];
+			$this->bd = new PDO (
+				$string,
+				$db['user'],
+				$db['password']
+			);
+		} catch (Exception $e) {
+			throw new PDOConnectionException (
+				$string,
+				$db['user'], MinzException::WARNING
+			);
+		}
+	}
+}

+ 77 - 0
lib/dao/Model_txt.php

@@ -0,0 +1,77 @@
+<?php
+/** 
+ * MINZ - Copyright 2011 Marien Fressinaud
+ * Sous licence AGPL3 <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * La classe Model_txt représente le modèle interragissant avec les fichiers de type texte
+ */
+class Model_txt {
+	/**
+	 * $file représente le fichier à ouvrir
+	 */
+	protected $file;
+	
+	/**
+	 * $filename est le nom du fichier
+	 */
+	protected $filename;
+	
+	/**
+	 * Ouvre un fichier dans $file
+	 * @param $nameFile nom du fichier à ouvrir
+	 * @param $mode mode d'ouverture du fichier ('a+' par défaut)
+	 * @exception FileNotExistException si le fichier n'existe pas
+	 *          > ou ne peux pas être ouvert
+	 */
+	public function __construct ($nameFile, $mode = 'a+') {
+		$this->filename = $nameFile;
+		$this->file = fopen ($this->filename, $mode);
+		
+		if (!$this->file) {
+			throw new FileNotExistException (
+				$this->filename,
+				MinzException::WARNING
+			);
+		}
+	}
+	
+	/**
+	 * Lit une ligne de $file
+	 * @return une ligne du fichier
+	 */
+	public function readLine () {
+		return fgets ($this->file);
+	}
+	
+	/**
+	 * Écrit une ligne dans $file
+	 * @param $line la ligne à écrire
+	 */
+	public function writeLine ($line, $newLine = true) {
+		$char = '';
+		if ($newLine) {
+			$char = "\n";
+		}
+		
+		fwrite ($this->file, $line . $char);
+	}
+	
+	/**
+	 * Efface le fichier $file
+	 * @return true en cas de succès, false sinon
+	 */
+	public function erase () {
+		return ftruncate ($this->file, 0);
+	}
+	
+	/**
+	 * Ferme $file
+	 */
+	public function __destruct () {
+		if (isset ($this->file)) {
+			fclose ($this->file);
+		}
+	}
+}

+ 94 - 0
lib/exceptions/MinzException.php

@@ -0,0 +1,94 @@
+<?php
+
+class MinzException extends Exception {
+	const ERROR = 0;
+	const WARNING = 10;
+	const NOTICE = 20;
+
+	public function __construct ($message, $code = self::ERROR) {
+		if ($code != MinzException::ERROR
+		 && $code != MinzException::WARNING
+		 && $code != MinzException::NOTICE) {
+			$code = MinzException::ERROR;
+		}
+		
+		parent::__construct ($message, $code);
+	}
+}
+
+class PermissionDeniedException extends MinzException {
+	public function __construct ($file_name, $code = self::ERROR) {
+		$message = 'Permission is denied for `' . $file_name.'`';
+		
+		parent::__construct ($message, $code);
+	}
+}
+class FileNotExistException extends MinzException {
+	public function __construct ($file_name, $code = self::ERROR) {
+		$message = 'File doesn\'t exist : `' . $file_name.'`';
+		
+		parent::__construct ($message, $code);
+	}
+}
+class BadConfigurationException extends MinzException {
+	public function __construct ($part_missing, $code = self::ERROR) {
+		$message = '`' . $part_missing
+		         . '` in the configuration file is missing';
+		
+		parent::__construct ($message, $code);
+	}
+}
+class ControllerNotExistException extends MinzException {
+	public function __construct ($controller_name, $code = self::ERROR) {
+		$message = 'Controller `' . $controller_name
+		         . '` doesn\'t exist';
+		
+		parent::__construct ($message, $code);
+	}
+}
+class ControllerNotActionControllerException extends MinzException {
+	public function __construct ($controller_name, $code = self::ERROR) {
+		$message = 'Controller `' . $controller_name
+		         . '` isn\'t instance of ActionController';
+		
+		parent::__construct ($message, $code);
+	}
+}
+class ActionException extends MinzException {
+	public function __construct ($controller_name, $action_name, $code = self::ERROR) {
+		$message = '`' . $action_name . '` cannot be invoked on `'
+		         . $controller_name . '`';
+		
+		parent::__construct ($message, $code);
+	}
+}
+class RouteNotFoundException extends MinzException {
+	private $route;
+	
+	public function __construct ($route, $code = self::ERROR) {
+		$this->route = $route;
+		
+		$message = 'Route `' . $route . '` not found';
+		
+		parent::__construct ($message, $code);
+	}
+	
+	public function route () {
+		return $this->route;
+	}
+}
+class PDOConnectionException extends MinzException {
+	public function __construct ($string_connection, $user, $code = self::ERROR) {
+		$message = 'Access to database is denied for `' . $user . '`'
+		         . ' (`' . $string_connection . '`)';
+		
+		parent::__construct ($message, $code);
+	}
+}
+class CurrentPagePaginationException extends MinzException {
+	public function __construct ($page) {
+		$message = 'Page number `' . $page . '` doesn\'t exist';
+		
+		parent::__construct ($message, self::ERROR);
+	}
+}

+ 67 - 0
lib/lib_rss.php

@@ -0,0 +1,67 @@
+<?php
+// tiré de Shaarli de Seb Sauvage
+function small_hash ($txt) {
+
+    $t = rtrim (base64_encode (hash ('crc32', $txt, true)), '=');
+    $t = str_replace ('+', '-', $t); // Get rid of characters which need encoding in URLs.
+    $t = str_replace ('/', '_', $t);
+    $t = str_replace ('=', '@', $t);
+    
+    return $t;
+}
+
+function timestamptodate ($t, $hour = true) {
+	$jour = date ('d', $t);
+	$mois = date ('m', $t);
+	$annee = date ('Y', $t);
+	
+	switch ($mois) {
+	case 01:
+		$mois = 'janvier';
+		break;
+	case 02:
+		$mois = 'février';
+		break;
+	case 03:
+		$mois = 'mars';
+		break;
+	case 04:
+		$mois = 'avril';
+		break;
+	case 05:
+		$mois = 'mai';
+		break;
+	case 06:
+		$mois = 'juin';
+		break;
+	case 07:
+		$mois = 'juillet';
+		break;
+	case 08:
+		$mois = 'août';
+		break;
+	case 09:
+		$mois = 'septembre';
+		break;
+	case 10:
+		$mois = 'octobre';
+		break;
+	case 11:
+		$mois = 'novembre';
+		break;
+	case 12:
+		$mois = 'décembre';
+		break;
+	}
+	
+	$date = $jour . ' ' . $mois . ' ' . $annee;
+	if ($hour) {
+		return $date . date (' \à H\:i', $t);
+	} else {
+		return $date;
+	}
+}
+
+function sortEntriesByDate ($entry1, $entry2) {
+	return $entry2->date (true) - $entry1->date (true);
+}

Разница между файлами не показана из-за своего большого размера
+ 8945 - 0
lib/lib_simplepie.php


+ 6 - 0
public/.htaccess

@@ -0,0 +1,6 @@
+RewriteEngine On
+RewriteCond %{REQUEST_FILENAME} -s [OR]
+RewriteCond %{REQUEST_FILENAME} -l [OR]
+RewriteCond %{REQUEST_FILENAME} -d
+RewriteRule ^.*$ - [NC,L]
+RewriteRule ^.*$ index.php [NC,L]

+ 38 - 0
public/index.php

@@ -0,0 +1,38 @@
+<?php
+# ***** BEGIN LICENSE BLOCK *****
+# MINZ - A free PHP framework
+# Copyright (C) 2011 Marien Fressinaud
+# 
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+# 
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+# 
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# ***** END LICENSE BLOCK *****
+
+// Constantes de chemins
+define ('PUBLIC_PATH', realpath (dirname(__FILE__)));
+define ('LIB_PATH', realpath (PUBLIC_PATH.'/../lib'));
+define ('APP_PATH', realpath (PUBLIC_PATH.'/../app'));
+define ('LOG_PATH', realpath (PUBLIC_PATH.'/../log'));
+define ('CACHE_PATH', realpath (PUBLIC_PATH.'/../cache'));
+
+set_include_path (get_include_path ()
+                 . PATH_SEPARATOR
+                 . LIB_PATH
+                 . PATH_SEPARATOR
+                 . APP_PATH);
+
+require_once(APP_PATH.'/App_FrontController.php');
+
+$front_controller = new App_FrontController ();
+$front_controller->init ();
+$front_controller->run ();

+ 295 - 0
public/theme/base.css

@@ -0,0 +1,295 @@
+* {
+	margin: 0;
+	padding: 0;
+}
+html, body {
+	height: 100%;
+}
+
+/* LIENS */
+a {
+	color: #0062BE;
+	text-decoration: none;
+}
+	a:hover {
+		text-decoration: underline;
+	}
+	a.add, a.update, a.delete, a.back {
+		height: 30px;
+		padding: 0 20px;
+		line-height: 30px;
+	}
+		a.add {
+			background: url("img/add.png") no-repeat left 3px;
+		}
+		a.update {
+			background: url("img/update.png") no-repeat left 3px;
+		}
+		a.delete {
+			background: url("img/delete.png") no-repeat left 3px;
+		}
+		a.back {
+			background: url("img/back.png") no-repeat left 5px;
+		}
+
+/* LISTES */
+ul {
+	margin: 10px 0 10px 30px;
+	line-height: 190%;
+}
+
+/* TITRES */
+h1, h2, h3 {
+	min-height: 50px;
+	padding: 10px 0 20px;
+	line-height: 50px;
+}
+
+/* IMG */
+img {
+	max-width: 100%;
+	vertical-align: middle;
+}
+
+/* FORMULAIRES */
+form {
+	width: 450px;
+	max-width: 100%;
+	margin: 20px auto;
+	padding: 20px;
+	background: #f0f0f0;
+	border: 1px solid #ddd;
+	border-radius: 3px;
+	box-shadow: 0 1px 3px #aaa;
+}
+	label {
+		display: block;
+		padding: 0 20px 0 0;
+		font-weight: bold;
+	}
+	input:focus, textarea:focus {
+		color: #3366cc !important;
+		border: 1px solid #3366cc !important;
+	}
+	input[type="text"], input[type="url"], input[type="number"], textarea {
+		display: block;
+		width: 430px;
+		max-width: 95%;
+		margin: 5px 0 5px;
+		padding: 5px 10px;
+		background: #fff;
+		border: 1px solid #ccc;
+		border-radius: 5px;
+		font-size: 90%;
+	}
+		textarea {
+			min-height: 100px;
+			font-size: 110%;
+			line-height: 150%;
+			font-family: Monospace;
+		}
+	input[type="submit"] {
+		width: 100%;
+		margin: 5px 0 5px;
+		padding: 5px 0;
+	}
+	.radio_group label {
+		display: inline-block;
+		padding: 0 0 0 5px;
+		font-weight: normal;
+	}
+
+/* STRUCTURE */
+#global {
+	display: table;
+	width: 100%;
+	height: 100%;
+}
+	.aside {
+		display: table-cell;
+		height: 100%;
+		width: 250px;
+		vertical-align: top;
+		border-right: 1px solid #aaa;
+	}
+		.aside ul {
+			margin: 0;
+			list-style: none;
+		}
+			.aside li {
+				height: 50px;
+				line-height: 50px;
+			}
+				.aside li.active a {
+					background: #0062BE !important;
+					color: #fff;
+				}
+				.aside li a {
+					display: block;
+					padding: 0 10px;
+				}
+					.aside li a:hover {
+						text-decoration: none;
+						background: #fafafa;
+					}
+				.aside li h2 {
+					height: 50px;
+					padding: 0;
+					text-align: center;
+					background: #eee;
+					line-height: 50px;
+				}
+		.aside form {
+			display: table;
+			width: 250px;
+			margin: 0;
+			padding: 0;
+			background: #f0f0f0;
+			border: none;
+			border-bottom: 1px solid #aaa;
+			border-radius: 0;
+			box-shadow: none;
+		}
+			.aside form input {
+				display: table-cell;
+				height: 48px;
+				line-height: 48px;
+			}
+				.aside form input[type="url"] {
+					width: 200px;
+					margin: 0;
+					padding: 0;
+					border: none !important;
+					border-radius: 0;
+				}
+				.aside form input[type="submit"] {
+					width: 50px;
+					margin: 0;
+					padding: 0;
+					border: none;
+					border-radius: 0;
+				}
+	#main {
+		display: table-cell;
+		height: 100%;
+		max-width: 800px;
+		line-height: 180%;
+		background: #fafafa;
+	}
+		#top {
+			width: 100%;
+			background: #eee;
+			border-bottom: 1px solid #aaa;
+			box-shadow: 0 1px 3px #aaa;
+			text-align: center;
+		}
+			#top a {
+				display: inline-block;
+				height: 50px;
+				width: 50%;
+				line-height: 50px;
+				font-weight: bold;
+			}
+				#top a:hover {
+					background: #fafafa;
+					text-decoration: none;
+				}
+		#stream {
+			padding: 20px 0;
+		}
+
+.post {
+	width: 80%;
+	margin: 0 auto;
+}
+	.post.flux {
+		margin: 40px auto;
+		padding: 25px 20px;
+		font-family: Palatino, "Times New Roman", serif;
+		line-height: 170%;
+		border-left: 5px solid #aaa;
+		background: #eee;
+		border-radius: 5px;
+		box-shadow: 0 1px 3px #aaa;
+	}
+		.post.flux .before {
+			color: #666;
+			font-size: 80%;
+			text-align: center;
+		}
+		.post.flux .after {
+			margin: 50px 0 0;
+			font-size: 80%;
+			text-align: center;
+			border-top: 1px solid #aaa;
+		}
+			.post.flux .after a {
+				display: inline-block;
+				height: 50px;
+				line-height: 50px;
+				width: 50%;
+			}
+				.post.flux .after  a:hover {
+					background: #fff;
+					text-decoration: none;
+					border-radius: 0 0 5px 5px;
+					box-shadow: 0 1px 3px #aaa;
+				}
+		/* temporaire !!! */
+		.post.flux .content {
+			/*display: none;*/
+		}
+			.post.flux .content img {
+				border-radius: 5px;
+			}
+			.post.flux .content pre {
+				width: 90%;
+				margin: 10px auto;
+				padding: 10px;
+				overflow: auto;
+				background: #666;
+				border: 1px solid #000;
+				color: #fafafa;
+				border-radius: 5px;
+			}
+			.post.flux .content q {
+				display: block;
+				width: 90%;
+				margin: 10px auto;
+				padding: 10px;
+				font-style: italic;
+				border-left: 15px solid #ccc;
+				color: #669;
+				border-radius: 5px 0 0 5px;
+			}
+		.post.flux.not_read {
+			border-left: 5px solid #FF5300;
+			background: #FFF3ED;
+		}
+		.post.flux.favorite {
+			border-left: 5px solid #FFC300;
+			background: #FFF6DA;
+		}
+
+/*** PAGINATION ***/
+.pagination {
+	margin: 20px 0;
+	list-style: none;
+	text-align: center;
+	font-size: 100%;
+	}
+	.pagination li {
+		display: inline-block;
+		width: 30px;
+		height: 30px;
+		}
+		.pagination li.pager-next, .pagination li.pager-previous, .pagination li.pager-first, .pagination li.pager-last {
+			width: 100px;
+		}
+		.pagination li.pager-current {
+			font-weight: bold;
+			}
+		.pagination li a {
+			display: block;
+			color: #F09600;
+			}

Некоторые файлы не были показаны из-за большого количества измененных файлов