diff --git a/application/modules/opac/controllers/SitoController.php b/application/modules/opac/controllers/SitoController.php
index 8d180a1a92c9889ae43154f7ae994d61b9bf3be1..523be98d64c5bb515a862b7a558e5e77e7cab2d1 100644
--- a/application/modules/opac/controllers/SitoController.php
+++ b/application/modules/opac/controllers/SitoController.php
@@ -16,7 +16,7 @@
  *
  * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
  * along with AFI-OPAC 2.0; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA 
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 
 class SitoController extends Zend_Controller_Action {
@@ -50,14 +50,14 @@ class SitoController extends Zend_Controller_Action {
 
 	public function viewrecentAction() {
 		$class_sito = new Class_Sitotheque();
-		$nb_sito = (int)$this->_request->getParam('nb');
+		$nb_sito = (int)$this->_request->getParam('nb', 10);
+		$limit = ($nb_sito < 1) ? 10 : $nb_sito;
 
-        if ($nb_sito < 1)	$limit = 10; else $limit = $nb_sito;
-            $sitos = $class_sito->getLastSito($limit);
-
-			$this->view->sitos = $sitos;
-			$this->view->title = $this->view->_("Derniers Sites");
-			$this->renderScript('sito/viewsitos.phtml');
+		$sites = Class_Sitotheque::findAllBy(['order' => 'date_maj desc',
+																					'limit' => $limit]);
+		$this->view->sitos = $sites;
+		$this->view->title = $this->view->_("Derniers Sites");
+		$this->renderScript('sito/viewsitos.phtml');
 	}
 
 
@@ -66,7 +66,7 @@ class SitoController extends Zend_Controller_Action {
 
 		$preferences = Class_Profil::getCurrentProfil()
 			->getModuleAccueilPreferences($id_module, 'SITO');
-		
+
 		$sites = Class_Sitotheque::getSitesFromIdsAndCategories(
 			explode('-', $preferences['id_items']),
 			explode('-', $preferences['id_categorie']));
diff --git a/library/Class/CompositeBuilder.php b/library/Class/CompositeBuilder.php
index 1d64d6682c0e1e133180be9c0b3e8b93bdb55fa4..c177513d22d2c81c38fb82031250299d2d681d85 100644
--- a/library/Class/CompositeBuilder.php
+++ b/library/Class/CompositeBuilder.php
@@ -16,7 +16,7 @@
  *
  * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
  * along with AFI-OPAC 2.0; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA 
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 
 require_once "CompositeBuilder.php";
@@ -95,24 +95,25 @@ class AbstractItem {
 	protected function addAbstractItemsFromRows($rows, $id_field, $cat_field, $build_func){
 		$builder = $this->getBuilder();
 		foreach ($rows as $row) {
-			$abs_item = $builder->$build_func($row[$id_field], 
+			$abs_item = $builder->$build_func($row[$id_field],
 																				$row[$cat_field]);
 			if ($abs_item !== null) $abs_item->update_attributes($row);
 		}
+		$builder->tryToAddOrphans();
 		return $this;
 	}
 
 	public function addItemsFromRows($rows, $id_field, $cat_field){
-		return $this->addAbstractItemsFromRows($rows, 
-																					 $id_field, 
-																					 $cat_field, 
+		return $this->addAbstractItemsFromRows($rows,
+																					 $id_field,
+																					 $cat_field,
 																					 "newItemIn");
 	}
 
 	public function addCategoriesFromRows($rows, $id_field, $cat_field){
-		return $this->addAbstractItemsFromRows($rows, 
+		return $this->addAbstractItemsFromRows($rows,
 																					 $id_field,
-																					 $cat_field, 
+																					 $cat_field,
 																					 "newSubcategoryIn");
 	}
 }
@@ -130,40 +131,47 @@ class ItemCategory extends AbstractItem {
 
 	public function initialize() {
 		parent::initialize();
-		$this->categories = array();
-		$this->items = array();
+		$this->categories = [];
+		$this->items = [];
 	}
 
+
 	public function getCategories() {
 		return array_values($this->categories);
 	}
 
+
 	public function getItems() {
 		return array_values($this->items);
 	}
 
+
 	public function addCategory($sub_category) {
 		return $this->addChildToList($sub_category, $this->categories);
 	}
 
+
 	public function addAllCategories($categories) {
-		foreach($categories as $cat)
-			$this->addCategory($cat);
+		array_walk($categories, function($cat) { $this->addCategory($cat); });
 	}
 
+
 	public function addItem($sub_item) {
 		return $this->addChildToList($sub_item, $this->items);
 	}
 
+
 	public function getCategoryWithId($id) {
 		if ($this->getId()==$id) return $this;
 		return $this->getChildWithId($id, $this->categories, "getCategoryWithId");
 	}
 
+
 	public function getItemWithId($id) {
 		return $this->getChildWithId($id, $this->items, "getItemWithId");
 	}
 
+
 	public function acceptVisitor($visitor) {
 		$visitor->startVisitCategory($this);
 		foreach($this->categories as $cat) $cat->acceptVisitor($visitor);
@@ -171,6 +179,7 @@ class ItemCategory extends AbstractItem {
 		$visitor->endVisitCategory($this);
 	}
 
+
 	public function toJSON(){
 		$json_categories = array();
 		$json_items = array();
@@ -178,7 +187,7 @@ class ItemCategory extends AbstractItem {
 		foreach($this->categories as $cat)
 			$json_categories []= $cat->toJSON();
 
-		foreach($this->items as $item) 
+		foreach($this->items as $item)
 			$json_items []= $item->toJSON();
 
 		return  '{'.
@@ -191,7 +200,7 @@ class ItemCategory extends AbstractItem {
 
 	protected function getChildWithId($id, &$list, $get_func) {
 		if (array_key_exists($id, $list))	return $list[$id];
-		
+
 		foreach($this->categories as $subcat) {
 			$result = $subcat->$get_func($id);
 			if ($result) return $result;
@@ -201,7 +210,7 @@ class ItemCategory extends AbstractItem {
 	}
 
 	protected function addChildToList($child, &$list) {
-		$list [$child->getId()]= $child;
+		$list[$child->getId()] = $child;
 	  $child->setParent($this);
 		return $child;
 	}
@@ -243,23 +252,21 @@ class CompositeBuilder {
 		return $this->root->getCategoryWithId($id);
 	}
 
-	public function newSubcategoryIn($id, $parent_id) {		
+	public function newSubcategoryIn($id, $parent_id) {
 		$new_category = $this->newCategory($id);
 
 		if (array_key_exists($parent_id, $this->orphan_categories)) {
 			$parent = $this->orphan_categories[$parent_id]['category'];
 			$parent->addCategory($new_category);
 		} else {
-			$this->orphan_categories [$id]= array('parent_id' => $parent_id, 
+			$this->orphan_categories [$id]= array('parent_id' => $parent_id,
 																						'category' => $new_category);
 		}
 
-		while ($this->tryToAddOrphans()) {}
-		
 		return $new_category;
 	}
 
-	protected function tryToAddOrphans(){
+	public function tryToAddOrphans(){
 		$orphan_added = false;
 
 		foreach($this->orphan_categories as $id => $pid_cat) {
@@ -274,6 +281,7 @@ class CompositeBuilder {
 		return $orphan_added;
 	}
 
+
 	public function newItemIn($id, $parent_id) {
 		$parent = $this->getCategoryWithId($parent_id);
 		if ($parent==null) return;
diff --git a/library/Class/Import/Typo3.php b/library/Class/Import/Typo3.php
new file mode 100644
index 0000000000000000000000000000000000000000..c46f11dbfd5fbf14e88109dd5e87d5a76d844e3e
--- /dev/null
+++ b/library/Class/Import/Typo3.php
@@ -0,0 +1,812 @@
+<?php
+/**
+ * Copyright (c) 2012-2014, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * AFI-OPAC 2.0 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.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * AFI-OPAC 2.0 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 AFI-OPAC 2.0; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+class Class_Import_Typo3 {
+  const DEFAULT_CAT_ID = 30000;
+	const BOKEH_IMG_URL="http://www.mediathequeouestprovence.fr/uploads/";
+//	const BOKEH_IMG_URL="http://web.afi-sa.net/miop-test.net/userfiles/image/uploads/";
+	const BOKEH_ATTACHMENT_URL="http://www.mediathequeouestprovence.fr/fileadmin/fichiers/";
+//	const BOKEH_ATTACHMENT_URL="http://web.afi-sa.net/miop-test.net/userfiles/file/";
+	const BOKEH_FILEADMIN_URL= "http://www.mediathequeouestprovence.fr/fileadmin/";
+//	const BOKEH_FILEADMIN_URL= "http://web.afi-sa.net/miop_test.net/userfiles/image/";
+	protected
+		$user_mapper,
+		$unknown_koha_urls = [];
+
+  public function __construct() {
+    $this->t3db = new Typo3DB();
+    $this->report = ['categories_created' => 0,
+                     'domaine_created' => 0,
+                     'categories_sites_created' => 0,
+                     'articles_created' => 0,
+                     'articles_errors' => 0,
+                     'pages_created' => 0,
+                     'pages_errors' => 0,
+                     'calendar_created' => 0,
+                     'calendar_errors' => 0,
+                     'articles_pages_errors' => 0,
+                     'articles_pages_created' => 0,
+                     'sitotheque_created' => 0,
+                     'sitotheque_errors' => 0];
+
+    $this->errors = [];
+    $this->domaine_map = new DomaineMap();
+		$this->userMap = new UserMap();
+  }
+
+	public function setTypo3DB($t3db) {
+		$this->t3db=$t3db;
+		return $this;
+	}
+
+
+  public function run($what = 'all') {
+		$_SERVER['HTTP_HOST']='localhost';
+
+    if ($what == 'all' || $what == 'users') {
+      Class_Import_Typo3_Logs::getInstance()->addLogRow("\n\n ******** Importing users");
+      $this->import_user();
+    }
+
+    if ($what == 'all' || $what == 'articles') {
+      Class_Import_Typo3_Logs::getInstance()->addLogRow("\n\n ******** Importing categories");
+      $this->import_categories();
+
+      Class_Import_Typo3_Logs::getInstance()->addLogRow("\n\n ******** Importing articles pages");
+      $this->importArticlesPages();
+
+      Class_Import_Typo3_Logs::getInstance()->addLogRow("\n\n ******** Importing articles");
+      $this->importArticles();
+
+      Class_Import_Typo3_Logs::getInstance()->addLogRow("\n\n ******** Importing events");
+      $this->importCalendar();
+
+      Class_Import_Typo3_Logs::getInstance()->addLogRow("\n\n ******** Importing sites");
+      $this->importSites();
+    }
+
+		return Class_Import_Typo3_Logs::getInstance()->report();
+  }
+
+
+  public function import_user() {
+    $t3users = $this->t3db->findAllUsers();
+
+    foreach($t3users as $t3user) {
+			$this->userMap->newUser($t3user);
+    }
+
+		Class_Import_Typo3_Logs::getInstance()->setUsersLog();
+  }
+
+
+  public function import_categories() {
+		Class_Article::deleteBy([]);
+    Class_ArticleCategorie::deleteBy([]);
+    Class_Catalogue::deleteBy([]);
+    Class_CodifThesaurus::deleteBy([]);
+    Class_Sitotheque::deleteBy([]);
+    Class_SitothequeCategorie::deleteBy([]);
+    $this->reset_auto_increment();
+
+    $this->cal_categories_map= new CalendarCategoriesMap('Agenda', $this->domaine_map);
+    $this->news_categories_map = new ArticleCategoriesMap('Non classé', $this->domaine_map);
+    $this->sites_categories_map = new SiteCategoriesMap('Non classé', $this->domaine_map);
+
+    $t3categories = $this->t3db->findAllNewsCat();
+		$this->news_categories_map->build($t3categories);
+		$this->sites_categories_map->build($t3categories);
+
+    // Categories d'évènements.
+    $cal_categories = $this->t3db->findAllEventCat();
+		$this->cal_categories_map->build($cal_categories);
+
+		Class_Import_Typo3_Logs::getInstance()->setCategoriesLog(count($t3categories));
+  }
+
+
+  public function importSites() {
+    $t3news = $this->t3db->findAllSites();
+
+    foreach($t3news as $new) {
+			$element = $this->traceUnknownKohaUrls(
+																	function() use ($new){
+																		return $this->createSito($new);
+																	},
+																 $new['uid']);
+
+		}
+
+	}
+
+	public function createSito($new) {
+      $date_creation = date("Y-m-d H:i:s", $new['crdate']);
+      $element = Class_Sitotheque::newInstance(['id_cat' => $this->sites_categories_map->find($new['category'])->getId(),
+                                                'date_maj' => $date_creation,
+                                                'titre' => $new['title'],
+                                                'url' => $this->convertKohaToBokehUrl($this->format_url($new['ext_url'])),
+                                                'tags' => str_replace(', ', ';', $new['tx_danpextendnews_tags']),
+                                                'description' => $new['short']]);
+
+      if (!$element->save()) {
+        $this->report['sitotheque_errors']++;
+        Class_Import_Typo3_Logs::getInstance()->addErrorRow("site with uid : " . $new['uid'] . ' (' . $new['title'] . ') - ' . implode(', ', $element->getErrors()));
+        return $element;
+      }
+
+			$foreign_uids = $this->t3db->findAllForeignUidForNewsCat($new['uid']);
+			$this->sites_categories_map->setDomainsFor($element, $foreign_uids);
+
+			if (in_array(43, array_map(function($row){return $row['uid_foreign'];}, $foreign_uids))) {
+				$this->putSiteInTop5($element, $new);
+			}
+			return $element;
+	}
+
+
+
+	public function putSiteInTop5($element, $t3record) {
+		$parts = $element->getCategorie()->getPathParts();
+		$parts[0] = 'NOS TOPS 5';
+		$path = '/' . implode('/', $parts);
+
+		if ($title = $this->t3db->findPageTitle($t3record['pid'])) {
+			$path .= ('/' . $title);
+		}
+
+		$site_top5_cat = $this->sites_categories_map->findOrCreateByPath($path);
+		$element->setCategorie($site_top5_cat)->save();
+	}
+
+
+	public function manipulate_body($body,$image='') {
+		$body = $this->addImage($image).$body;
+		$body = $this->wrapWithHtml($body);
+		$body = $this->translateLinksToAnchor($body);
+		$body = $this->fixImageLinks($body);
+		$body = $this->fixAttachmentsLinks($body);
+		$body = $this->fixFileAdminLinks($body);
+		return str_replace(["\r\n", "\n\r","\n","\r"],"<br />",$body);
+	}
+
+
+	protected function addImage($image) {
+		if (!$image)
+			return '';
+		return '<img src="'.self::BOKEH_IMG_URL.'pics/'.$image.'" alt=""/>';
+
+	}
+
+
+	protected function fixImageLinks($body) {
+		return $this->replaceUserfilesLinkPath($body, 'uploads\/',self::BOKEH_IMG_URL);
+	}
+
+
+	protected function fixAttachmentsLinks($body) {
+		return $this->replaceUserfilesLinkPath($body, 'fileadmin\/fichiers\/',self::BOKEH_ATTACHMENT_URL);
+	}
+
+
+	protected function fixFileAdminLinks($body) {
+		return $this->replaceUserfilesLinkPath($body,'fileadmin\/user_upload\/',self::BOKEH_FILEADMIN_URL);
+	}
+
+	protected function replaceUserfilesLinkPath($body,$source, $dest) {
+		$regs= '/(< *(a|img)[^>]*(href|src) *= *["\'])(http:\/\/[^"\']*\/)?'.$source.'([^"\']*)/i';
+		return preg_replace($regs, '$1'.$dest.'$5', $body);
+	}
+
+
+	protected function translateLinksToAnchor($content) {
+		while((false !== ($start = strpos($content, '<link '))) && (false !== ($end = strpos($content, '</link>')))) {
+			if(false === ($link = substr($content, $start, $end - $start + 7)))
+				return $content;
+			$content = str_replace($link, $this->linkToAnchor($link), $content);
+		}
+		return $content;
+	}
+
+
+	protected function linkToAnchor($link) {
+		$link_array = explode(' ', $link);
+		$target = '';
+
+		if(in_array('_blank', $link_array))
+			 $target = ' target="_blank"';
+
+		if(!isset($link_array[1]))
+			return;
+
+		$url = $link_array[1];
+
+		if(false !== ($end = strpos($link_array[1], '>')))
+			$url = substr($url, 0, $end);
+
+		$url = $this->convertKohaToBokehUrl($url);
+
+		return '<a href="' . $url . '">' . $this->getContentFromLink($link) . '</a>';
+	}
+
+	protected function convertKohaToBokehUrl($url) {
+		if (false === strpos($url,'http://koha.mediathequeouestprovence.fr/'))
+			return $url;
+
+		if (preg_match('/biblionumber=(\d+)$/', $url, $matches))
+			return $this->urlViewNotice($url, $matches[1]);
+
+		if (preg_match('/opac-search.pl\?q=au:(.+)$/', $url, $matches))
+			return $this->urlSearchAuthors($url, $matches[1]);
+
+		if (preg_match('/opac-search.pl\?q=([^=&]+)$/', $url, $matches))
+			return $this->urlSimpleSearch($url, $matches[1]);
+
+		$this->unknown_koha_urls[] = $url;
+
+		return $url;
+	}
+
+	protected function urlSearchAuthors($url, $search_expression) {
+		return '/miop-test.net/recherche/simple/rech_auteurs/'.$search_expression;
+	}
+
+
+	protected function urlSimpleSearch($url, $search_expression) {
+		return '/miop-test.net/recherche/simple/expressionRecherche/'.$search_expression;
+	}
+
+	protected function urlViewNotice($url, $id_origine) {
+		if (!$exemplaire = Class_Exemplaire::findFirstBy(['id_origine' => $id_origine]))
+			return $url;
+
+		$clef_alpha = $exemplaire->getNotice()->getClefAlpha();
+		return '/miop-test.net/recherche/viewnotice/clef/'.$clef_alpha;
+	}
+
+
+	protected function getContentFromLink($link) {
+		$content_start = strpos($link, '>') + 1;
+		return  substr($link, $content_start, strpos($link, '</link>') - $content_start);
+	}
+
+
+	protected function wrapWithHtml($content) {
+
+		if('<' === substr($content,0,1))
+			return $content;
+
+		return '<p>' . $content . '</p>';
+	}
+
+
+  public function importArticles() {
+    $t3news = $this->t3db->findAllArticles();
+    foreach($t3news as $new) {
+			$id_cat = $this->news_categories_map->find($new['category'])->getId();
+			$element = $this->traceUnknownKohaUrls(
+																	function() use ($new, $id_cat){
+																		return $this->createArticle($new, $id_cat);
+																	},
+ 																  $new['uid']);
+
+
+			$this->news_categories_map->setDomainsFor($element,
+																								$this->t3db->findAllForeignUidForNewsCat($new['uid']));
+    }
+
+		Class_Import_Typo3_Logs::getInstance()->setArticlesLog();
+    return $this;
+  }
+
+
+	public function createArticle($new,$id_cat) {
+		$date_creation = date("Y-m-d H:i:s", $new['crdate']);
+		$debut = $new['starttime'] ? date("Y-m-d", $new['starttime']) : '';
+		$fin = $new['endtime'] ? date("Y-m-d", $new['endtime']) : '';
+
+		$element = Class_Article::newInstance(['date_creation' => $date_creation,
+																					 'debut' => $debut,
+																					 'fin' => $new['hidden'] ? null : $fin,
+																					 'id_user' => $this->userMap->find($new['cruser_id']),
+																					 'description' => $new['short']? $this->manipulate_body($new['short'],$new['image']): '',
+																					 'contenu' => $new['bodytext'] ? $this->manipulate_body($new['bodytext'],$new['image']) : '&nbsp;',
+																					 'id_cat' => $id_cat,
+																					 'status' => Class_Article::STATUS_VALIDATED,
+																					 'tags' => str_replace(', ', ';', $new['tx_danpextendnews_tags']),
+																					 'titre' => $new['title']]);
+
+
+		if (!$element->save()) {
+			Class_Import_Typo3_Logs::getInstance()->incrementArticleRejected($element, [$new['uid'],
+																																									$new['title']]);
+			return $element;
+
+		}
+		Class_Import_Typo3_Logs::getInstance()->incrementArticlesSaved();
+		return $element;
+	}
+
+  private function typo_event_to_date($date, $time) {
+    $year = substr($date, 0, 4);
+    $month = substr($date, 4, 2);
+    $day = substr($date, 6, 4);
+
+    $seconds_left = $time % 3600;
+    $hour = (int)(($time - $seconds_left) / 3600);
+    $minutes = (int)($seconds_left / 60);
+
+    if (0 == $hour + $minutes) {
+      $hour = 23;
+      $minutes = 59;
+    }
+
+    return sprintf('%s-%s-%s %s:%s:%s', $year, $month, $day, $hour, $minutes, '00');
+  }
+
+
+  public function importCalendar() {
+    $cal_news = $this->t3db->findAllCalendarEvents();
+    foreach($cal_news as $new) {
+			$this->traceUnknownKohaUrls( function() use ($new) {
+																														return $this->createCalendar($new);
+																													},
+																	 $new['uid']);
+		}
+    return $this;
+  }
+
+
+	public function createCalendar($new) {
+
+      $date_creation = date("Y-m-d H:i:s", $new['crdate']);
+      $debut = $new['starttime'] ? date("Y-m-d", $new['starttime']) : '';
+      $fin = $new['endtime'] ? date("Y-m-d", $new['endtime']) : '';
+      $event_debut = $this->typo_event_to_date($new['start_date'], $new['start_time']);
+      $event_fin = $this->typo_event_to_date($new['end_date'], $new['end_time']);
+
+      $element = Class_Article::newInstance(['debut' => $debut,
+                                             'fin' => $new['hidden'] ? null : $fin,
+                                             'events_debut' => $event_debut,
+                                             'events_fin' => $event_fin,
+                                             'id_user' => $this->userMap->find($new['cruser_id']),
+                                             'all_day' => $new['allday'],
+                                             'description' => '',
+                                             'contenu' => $this->manipulate_body($new['description'] ? $new['description'] : $new['title'],$new['image']),
+                                             'id_cat' => $this->cal_categories_map->find($new['category_id'])->getId(),
+                                             'status' => Class_Article::STATUS_VALIDATED,
+                                             'tags' => '',
+                                             'titre' => $new['title']]);
+
+      if (!$element->save()) {
+        $this->report['calendar_errors']++;
+        Class_Import_Typo3_Logs::getInstance()->addErrorRow('calendar with uid: ' . $new['uid'] . '(' . $new['title'] . ') - ' . implode(', ', $element->getErrors()));
+        return $element;
+      }
+
+			$element->setDateCreation($date_creation)->save();
+
+      $this->report['calendar_created']++;
+
+			$this->cal_categories_map->setDomainsFor($element,
+																							 $this->t3db->findAllForeignUidForCalendarEventCategory($new['uid']));
+			return $element;
+
+	}
+
+
+  public function importArticlesPages() {
+    $rows = $this->t3db->findAllContents();
+
+    $pages_cat = Class_ArticleCategorie::newInstance(['libelle' => 'Pages fixes']);
+    $pages_cat->save();
+    foreach($rows as $new) {
+			$this->traceUnknownKohaUrls(
+				function() use ($new, $pages_cat){
+					return $this->createArticlePage($new, $pages_cat);
+				},
+				$new['uid']);
+
+      $this->report['pages_created']++;
+    }
+    return $this;
+  }
+
+
+	public function createArticlePage($new, $pages_cat) {
+		$date_creation = date("Y-m-d H:i:s", $new['crdate']);
+		$debut = $new['starttime'] ? date("Y-m-d", $new['starttime']) : '';
+		$fin = $new['endtime'] ? date("Y-m-d", $new['endtime']) : '';
+
+
+		$element = Class_Article::newInstance(['date_creation' => $date_creation,
+																					 'debut' => $debut,
+																					 'fin' => $new['hidden'] ? null : $fin,
+																					 'id_user' => $this->userMap->find($new['cruser_id']),
+																					 'description' => '',
+																					 'contenu' => $new['bodytext'] ? $this->manipulate_body($new['bodytext'],$new['image']) :'',
+																					 'id_cat' => $pages_cat->getId(),
+																					 'status' => Class_Article::STATUS_VALIDATED,
+																					 'tags' => '',
+																					 'titre' => $new['header']]);
+
+		if (!$element->save()) {
+			$this->report['pages_errors']++;
+			Class_Import_Typo3_Logs::getInstance()->addErrorRow('pages with uid: ' . $new['uid'] . ' (' . $new['title'] . ') - ' . implode(', ', $element->getErrors()));
+		}
+		return $element;
+	}
+
+
+	public function traceUnknownKohaUrls($closure,$uid) {
+		$this->unknown_koha_urls = [];
+		$element = $closure();
+		foreach($this->unknown_koha_urls as $url) {
+			Class_Import_Typo3_Logs::getInstance()->addUnknownUrl($element,
+																														$url,
+																														$uid);
+		}
+		return $element;
+	}
+
+
+
+  public function format_url($url) {
+    if (!$url = explode(' ', $url)[0])
+      return '';
+    $url = preg_replace(['/^http:\/\//', '/^\/\//'], '', trim(strtolower($url)));
+    return (0 !== strpos('http', $url)) ? 'http://'.$url : $url;
+  }
+
+
+
+
+  public function reset_auto_increment() {
+    Zend_Registry::get('sql')->execute('alter table cms_article AUTO_INCREMENT=1');
+    Zend_Registry::get('sql')->execute('alter table cms_categorie AUTO_INCREMENT=1');
+    Zend_Registry::get('sql')->execute('alter table sito_url AUTO_INCREMENT=1');
+    Zend_Registry::get('sql')->execute('alter table sito_categorie AUTO_INCREMENT=1');
+    Zend_Registry::get('sql')->execute('alter table catalogue AUTO_INCREMENT=1');
+  }
+
+}
+
+
+class UserMap {
+
+
+	protected $map = [];
+	protected $saved = 0;
+	protected $updated = 0;
+	protected $errors = [];
+	protected $t3users = 0;
+
+
+	public function __construct() {
+		$where = 'ROLE_LEVEL < ' . ZendAfi_Acl_AdminControllerRoles::ADMIN_PORTAIL; // exclude ADMIN.
+		$where .= ' AND ROLE_LEVEL <> ' . ZendAfi_Acl_AdminControllerRoles::ABONNE_SIGB; // exclude SIGB members.
+
+    Class_Users::deleteBy(['where' => $where]);
+	}
+
+
+	public function updateUser($data, &$user) {
+		$user->setNom($data['realName']);
+		$user->setMail($data['email']);
+	}
+
+
+	public function newUser($data) {
+		Class_Import_Typo3_Logs::getInstance()->incrementT3Users();
+			$user;
+			$updated = 0;
+			if ($user = Class_Users::findFirstBy(['login'=> $data['username']])) {
+				$updated = 1;
+				$this->updateUser($data, $user);
+			}
+			else {
+				$user = Class_Users::newInstance(['nom' => $data['realName'],
+																					'mail' => $data['email'],
+																					'login' => $data['username'],
+																					'password' => 'achanger']);
+			}
+
+			if ($data['admin'] == '1') {
+				$user->beAdminPortail();
+			}
+			else {
+				$user->changeRoleTo(ZendAfi_Acl_AdminControllerRoles::MODO_PORTAIL);
+			}
+
+			if (!$user->save())
+				return Class_Import_Typo3_Logs::getInstance()->incrementRejected($user, $data);
+
+			$updated
+				? Class_Import_Typo3_Logs::getInstance()->incrementUpdated()
+				: Class_Import_Typo3_Logs::getInstance()->incrementSaved();
+
+			$this->map[$data['uid']] = $user->getId();
+	}
+
+
+	public function find($uid) {
+    return  isset($this->map[$uid])
+      ? $this->map[$uid]
+      : 0;
+	}
+}
+
+
+
+
+abstract class CategoriesMap {
+  protected
+    $map = [],
+    $default_cat,
+    $domaine_map;
+
+  public function __construct($default_cat_title, $domaine_map) {
+    $this->domaine_map = $domaine_map;
+    $this->default_cat = $this->_buildNewCategory($default_cat_title);
+    $this->default_cat->assertSave();
+  }
+
+
+	public function build($t3categories) {
+    foreach($t3categories as $t3cat)
+      $this->newCategory($t3cat['title'], $t3cat['uid'], $t3cat['parent_category']);
+
+    foreach($t3categories as $t3cat)
+			$this->fixParent($t3cat['uid'], $t3cat['parent_category']);
+
+		$this->createDomainMap();
+	}
+
+
+  public function getParentCatId($t3id) {
+    if (isset($this->map[$t3id])) {
+      $cat = $this->map[$t3id];
+      return $cat->getId();
+    }
+    return  0;
+  }
+
+  public function newCategory($title, $t3uid, $t3pid) {
+    $category = $this->_buildNewCategory($title, $this->getParentCatId($t3pid));
+    if(!$category->save())
+			return $this->addError($category, $title, $t3uid, $t3pid);
+
+		$this->incrementSaved();
+    return $this->map[$t3uid] = $category;
+  }
+
+
+	public function fixParent($t3uid, $t3pid) {
+		$category = $this->find($t3uid);
+		if (!$category->hasParentCategorie() &&
+				$category->getId() !== $this->default_cat->getId() &&
+				isset($this->map[$t3pid])) {
+			$category->setParentCategorie($this->find($t3pid))
+							 ->save();
+		}
+	}
+
+	public function createDomainMap() {
+		foreach($this->map as $category)
+			$this->domaine_map->findOrCreateDomaine($category);
+	}
+
+
+	abstract protected function addError($category, $title, $t3uid, $t3pid);
+
+	abstract protected function incrementSaved();
+
+
+  public function find($t3id) {
+    return  isset($this->map[$t3id])
+      ? $this->map[$t3id]
+      : $this->default_cat;
+  }
+
+
+	public function setDomainsFor($model, $t3_domain_ids) {
+		$domains = [$this->domaine_map->findOrCreateDomaine($model->getCategorie())];
+		foreach ($t3_domain_ids as $t3_domain) {
+			$category = $this->find($t3_domain['uid_foreign']);
+			$domains[] = $this->domaine_map->findOrCreateDomaine($category);
+		}
+
+		if (isset($category)) {
+			$model->setCategorie($category);
+		}
+
+		$model->setDomaines($domains)->save();
+	}
+}
+
+
+
+class DomaineMap {
+  protected $map = [];
+
+  public function findOrCreateDomaine($category) {
+    if (!$category)
+      return null;
+
+    $path = $category->getPath();
+
+    if (isset($this->map[$path]))
+      return $this->map[$path];
+
+    $parent_domaine = $this->findOrCreateDomaine($category->getParentCategorie());
+    $catalogue = Class_Catalogue::newInstance(['libelle' => $category->getLibelle(),
+                                               'parent_id' => $parent_domaine ? $parent_domaine->getId() : 0]);
+
+		if (!$catalogue->save()) {
+			Class_Import_Typo3_Logs::getInstance()->incrementDomainsRejected($catalogue);
+			return;
+		}
+
+		Class_Import_Typo3_Logs::getInstance()->incrementDomainsSaved();
+    return $this->map[$path] = $catalogue;
+  }
+}
+
+
+class ArticleCategoriesMap  extends CategoriesMap {
+  protected function _buildNewCategory($libelle, $parent_id=0) {
+    $cat =  Class_ArticleCategorie::newInstance(['libelle' => $libelle,
+                                                 'id_cat_mere' => $parent_id]);
+    /* if ($double_cat = $this->findByPath($cat->getPath)) */
+    /*  return $double_cat; */
+    /* $this->path_map [] = $cat; */
+    return $cat;
+  }
+
+
+	protected function addError($category, $title, $t3uid, $t3pid) {
+		return Class_Import_Typo3_Logs::getInstance()->incrementArticleCategoriesRejected($category, $title, $t3uid, $t3pid);
+	}
+
+
+	protected function incrementSaved() {
+		Class_Import_Typo3_Logs::getInstance()->incrementArticleCategoriesSaved();
+	}
+}
+
+
+class SiteCategoriesMap  extends CategoriesMap {
+  protected function _buildNewCategory($libelle, $parent_id=0) {
+    return Class_SitothequeCategorie::newInstance(['libelle' => $libelle,
+                                                   'id_cat_mere' => $parent_id]);
+  }
+
+
+	protected function addError($category, $title, $t3uid, $t3pid) {
+		return Class_Import_Typo3_Logs::getInstance()->incrementSitoCategoriesRejected($category, $title, $t3uid, $t3pid);
+	}
+
+
+	protected function incrementSaved() {
+		Class_Import_Typo3_Logs::getInstance()->incrementSitoCategoriesSaved();
+	}
+
+
+
+	public function findOrCreateByPath($path) {
+		if ($path == Trait_TreeNode::$PATH_SEPARATOR)
+			return null;
+
+		$parts = array_values(array_filter(explode(Trait_TreeNode::$PATH_SEPARATOR, $path)));
+
+		$category = null;
+		foreach($parts as $part) {
+			if (!$sub_category = Class_SitothequeCategorie::findFirstBy(['libelle' => $part,
+																																	 'id_cat_mere' => $category ? $category->getId() : 0])) {
+				$sub_category = Class_SitothequeCategorie::newInstance(['libelle' => $part,
+																														'id_cat_mere' => $category ? $category->getId() : 0]);
+				$sub_category->save();
+			}
+			$category = $sub_category;
+		}
+
+		return $category;
+	}
+}
+
+
+
+
+class CalendarCategoriesMap  extends ArticleCategoriesMap {
+  public function getParentCatId($t3id) {
+    return $this->find($t3id)->getId();
+  }
+}
+
+
+
+class Typo3DB {
+	protected
+		$t3db,
+		$pages_titles;
+
+
+
+	public function __construct() {
+    $this->t3db = new Class_Systeme_Sql('','','','');
+		$this
+			->t3db
+			->setAdapter(Zend_Db::factory(
+																		'mysqli',
+																		['host' => 'localhost',
+																		 'username' => 'root',
+																		 'password' => 'root',
+																		 'dbname' =>  'miop_typo3']));
+	}
+
+
+	public function findPageTitle($uid) {
+		if (isset($this->pages_titles[$uid]))
+			return $this->pages_titles[$uid];
+
+		if (!$row = $this->t3db->fetchEnreg('select title from pages where uid='.$uid)) {
+			return $this->pages_titles[$uid] = '';
+		}
+
+		return $this->pages_titles[$uid] = $row['title'];
+	}
+
+	public function findAllUsers() {
+		return $this->t3db->fetchAll('select * from be_users where uid > 1');
+	}
+
+	public function findAllNewsCat() {
+		return $this->t3db->fetchAll('select * from tt_news_cat where deleted=0 order by uid ASC');
+	}
+
+	public function findAllEventCat() {
+		return $this->t3db->fetchAll("select * from tx_cal_category where deleted=0 order by uid");
+	}
+
+	public function findAllSites() {
+		return $this->t3db->fetchAll("select * from tt_news where deleted=0 and hidden=0 and ext_url>'' order by uid ASC");
+	}
+
+	public function findAllArticles() {
+		return $this->t3db->fetchAll("select * from tt_news where deleted=0 and hidden=0 and ext_url='' order by uid ASC");
+	}
+
+	public function findAllForeignUidForNewsCat($uid) {
+		return $this->t3db->fetchAll("select distinct uid_foreign from tt_news_cat_mm where uid_local=". $uid ." order by sorting");
+	}
+
+	public function findAllForeignUidForCalendarEventCategory($uid) {
+		return $this->t3db->fetchAll("select distinct uid_foreign from tx_cal_event_category_mm where uid_local=" . $uid . " order by sorting");
+	}
+
+
+	public function findAllCalendarEvents() {
+		return $this->t3db->fetchAll("select * from tx_cal_event where deleted=0 and hidden=0 order by uid ASC");
+	}
+
+	public function findAllContents() {
+		return $this->t3db->fetchAll("select * from tt_content where deleted=0 and hidden=0 and header>'' and bodytext>'' and  (ctype='text' or ctype='textpic') order by uid ASC");
+	}
+}
+?>
\ No newline at end of file
diff --git a/library/Class/Import/Typo3/Logs.php b/library/Class/Import/Typo3/Logs.php
new file mode 100644
index 0000000000000000000000000000000000000000..b04e837c0467940cb77a4ce533b13001c07a66aa
--- /dev/null
+++ b/library/Class/Import/Typo3/Logs.php
@@ -0,0 +1,220 @@
+<?php
+/**
+ * Copyright (c) 2012-2014, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * AFI-OPAC 2.0 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.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * AFI-OPAC 2.0 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 AFI-OPAC 2.0; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class Class_Import_Typo3_logs {
+	protected static $_instance;
+	protected
+		$_logs = '',
+		$_errors = '',
+		$_rejected_users = 0,
+		$_t3_users = 0,
+		$_saved_users = 0,
+		$_updated_users = 0,
+		$_rejected_categories_sito = 0,
+		$_rejected_categories_article = 0,
+		$_saved_articles = 0,
+		$_saved_domains = 0,
+		$_rejected_articles,
+		$_saved_categories_article = 0,
+		$_saved_categories_sito = 0,
+		$_output_activated = false;
+
+
+	public static function getInstance() {
+				if (null === self::$_instance)
+			self::$_instance = new self();
+
+		return self::$_instance;
+	}
+
+
+	public function activateOutput() {
+		$this->_output_activated = true;
+		return $this;
+	}
+
+
+	public function getLogs() {
+		return $this->_logs;
+	}
+
+	public function addUnknownUrl($element, $url, $uid) {
+		$this->addLogRow(get_class($element).' id:'.$element->getId().' , typo3 uid: '.$uid.', unknown URL: '.$url);
+	}
+
+
+	public function addLogRow($log) {
+		$this->_logs .= $log . "\n";
+		if ($this->_output_activated) {
+			echo $log."\n";
+		}
+		return $this;
+	}
+
+	public function cleans() {
+		$this->_logs = '';
+		$this->_rejected_users = 0;
+		$this->_t3_users = 0;
+		$this->_saved_users = 0;
+		$this->_updated_users = 0;
+		$this->_saved_categories_sito = 0;
+		$this->_saved_categories_article = 0;
+		$this->_rejected_categories_sito = 0;
+		$this->_rejected_categories_article = 0;
+		$this->_saved_articles = 0;
+		$this->_saved_domains = 0;
+		$this->_rejected_articles = 0;
+		return $this;
+	}
+
+
+	public function addLogs($logs) {
+		foreach($logs as $log)
+			self::getInstance()->addLogRow($log);
+		return $this;
+	}
+
+
+	public function getErrors() {
+		return $this->_errors;
+	}
+
+
+	public function addErrorRow($error) {
+		$this->_errors .= $error . '\n';
+
+		if ($this->_output_activated) {
+			echo "[ERROR]  $error\n";
+		}
+
+		return $this;
+	}
+
+
+	public function incrementRejected($user, $data) {
+		$this->_rejected_users++;
+		return $this->addErrorRow('user with uid ' . $data['uid'] . ', (' . $data['username'] . ' ' . $data['realName'] . ' ) ' . 'can\'t be saved: ' . implode(', ', $user->getErrors()));
+	}
+
+
+	public function incrementUpdated() {
+		$this->_updated_users++;
+		return $this;
+	}
+
+
+	public function incrementSaved() {
+		$this->_saved_users++;
+		return $this;
+	}
+
+
+	public function incrementT3Users() {
+		$this->_t3_users++;
+		return $this;
+	}
+
+
+	public function report() {
+		return $this->getLogs() .
+			$this->getErrors();
+	}
+
+
+	public function setUsersLog() {
+		return $this->addLogRow('User(s) found: ' . $this->_t3_users)
+								->addLogRow('User(s) rejected: ' . $this->_rejected_users)
+								->addLogRow('User(s) added: ' . $this->_saved_users)
+								->addLogRow('User(s) updated: ' . $this->_updated_users);
+	}
+
+
+	public function setCategoriesLog($number) {
+		return $this->addLogRow('Typo3 categories found: ' . $number)
+								->addLogRow('Typo3 Sito categories rejected: ' . $this->_rejected_categories_sito)
+								->addLogRow('Typo3 Article categories rejected: ' . $this->_rejected_categories_article)
+								->addLogRow('Typo3 Article categories saved: ' . $this->_saved_categories_article)
+								->addLogRow('Typo3 Sito categories saved: ' . $this->_saved_categories_sito)
+								->addLogRow('Typo3 Domains saved: ' . $this->_saved_domains);
+	}
+
+
+	public function setArticlesLog() {
+		return $this->addLogRow('Typo3 Articles saved: ' . $this->_saved_articles)
+								->addLogRow('Typo3 Articles rejected: ' . $this->_rejected_articles);
+	}
+
+
+	public function incrementSitoCategoriesSaved() {
+		 $this->_saved_categories_sito++;
+		 return $this;
+	}
+
+
+	public function incrementArticleCategoriesSaved() {
+		 $this->_saved_categories_article++;
+		 return $this;
+	}
+
+
+	public function incrementSitoCategoriesRejected($category, $title, $t3uid, $t3pid) {
+		$this->_rejected_categories_sito++;
+		return $this->addErrorRow('Category Sito' . $this->incrementDefaultRejectedMessage($category, [$title, $t3uid, $t3pid]));
+	}
+
+
+	public function incrementArticleCategoriesRejected($category, $title, $t3uid, $t3pid) {
+		$this->_rejected_categories_article++;
+		return $this->addErrorRow('Category Article' . $this->incrementDefaultRejectedMessage($category, [$title, $t3uid, $t3pid]));
+	}
+
+
+	protected function incrementDefaultRejectedMessage($model, $options = []) {
+		return implode(' ', $options) . ' can not be saved: ' . implode(', ', $model->getErrors());
+	}
+
+
+	public function incrementDomainsRejected($catalogue) {
+		$this->_rejected_domains++;
+		return $this->addErrorRow('Domain' . $this->incrementDefaultRejectedMessage($catalogue, [$catalogue->getLibelle]));
+	}
+
+
+	public function incrementDomainsSaved() {
+		 $this->_saved_domains++;
+		 return $this;
+	}
+
+
+	public function incrementArticlesSaved() {
+		 $this->_saved_articles++;
+		 return $this;
+	}
+
+
+	public function incrementArticleRejected($article, $options) {
+		$this->_rejected_articles++;
+		return $this->addErrorRow('Article ' . $this->incrementDefaultRejectedMessage($article, $options));
+	}
+
+}
+?>
\ No newline at end of file
diff --git a/library/Class/Sitotheque.php b/library/Class/Sitotheque.php
index 7f0228df98ea97d6ef70b1a5986f84c4c7ddf7df..2e55c2d62ae45d2ef3a9663fb468ac9ff84a4bc1 100644
--- a/library/Class/Sitotheque.php
+++ b/library/Class/Sitotheque.php
@@ -16,7 +16,7 @@
  *
  * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
  * along with AFI-OPAC 2.0; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA 
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 require_once dirname(__FILE__)."/CompositeBuilder.php";
 
@@ -177,9 +177,9 @@ class Class_Sitotheque extends Storm_Model_Abstract {
 	// Renvoie tous les articles pour une categorie et ses sous-categories
 	//------------------------------------------------------------------------------------------------------
 	public function getSitesCategorie($id_categorie) {
-		if(!$id_categorie) return array();		
+		if(!$id_categorie) return array();
+
 
-		
 		$categories = Class_SitothequeCategorie::find($id_categorie)->getRecursiveSousCategories();
 
 		$ids = array_map(function($categorie) {return $categorie->getId();},
@@ -210,18 +210,6 @@ class Class_Sitotheque extends Storm_Model_Abstract {
 	}
 
 
-	public function getLastSito($nb)  {
-		$sql = Zend_Registry::get('sql');
-		try{
-			$fetch = $sql->fetchAll("select * from sito_url order by DATE_MAJ DESC LIMIT $nb");
-			return $fetch;
-		}catch (Exception $e){
-			logErrorMessage('Class: Class_CMS; Function: getLastArticles' . NL . $e->getMessage());
-			return $this->_dataBaseError;
-		}
-	}
-
-
 	public function validate()	{
 		$this->check($this->getTitre(), $this->_("Vous devez compléter le champ 'Titre'"));
 
@@ -236,7 +224,7 @@ class Class_Sitotheque extends Storm_Model_Abstract {
 
 		//	$this->check(strlen_utf8($this->getDescription()) <= 250,
 		//						 $this->_("Le champ 'Commentaire' doit être inférieur à 250 caractères"));
-		
+
 		$url_validator = new ZendAfi_Validate_Url();
 		if (!$url_validator->isValid($this->getUrl())) {
 			$messages = $url_validator->getMessages();
@@ -254,12 +242,12 @@ class Class_Sitotheque extends Storm_Model_Abstract {
 	public function getIcoInfoTitle() {
 		return '';
 	}
-	
+
 
 	public function getIcoInfo() {
 			return 'ico/liste.gif';
 	}
-	
+
 
 }
 ?>
\ No newline at end of file
diff --git a/library/Class/Systeme/Sql.php b/library/Class/Systeme/Sql.php
index 82e5b92dd4b699c2a43fd22035f6c58c0fcc6c10..cdf2f49566370ad0588060eceb925638d78b7122 100644
--- a/library/Class/Systeme/Sql.php
+++ b/library/Class/Systeme/Sql.php
@@ -25,7 +25,7 @@ class Class_Systeme_Sql {
 	private $filtre_write;											// Filtre pour les ecritures
 
 
-	public function __construct($server,$user,$pwd,$base) {
+	public function __construct() {
 		$this->filtre_write = new ZendAfi_Filters_WriteSql();
 	}
 
diff --git a/library/Trait/HasManyDomaines.php b/library/Trait/HasManyDomaines.php
index 8939fc89fb30956c960ebeee6b3467b8ef02bbbe..8b8b0f57b75befb19d0c658849a55bd27133b901 100644
--- a/library/Trait/HasManyDomaines.php
+++ b/library/Trait/HasManyDomaines.php
@@ -16,14 +16,14 @@
  *
  * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
  * along with AFI-OPAC 2.0; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA 
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 
 
 trait Trait_HasManyDomaines {
 	public function setDomaineIds($domaines) {
 		if (is_array($domaines))
-			$domaines=implode(';',$domaines);
+			$domaines=implode(';', array_unique($domaines));
 		$domaines=str_replace('-',';',$domaines);
 		return $this->_set('domaine_ids',$domaines);
 	}
@@ -36,15 +36,21 @@ trait Trait_HasManyDomaines {
 
 
 	public function getDomaines() {
-		$ids =  getDomaineIdsAsArray();
+		$ids = $this->getDomaineIdsAsArray();
 		$domaines = [];
 		foreach ($ids as $id) {
-			$domaines[] = Class_Catalogue::findById($id);
+			$domaines[] = Class_Catalogue::find($id);
 		}
 		return $domaines;
 	}
 
 
+	public function getDomaineLibelles() {
+		return array_map(function($model) {return $model->getLibelle();},
+										 $this->getDomaines());
+	}
+
+
 	public function setDomaines($domaines) {
 		$ids = [];
 		foreach($domaines as $domaine)
diff --git a/library/Trait/TreeNode.php b/library/Trait/TreeNode.php
index da19ee3d40fb2a7f5ac6629c6626466f56283eb3..abc6b1cae2955c59e4b30f75d79e7e23f1264253 100644
--- a/library/Trait/TreeNode.php
+++ b/library/Trait/TreeNode.php
@@ -16,7 +16,7 @@
  *
  * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
  * along with AFI-OPAC 2.0; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA 
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 
 trait Trait_TreeNode {
@@ -55,20 +55,20 @@ trait Trait_TreeNode {
 
 
 	public function getParentsAndSelf() {
-		return ($parent = $this->getParent()) 
-			? array_merge($parent->getParentsAndSelf(), [$this]) 
-			: [$this];		
+		return ($parent = $this->getParent())
+			? array_merge($parent->getParentsAndSelf(), [$this])
+			: [$this];
 	}
 
 
 	public function getParents() {
-		return ($parent = $this->getParent()) 
-			? $parent->getParentsAndSelf() 
+		return ($parent = $this->getParent())
+			? $parent->getParentsAndSelf()
 			: [];
 	}
 
 
-	abstract public function getParent(); 
+	abstract public function getParent();
 
 	abstract public function getChildren();
 
diff --git a/library/ZendAfi/Validate/Url.php b/library/ZendAfi/Validate/Url.php
index 402c1ee97198f8d77980267ad75c8ccf0a44f0b7..abf30630193c3d15969429ae1e79a74d981925f8 100644
--- a/library/ZendAfi/Validate/Url.php
+++ b/library/ZendAfi/Validate/Url.php
@@ -16,20 +16,21 @@
  *
  * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
  * along with AFI-OPAC 2.0; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA 
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 class ZendAfi_Validate_Url extends Zend_Validate_Abstract {
 	const INVALID_URL = 'invalidUrl';
-	
+
 	protected $_messageTemplates = array(self::INVALID_URL   => "'%value%' n'est pas une URL valide");
-	
+
 	public function isValid($value)	{
 		if ('' === $valueString = (string) $value)
 			return true;
+		if (false == strpos($valueString,'://'))
+			$valueString='http:/'.$valueString;
+		$this->_setValue($value);
 
-		$this->_setValue($valueString);
-		
-		if (!Zend_Uri::check($value)) {
+		if (!Zend_Uri::check($valueString)) {
 			$this->_error(self::INVALID_URL);
 			return false;
 		}
diff --git a/library/ZendAfi/View/Helper/WebThumbnail.php b/library/ZendAfi/View/Helper/WebThumbnail.php
index d985599dea0949bdb3b61f589c260c474ba0b208..6713b5c8d531db1d6fdb4e5dc90d770f30817aca 100644
--- a/library/ZendAfi/View/Helper/WebThumbnail.php
+++ b/library/ZendAfi/View/Helper/WebThumbnail.php
@@ -36,8 +36,7 @@ class ZendAfi_View_Helper_WebThumbnail extends ZendAfi_View_Helper_BaseHelper {
 		if (!$this->getFileWriter()->fileExists($filepath)) {
 			return Class_AdminVar::isBlugaEnabled()
 				? $this->view->absoluteUrl(['controller' => 'sito',
-																		'action' => 'webthumbnail',
-																		'url' =>$url], null, true)
+																		'action' => 'webthumbnail'],null,true) . '?url=' . urlencode($url)
 				: '';
 		}
 
diff --git a/library/startup.php b/library/startup.php
index ef045f3a0e82ce9c037bb47fa56f4c6dde83c0eb..4072ec0e99ba5c4d8eaa274292893a71f8db61bb 100644
--- a/library/startup.php
+++ b/library/startup.php
@@ -194,14 +194,9 @@ function setupDatabase($cfg) {
 	$sql->usePrepared(false);
 	Zend_Db_Table::setDefaultAdapter($sql);
 
-	$afi_sql = new Class_Systeme_Sql(
-																	 $cfg->sgbd->config->host,
-																	 $cfg->sgbd->config->username,
-																	 $cfg->sgbd->config->password,
-																	 $cfg->sgbd->config->dbname);
+	$afi_sql = new Class_Systeme_Sql();
 	Zend_Registry::set('sql', $afi_sql);
 
-
 	Zend_Db_Table::getDefaultAdapter()->query('set names "UTF8"');
 }
 
diff --git a/scripts/import_typo3.php b/scripts/import_typo3.php
index 913c90505cbb0b9dda93bab5302e5bf39010ae97..13c0e102fae4c412b47f7b0f7b266e48766d25c9 100644
--- a/scripts/import_typo3.php
+++ b/scripts/import_typo3.php
@@ -11,415 +11,8 @@ require('console.php');
 
 $toRun = $argv[1];
 
-$migration = new Typo2Bokeh();
-$migration->run($toRun);
-
-
-
-abstract class CategoriesMap {
-  protected
-    $map = [],
-    $default_cat,
-    $domaine_map;
-
-  public function __construct($default_cat_title, $domaine_map) {
-    $this->domaine_map = $domaine_map;
-    $this->default_cat = $this->_buildNewCategory($default_cat_title);
-    $this->default_cat->assertSave();
-  }
-
-  public function getParentCatId($t3id) {
-    if (isset($this->map[$t3id])) {
-      $cat = $this->map[$t3id];
-      return $cat->getId();
-    }
-    return  0;
-  }
-
-  public function newCategory($title, $t3uid, $t3pid) {
-    $category = $this->_buildNewCategory($title, $this->getParentCatId($t3pid));
-    $category->assertSave();
-
-    $this->domaine_map->findOrCreateDomaine($category);
-
-    return $this->map[$t3uid] = $category;
-  }
-
-  public function find($t3id) {
-    return  isset($this->map[$t3id])
-      ? $this->map[$t3id]
-      : $this->default_cat;
-  }
-}
-
-
-
-class DomaineMap {
-  protected $map = [];
-
-  public function findOrCreateDomaine($category) {
-    if (!$category)
-      return null;
-
-    $path = $category->getPath();
-    if (isset($this->map[$path]))
-      return $this->map[$path];
-
-    $parent_domaine = $this->findOrCreateDomaine($category->getParentCategorie());
-    $catalogue = Class_Catalogue::newInstance(['libelle' => $category->getLibelle(),
-                                               'parent_id' => $parent_domaine ? $parent_domaine->getId() : 0]);
-    $catalogue->save();
-    return $this->map[$path] = $catalogue;
-  }
-}
-
-
-class ArticleCategoriesMap  extends CategoriesMap {
-  protected function _buildNewCategory($libelle, $parent_id=0) {
-    $cat =  Class_ArticleCategorie::newInstance(['libelle' => $libelle,
-                                                 'id_cat_mere' => $parent_id]);
-    /* if ($double_cat = $this->findByPath($cat->getPath)) */
-    /*  return $double_cat; */
-    /* $this->path_map [] = $cat; */
-    return $cat;
-  }
-}
-
-
-class SiteCategoriesMap  extends CategoriesMap {
-  protected function _buildNewCategory($libelle, $parent_id=0) {
-    return Class_SitothequeCategorie::newInstance(['libelle' => $libelle,
-                                                   'id_cat_mere' => $parent_id]);
-  }
-}
-
-
-class CalendarCategoriesMap  extends ArticleCategoriesMap {
-  public function getParentCatId($t3id) {
-    return $this->find($t3id)->getId();
-  }
-}
-
-
-class Typo2Bokeh {
-  const DEFAULT_CAT_ID = 30000;
-
-
-  public function __construct() {
-    $this->t3db = new Class_Systeme_Sql('localhost', 'root', 'root', 'miop_typo3');
-    $this->report = ['user_saved' => 0,
-                     'categories_created' => 0,
-                     'domaine_created' => 0,
-                     'categories_sites_created' => 0,
-                     'articles_created' => 0,
-                     'articles_errors' => 0,
-                     'pages_created' => 0,
-                     'pages_errors' => 0,
-                     'calendar_created' => 0,
-                     'calendar_errors' => 0,
-                     'articles_pages_errors' => 0,
-                     'articles_pages_created' => 0,
-                     'sitotheque_created' => 0,
-                     'sitotheque_errors' => 0,
-    ];
-    $this->errors = [];
-    $this->domaine_map = new DomaineMap();
-  }
-
-
-  public function run($what = 'all') {
-    if ($what == 'all' || $what == 'users') {
-      echo "Importing users\n";
-      $this->import_user();
-    }
-
-    if ($what == 'all' || $what == 'articles') {
-      echo "Importing categories\n";
-      $this->import_categories();
-
-      echo "Importing articles\n";
-      $this->import_articles();
-
-      echo "Importing articles pages\n";
-      $this->import_articles_pages();
-
-      echo "Importing events\n";
-      $this->import_calendar();
-
-      echo "Importing sites\n";
-      $this->import_sites();
-    }
-
-    print_r($this->report);
-    print_r($this->errors);
-  }
-
-
-  private function import_user() {
-    Class_Users::deleteBy(['where' => 'ROLE_LEVEL < ' . ZendAfi_Acl_AdminControllerRoles::SUPER_ADMIN]);
-    $t3users = $this->t3db->fetchAll('select * from be_users where uid > 1');
-
-    foreach($t3users as $t3user) {
-      $user = Class_Users::newInstance(['id_user' => $t3user['uid'],
-                                        'nom' => $t3user['realName'],
-                                        'mail' => $t3user['email'],
-                                        'login' => $t3user['username'],
-                                        'password' => $t3user['password']]);
-
-      if ($t3user['admin'])
-        $user->beAdminPortail();
-
-      if ($user->save())
-        $this->report['user_saved']++;
-
-
-      // Questions:
-      // Pour les noms et prenoms, dans typo3, il sont dans la colonne realName, dans bokeh, ils sont dans nom ET prenom.
-
-    }
-  }
-
-
-  private function import_categories() {
-    Class_Article::deleteBy([]);
-    Class_ArticleCategorie::deleteBy([]);
-    Class_Catalogue::deleteBy([]);
-    Class_CodifThesaurus::deleteBy([]);
-    Class_Sitotheque::deleteBy([]);
-    Class_SitothequeCategorie::deleteBy([]);
-    Class_Catalogue::deleteBy([]);
-    $this->reset_auto_increment();
-
-    $this->cal_categories_map= new CalendarCategoriesMap('Agenda', $this->domaine_map);
-    $this->news_categories_map = new ArticleCategoriesMap('Non classé', $this->domaine_map);
-    $this->sites_categories_map = new SiteCategoriesMap('Non classé', $this->domaine_map);
+Class_Import_Typo3_Logs::getInstance()->activateOutput();
 
-    $t3categories = $this->t3db->fetchAll('select * from tt_news_cat where deleted=0 order by uid ASC');
-
-    foreach($t3categories as $t3cat) {
-      $this->news_categories_map->newCategory($t3cat['title'], $t3cat['uid'], $t3cat['parent_category']);
-      $this->sites_categories_map->newCategory($t3cat['title'], $t3cat['uid'], $t3cat['parent_category']);
-    }
-
-    // Categories d'évènements.
-    $cal_categories = $this->t3db->fetchAll("select * from tx_cal_category where deleted=0 order by uid");
-    foreach($cal_categories as $row) {
-      $this->cal_categories_map->newCategory($row['title'], $row['uid'], $row['parent_category']);
-    }
-  }
-
-
-  private function import_sites() {
-    $t3news = $this->t3db->fetchAll("select * from tt_news where deleted=0 and ext_url>'' order by uid ASC");
-
-    foreach($t3news as $new) {
-      $date_creation = date("Y-m-d H:i:s", $new['crdate']);
-      $element = Class_Sitotheque::newInstance(['id_cat' => $this->sites_categories_map->find($new['category'])->getId(),
-                                                'date_maj' => $date_creation,
-                                                'titre' => $new['title'],
-                                                'url' => $this->format_url($new['ext_url']),
-                                                'tags' => str_replace(', ', ';', $new['tx_danpextendnews_tags']),
-                                                'description' => $new['short']]);
-
-      if (!$element->save()) {
-        $this->report['sitotheque_errors']++;
-        $this->errors[] =  "site: " . $element->getTitre() . " (" . $element->getId() . ") - " . implode(", ", $element->getErrors());
-        continue;
-      }
-
-      $t3_news_domains = $this->t3db->fetchAll("select distinct uid_foreign from tt_news_cat_mm where uid_local=". $new['uid'] ." order by sorting");
-      $domains = [];
-      foreach ($t3_news_domains as $t3_domain) {
-        if (!$category = $this->news_categories_map->find($t3_domain['uid_foreign']))
-          $category = $this->sites_categories_map->find($t3_domain['uid_foreign']);
-        $domains[] = $this->domaine_map->findOrCreateDomaine($category);
-      }
-      $element->setDomaines($domains)->save();
-    }
-  }
-
-
-  private function import_articles() {
-		$_SERVER['HTTP_HOST']='localhost';
-
-    $t3news = $this->t3db->fetchAll("select * from tt_news where deleted=0 and ext_url='' order by uid ASC");
-    foreach($t3news as $new) {
-      $date_creation = date("Y-m-d H:i:s", $new['crdate']);
-      $debut = $new['starttime'] ? date("Y-m-d", $new['starttime']) : '';
-      $fin = $new['endtime'] ? date("Y-m-d", $new['endtime']) : '';
-
-      $element = Class_Article::newInstance(['date_creation' => $date_creation,
-                                             'debut' => $debut,
-                                             'fin' => $new['hidden'] ? null : $fin,
-                                             'id_user' => $new['cruser_id'],
-                                             'description' => $new['short'],
-                                             'contenu' => $new['bodytext'] ? $new['bodytext'] :'&nbsp;',
-                                             'id_cat' => $this->news_categories_map->find($new['category'])->getId(),
-                                             'status' => Class_Article::STATUS_VALIDATED,
-                                             'tags' => str_replace(', ', ';', $new['tx_danpextendnews_tags']),
-                                             'titre' => $new['title']]);
-      if (!$element->save()) {
-        $this->report['articles_errors']++;
-        $this->errors[] =  "article: " . $element->getTitre() . " (" . $element->getId() . ") - " . implode(", ", $element->getErrors());
-        continue;
-      }
-
-      $t3_news_domains = $this->t3db->fetchAll("select distinct uid_foreign from tt_news_cat_mm where uid_local=". $new['uid'] ." order by sorting");
-      $domains = [];
-      foreach ($t3_news_domains as $t3_domain) {
-        if (!$category = $this->news_categories_map->find($t3_domain['uid_foreign']))
-          $category = $this->sites_categories_map->find($t3_domain['uid_foreign']);
-        $domains[] = $this->domaine_map->findOrCreateDomaine($category);
-      }
-      $element->setDomaines($domains)->save();
-    }
-
-    return $this;
-  }
-
-
-  private function typo_event_to_date($date, $time) {
-    $year = substr($date, 0, 4);
-    $month = substr($date, 4, 2);
-    $day = substr($date, 6, 4);
-
-    $seconds_left = $time % 3600;
-    $hour = (int)(($time - $seconds_left) / 3600);
-    $minutes = (int)($seconds_left / 60);
-
-    if (0 == $hour + $minutes) {
-      $hour = 23;
-      $minutes = 59;
-    }
-
-    return sprintf('%s-%s-%s %s:%s:%s', $year, $month, $day, $hour, $minutes, '00');
-  }
-
-
-  private function import_calendar() {
-
-    $cal_news = $this->t3db->fetchAll("select * from tx_cal_event where deleted=0 order by uid ASC");
-
-    foreach($cal_news as $new) {
-      $date_creation = date("Y-m-d H:i:s", $new['crdate']);
-      $debut = $new['starttime'] ? date("Y-m-d", $new['starttime']) : '';
-      $fin = $new['endtime'] ? date("Y-m-d", $new['endtime']) : '';
-      $event_debut = $this->typo_event_to_date($new['start_date'], $new['start_time']);
-      $event_fin = $this->typo_event_to_date($new['end_date'], $new['end_time']);
-
-      $element = Class_Article::newInstance(['date_creation' => $date_creation,
-                                             'debut' => $debut,
-                                             'fin' => $new['hidden'] ? null : $fin,
-                                             'events_debut' => $event_debut,
-                                             'events_fin' => $event_fin,
-                                             'id_user' => $new['cruser_id'],
-                                             'all_day' => $new['allday'],
-                                             'description' => '',
-                                             'contenu' => $new['description'] ? $new['description'] : $new['title'],
-                                             'id_cat' => $this->cal_categories_map->find($new['category_id'])->getId(),
-                                             'status' => Class_Article::STATUS_VALIDATED,
-                                             'tags' => '',
-                                             'titre' => $new['title']]);
-
-      if (!$element->save()) {
-        $this->report['calendar_errors']++;
-        $this->errors[] =  "calendar: " . $element->getTitre() . " (" . $element->getId() . ") - " . implode(", ", $element->getErrors());
-        continue;
-      }
-
-      $this->report['calendar_created']++;
-
-      $t3_news_domains = $this->t3db->fetchAll("select distinct uid_foreign from tx_cal_event_category_mm where uid_local=". $new['uid'] ." order by sorting");
-      $domains = [];
-      foreach ($t3_news_domains as $t3_domain) {
-        $category = $this->cal_categories_map->find($t3_domain['uid_foreign']);
-        $domains[] = $this->domaine_map->findOrCreateDomaine($category);
-      }
-      $element->setDomaines($domains)->save();
-    }
-
-    return $this;
-  }
-
-
-  private function import_element($element, $type, $uid = null) {
-    if ($element->save()) {
-      if ($uid)
-        $element->setId($uid);
-      $this->update_domains($element);
-      $this->report[$type . '_created']++;
-      return;
-    }
-
-    $this->report[$type . '_errors']++;
-    $this->errors[] =  $type . ": " . $element->getTitre() . " (" . $element->getId() . ") - " . implode(", ", $element->getErrors());
-  }
-
-
-  private function import_articles_pages() {
-    $rows = $this->t3db->fetchAll("select * from tt_content where deleted=0 and header>'' and bodytext>'' and  (ctype='text' or ctype='textpic') order by uid ASC");
-
-    $pages_cat = Class_ArticleCategorie::newInstance(['libelle' => 'Pages fixes']);
-    $pages_cat->save();
-    foreach($rows as $new) {
-      $date_creation = date("Y-m-d H:i:s", $new['crdate']);
-      $debut = $new['starttime'] ? date("Y-m-d", $new['starttime']) : '';
-      $fin = $new['endtime'] ? date("Y-m-d", $new['endtime']) : '';
-
-      $element = Class_Article::newInstance(['date_creation' => $date_creation,
-                                             'debut' => $debut,
-                                             'fin' => $new['hidden'] ? null : $fin,
-                                             'id_user' => $new['cruser_id'],
-                                             'description' => '',
-                                             'contenu' => $new['bodytext'] ? $new['bodytext'] :'&nbsp;',
-                                             'id_cat' => $pages_cat->getId(),
-                                             'status' => Class_Article::STATUS_VALIDATED,
-                                             'tags' => '',
-                                             'titre' => $new['header']]);
-
-      if (!$element->save()) {
-        $this->report['pages_errors']++;
-        $this->errors[] =  "pages: " . $element->getTitre() . " (" . $element->getId() . ") - " . implode(", ", $element->getErrors());
-        continue;
-      }
-      $this->report['pages_created']++;
-    }
-    return $this;
-  }
-
-
-
-  public function format_url($url) {
-    if (!$url = explode(' ', $url)[0])
-      return '';
-    $url = preg_replace(['/^http:\/\//', '/^\/\//'], '', trim(strtolower($url)));
-    return (0 !== strpos('http', $url)) ? 'http://'.$url : $url;
-  }
-
-  public function get_all_domains($element) {
-    return $this->t3db->fetchAll("select distinct uid_foreign from tt_news_cat_mm where uid_local=".$element->getId()." order by sorting");
-  }
-
-
-
-  public function update_domains($element) {
-    // uid_foreign == id domaine
-    // uid_local == id news
-    $t3_news_domains = $this->get_all_domains($element);
-    $domains=[];
-    foreach ($t3_news_domains as $t3_domain) {
-      $domains[]= $t3_domain['uid_foreign'];
-    }
-    $element->setDomaineIds($domains);
-
-  }
-
-  public function reset_auto_increment() {
-    Zend_Registry::get('sql')->execute('alter table cms_article AUTO_INCREMENT=1');
-    Zend_Registry::get('sql')->execute('alter table cms_categorie AUTO_INCREMENT=1');
-    Zend_Registry::get('sql')->execute('alter table sito_url AUTO_INCREMENT=1');
-    Zend_Registry::get('sql')->execute('alter table sito_categorie AUTO_INCREMENT=1');
-    Zend_Registry::get('sql')->execute('alter table catalogue AUTO_INCREMENT=1');
-  }
-}
-?>
+$migration = new Class_Import_Typo3();
+$migration->run($toRun);
+?>
\ No newline at end of file
diff --git a/tests/application/modules/opac/controllers/SitoControllerTest.php b/tests/application/modules/opac/controllers/SitoControllerTest.php
index 37c819e79bddfee0f8b804ba61d38fb5c3fd3092..cb271128dd3f6a9647416104438b33d4956cc5b2 100644
--- a/tests/application/modules/opac/controllers/SitoControllerTest.php
+++ b/tests/application/modules/opac/controllers/SitoControllerTest.php
@@ -16,7 +16,7 @@
  *
  * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
  * along with AFI-OPAC 2.0; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA 
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 abstract class SitoControllerTestCase extends AbstractControllerTestCase {
 	public function setUp() {
@@ -58,6 +58,22 @@ abstract class SitoControllerTestCase extends AbstractControllerTestCase {
 
 
 
+class SitoControllerViewRecentTest extends SitoControllerTestCase {
+	public function setUp() {
+		parent::setUp();
+
+		$this->dispatch('/sito/viewrecent/nb/2', true);
+	}
+
+
+	/** @test */
+	public function linuxFrShouldBeVisible() {
+		$this->assertXPath('//div[@class="contenu"]//div[@class="sitotheque"]//a[@href="http://linuxfr.org"]',
+											 $this->_response->getBody());
+	}
+}
+
+
 
 class SitoControllerViewSelectionTest extends SitoControllerTestCase {
 	public function setUp() {
diff --git a/tests/library/Class/CompositeBuilderTreeTest.php b/tests/library/Class/CompositeBuilderTreeTest.php
index 29698c11b106217153ef5ceacdf681755a7ce74d..35eff640c2f5687695e5dac11ec25abf81060f7b 100644
--- a/tests/library/Class/CompositeBuilderTreeTest.php
+++ b/tests/library/Class/CompositeBuilderTreeTest.php
@@ -16,7 +16,7 @@
  *
  * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
  * along with AFI-OPAC 2.0; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA 
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 
 class CompositeBuilderTreeTest extends CompositeBuilderTreeTestCase {
@@ -25,7 +25,7 @@ class CompositeBuilderTreeTest extends CompositeBuilderTreeTestCase {
 	}
 
 	public function testFeteInternetIsInEvenements() {
-		$this->assertTrue(in_array($this->fete_internet, 
+		$this->assertTrue(in_array($this->fete_internet,
 															 $this->evenements->getItems()));
 	}
 
@@ -33,7 +33,7 @@ class CompositeBuilderTreeTest extends CompositeBuilderTreeTestCase {
 		$this->assertEquals(4, $this->visite_bib->getId());
 	}
 
-	public function testAnimationSubcategories() {		
+	public function testAnimationSubcategories() {
 		$this->assertEquals(array($this->animation_jeunesse, $this->animation_adulte),
 												$this->animation->getCategories());
 	}
@@ -77,7 +77,4 @@ class ItemVisitorTest extends CompositeBuilderTreeTestCase {
 		$this->root->acceptVisitor($this);
 		$this->assertEquals($this->expected_visit, $this->actual_visit);
 	}
-}
-
-
-?>
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/tests/library/Class/CompositeBuilderTreeTestCase.php b/tests/library/Class/CompositeBuilderTreeTestCase.php
index 2540350a49296cccb873ed1332d7a7898b144934..97216c0f1d8e5076f76778ed42c3fdc1527d45d5 100644
--- a/tests/library/Class/CompositeBuilderTreeTestCase.php
+++ b/tests/library/Class/CompositeBuilderTreeTestCase.php
@@ -16,7 +16,7 @@
  *
  * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
  * along with AFI-OPAC 2.0; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA 
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 abstract class CompositeBuilderTreeTestCase extends PHPUnit_Framework_TestCase {
 	public function setUp() {
@@ -34,11 +34,18 @@ abstract class CompositeBuilderTreeTestCase extends PHPUnit_Framework_TestCase {
 			      - fete internet 5
 		 */
 		$builder = new CompositeBuilder("ItemCategory", "BaseItem");
-		$this->root = $builder->getRoot();
-		$this->animation = $builder->newSubcategoryIn(1, 0);
-		$this->animation_jeunesse = $builder->newSubcategoryIn(11, 1);
-		$this->animation_adulte = $builder->newSubcategoryIn(12, 1);
-		$this->evenements = $builder->newSubCategoryIn(2, 0);
+		$this->root = $builder
+			->getRoot()
+			->addCategoriesFromRows([$this->def(1, 0),
+															 $this->def(11, 1),
+															 $this->def(12, 1),
+															 $this->def(2, 0)],
+															'id', 'pid');
+
+		$this->animation = $this->root->getCategoryWithId(1);
+		$this->animation_jeunesse = $this->animation->getCategoryWithId(11);
+		$this->animation_adulte = $this->animation->getCategoryWithId(12);
+		$this->evenements = $this->root->getCategoryWithId(2);
 
 		$this->rencontre_pere_noel = $builder->newItemIn(2, 11);
 		$this->carnaval = $builder->newItemIn(3, 11);
@@ -48,5 +55,10 @@ abstract class CompositeBuilderTreeTestCase extends PHPUnit_Framework_TestCase {
 		$this->categorie_fantome = $builder->newSubcategoryIn(99, 99);
 		$this->article_fantome = $builder->newItemIn(99, 99);
 	}
+
+
+	protected function def($id, $pid) {
+		return ['id' => $id, 'pid' => $pid];
+	}
 }
 ?>
\ No newline at end of file
diff --git a/tests/library/Class/GlobalSqlRefactoringTest.php b/tests/library/Class/GlobalSqlRefactoringTest.php
index 459f6dc38f629d9c8d08c81674c8d50c35e76fa6..43ad4640eff427329770a1404a8bbe4e99bb14a8 100644
--- a/tests/library/Class/GlobalSqlRefactoringTest.php
+++ b/tests/library/Class/GlobalSqlRefactoringTest.php
@@ -27,8 +27,7 @@ class GlobalSqlRefactoringTest extends Storm_Test_ModelTestCase {
 		$this->_old_sql = Zend_Registry::get('sql');
 
 		$this->mock_adapter = $this->mock()->beStrict();
-		$this->mock_sql = (new Class_Systeme_Sql('', '', '', ''))
-			->setAdapter($this->mock_adapter);
+		$this->mock_sql = (new Class_Systeme_Sql())->setAdapter($this->mock_adapter);
 		Zend_Registry::set('sql', $this->mock_sql);
 	}
 
@@ -99,7 +98,6 @@ class GlobalSqlRefactoringTest extends Storm_Test_ModelTestCase {
 	}
 
 
-	/** @test */
 	public function sitoGetLastSito() {
 		$this
 			->prepare('select * from sito_url order by DATE_MAJ DESC LIMIT 7',
diff --git a/tests/library/Class/Import/Typo3Fixture.php b/tests/library/Class/Import/Typo3Fixture.php
new file mode 100644
index 0000000000000000000000000000000000000000..6e4fe917e92eb8e3c57cb88339c3232d82e094c5
--- /dev/null
+++ b/tests/library/Class/Import/Typo3Fixture.php
@@ -0,0 +1,400 @@
+<?php
+/**
+ * Copyright (c) 2012-2014, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * AFI-OPAC 2.0 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.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * AFI-OPAC 2.0 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 AFI-OPAC 2.0; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+class MockTypo3DB {
+
+	public function findAllUsers() {
+		return [ ['uid' => 20,
+							'username' => 'toto',
+							'realName' => 'toto',
+							'email' => 'toto@null.com',
+							'admin' => 1
+			],
+						['uid' => 21,
+						 'username' => 'jean',
+						 'realName' => 'Jean Jean',
+						 'email' => 'jean-jeantoto@null.com',
+						 'admin' => 0
+						],
+						['uid' => 43,
+						 'username' => 'sserge',
+						 'realName' => 'Serge Serge',
+						 'email' => 'serge@null.com',
+						 'admin' => 0
+						],
+						['uid' => 123456789,
+						 'username' => null,
+						 'realName' => 'Tom',
+						 'email' => null,
+						 'admin' => 0
+						]
+		];
+	}
+
+	public function findAllNewsCat() {
+		return [
+						['title' => 'My category',
+						 'uid' => 1,
+						 'parent_category' => 0
+						],
+						['title' => 'Société & Civilisation',
+						 'uid' => 10,
+						 'parent_category' => 0
+						],
+						['title' => 'Musique & Cinéma',
+						 'uid' => 80,
+						 'parent_category' => 99
+						],
+						['title' => 'Divers',
+						 'uid' => 81,
+						 'parent_category' => 99
+						],
+						['title' => 'Autres',
+						 'uid' => 82,
+						 'parent_category' => 99
+						],
+						['title' => null,
+						 'uid' => 85,
+						 'parent_category' => 99
+						],
+						['title' => 'Art',
+						 'uid' => 99,
+						 'parent_category' => 0
+						],
+						['title' => 'NOS TOPS 5',
+						 'uid' => 43,
+						 'parent_category' => 0
+						]
+		];
+	}
+
+	public function findAllEventCat() {
+		return [
+						['uid' => 6,
+						 'title' => 'Atelier',
+						 'parent_category' => 0
+						],
+						['uid' => 24,
+						 'title' => 'Lecture',
+						 'parent_category' => 6
+						],
+						['uid' => 26,
+						 'title' => 'Pont',
+						 'parent_category' => 0
+						],
+		];
+	}
+
+	public function findAllSites() {
+		return [
+						['crdate' => '1412781027',
+						 'category' => 1,
+						 'title' => 'MuséoParc Alésia',
+						 'ext_url' => 'http://www.alesia.com/',
+						 'tx_danpextendnews_tags' => 'Alésia, Jules César, Vercingétorix, Gaule, armée, bataille',
+						 'short' => null,
+						 'uid' => 14478,
+						 'pid' => 49],
+
+						['crdate' => '1412769359',
+						 'category' => 1,
+						 'title' => 'L\'ouest canadien',
+						 'ext_url' => 'http://koha.mediathequeouestprovence.fr/cgi-bin/koha/opac-detail.pl?biblionumber=268360',
+						 'tx_danpextendnews_tags' => 'Canada',
+						 'short' => null,
+						 'uid' => 14479,
+						 'pid' => 0],
+						['crdate' => '1412769359',
+						 'category' => 1,
+						 'title' => 'Qui a dit que les pingouins ne savaient pas taper ? ',
+						 'ext_url' => 'http://koha.mediathequeouestprovence.fr/cgi-bin/koha/opac-detail.pl?foo=bar',
+						 'tx_danpextendnews_tags' => 'Canada',
+						 'short' => null,
+						 'uid' => 144333,
+						 'pid' => 0]
+
+		];
+	}
+
+
+	public function findAllArticles() {
+		return [ [
+							'uid' => 14474,
+							'crdate' => 141277461,
+							'cruser_id' => 43,
+							'starttime' => 1415110980,
+							'endtime' => 1422973380,
+							'hidden' => 0,
+							'short' => 'A bord.',
+							'category' => 10,
+							'image' =>  'bateau.jpg',
+							'tx_danpextendnews_tags' => 'pirate, bateau',
+							'title' => 'A bord du bateau pirate',
+							'bodytext' => 'bodytext: <div id="Titre-Conteneur-J-Coeur"><div id="Titre-Libelle-J-Coeur"><p>Titre&nbsp;:sss</p></div>
+<div id="Titre-Content-J-Coeur"><p>A bord du bateau pirate</p></div></div>
+<div id="Auteur-Conteneur-J-Coeur"><div id="Auteur-Libelle-J-Coeur"><p>Auteur&nbsp;: </p></div>
+<div id="Auteur-Content-J-Coeur"><p>Jean-Michel Billioud</p></div></div>
+<div id="Illustrateur-Conteneur-J-Coeur"><div id="Illustrateur-Libelle-J-Coeur"><p>Illustrateur&nbsp;: </p></div>
+<div id="Illustrateur-Content-J-Coeur"><p>Olivier Latyk</p></div></div>
+<div id="Support-Conteneur-J-Coeur"><div id="Support-Libelle-J-Coeur"><p>Support : </p></div>
+<div id="Support-Content-J-Coeur"><p>Livre</p></div></div>
+<div id="Public-Conteneur-J-Coeur"><div id="Public-Libelle-J-Coeur"><p>Public concerné&nbsp;: </p></div>
+<div id="Public-Content-J-Coeur"><p>à partir de 4 ans</p></div></div>
+<div id="Resume-Conteneur-J-Coeur"><div id="Resume-Libelle-J-Coeur"><p>Résumé&nbsp;: </p></div>
+<div id="Resume-Content-J-Coeur"><p>Le bateau pirate <i>Bel Espoir</i> quitte l\'île de la Jamaïque pour sillonner la mer des Caraïbes. Les soixante marins embarqués doivent affronter les éléments. A l\'horizon la voilure du<i> Josépha</i>, un galion espagnol chargé d\'or, est en vue. Le capitaine Rogers et son équipage sont prêts à en découdre afin de s\'emparer du trésor. Mais un navire de guerre apparaît soudain...</p></div></div>
+<link fileadmin/fichiers/fichiers_joints/reglement_interieur/REGLEMENT-INTERIEUR-ANNEXE-LISEUSES.pdf _blank download>liseuses</link>
+<link fileadmin/fichiers/action_culturelle/circuit.pdf _blank download>Circuit</link>
+<div id="Avis-Conteneur-J-Coeur"><div id="Avis-Libelle-J-Coeur"><p>Notre avis : </p></div>
+<link http://www.mediathequeouestprovence.fr/fileadmin/user_upload/jeunesse/formulaire_inscription_atelier_internet_espace_J.pdf _blank external-link-new-window>
+<link http://www.mediathequeouestprovence.fr/fileadmin/fichiers/fichiers_joints/formulaires/memento_communique_sans_danger.pdf _blank external-link-new-window>mémento</link>
+<div id="Avis-Content-J-Coeur"><p>Cet ouvrage nous décrit l\'histoire et la vie des pirates. De la préparation du voyage, à la vie à bord, aux escales, à l\'abordage des navires ennemis, en passant par le partage du butin et enfin à la condamnation des pilleurs. Les volets et les tirettes permettent à ce livre animé d\'être autant ludique qu\'informatif, pour le bonheur des plus jeunes lecteurs. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Monte à bord moussaillon, bienvenue dans la cabine du capitaine Rogers et cap sur les Antilles !</p></div></div>
+<link http://www.mediathequeouestprovence.fr/rem/aerodrome.html _blank external-link-new-window>diaporama des cartes postales anciennes de Miramas</link>.</td></tr><tr><td colspan="2" rowspan="1"><link 2157 _blank external-link-new-window><img style="padding-right: 10px; padding-bottom: 10px; float: left;" src="uploads/RTEmagicC_5_37.jpg.jpg" height="76" width="133" alt="" /></link>
+<div id="OPAC-Conteneur-J-Coeur"><div id="OPAC-Link-J-Coeur"><p><link http://koha.mediathequeouestprovence.fr/cgi-bin/koha/opac-detail.pl?biblionumber=262978 _blank - "Voir la disponibilité dans notre catalogue">Pour emprunter ou réserver le document </link></p></div></div>
+<div id="SB-Conteneur-J-Coeur"><div id="SB-Libelle-J-Coeur"><p>Sites ou blogs :</p></div>
+<ul id="SB-Content-J-Coeur"><li><link http://www.pacmusee.qc.ca/fr/expositions/pirates-ou-corsaires _blank - "Cliquer pour accéder au site ou au blog">Pirates ou corsaires ?</link></li><li><link http://www.pirates-corsaires.com/films.htm _blank - "Cliquer pour accéder au site ou au blog">Les pirates au cinéma</link></li></ul></div>
+<div id="OPAC-Conteneur-Others-J-Coeur"><div id="OPAC-Libelle-Others-J-Coeur"><p>Si vous avez aimé, vous aimerez aussi :</p></div><link typo3temp/pics/9ef98368e3.jpg _blank external-link-new-window>
+<ul id="OPAC-Content-Others-J-Coeur"><li><link http://koha.mediathequeouestprovence.fr/cgi-bin/koha/opac-detail.pl?biblionumber=264321 _blank - "Vérifier la disponibilité dans notre catalogue">La vérité vraie sur les pirates</link> de Alan Snow</li><li><link http://koha.mediathequeouestprovence.fr/cgi-bin/koha/opac-detail.pl?biblionumber=247203 _blank - "Vérifier la disponibilité dans notre catalogue">Des pirates et corsaires pour réfléchir</link> de Isabelle Wlodarczyk</li></ul></div>'
+			],
+						[ 'uid' => 14476,
+						 'starttime' => 0,
+						 'endtime' => 0,
+						 'hidden' => 0,
+						 'crdate' => 1359562198,
+						 'cruser_id' => 22,
+						 'short' => null,
+						 'category' => 1,
+						 'image' => '',
+						 'tx_danpextendnews_tags' => '',
+						 'title' => 'Architecture',
+						 'bodytext' => '<span style="float: left; font-size: 100%"><span style="text-decoration: none"><span style="font-style: normal"><span style="text-decoration: none"><span style="font-weight: normal">L</span></span></span></span></span><span style="text-decoration: none"><span style="font-style: normal"><span style="text-decoration: none"><span style="font-weight: normal">e domaine &quot;</span></span></span></span><span style="text-decoration: none"><span style="font-style: normal">Architecture<strong>&quot;</strong></span></span><strong><span style="text-decoration: none"><span style="font-style: normal"><span style="font-weight: normal"> rassemble</span></span></span></strong><span style="text-decoration: none"><span style="font-style: normal"><span style="font-weight: normal"> la documentation relative à cet art, dans son acception plus contemporaine, soit celui de &quot;diriger la construction, de concevoir les structures, de donner une apparence au final avec des matériaux&quot;. En synthèse, l\'art de bâtir des édifices. </span></span></span>
+La collection<span style="text-decoration: none"><span style="font-style: normal"><span style="font-weight: normal"> &quot;Architecture&quot; s\'articule autour de trois grandes thèmatiques, qui structurent la politique d\'acquisition et le développement des fonds documentaires du réseau :</span></span></span>
+<ul><liz><p> 	les 	généralités et l\'art du paysage</p> 	</li><li><p> 	l\'architecture 	ancienne de l\'antiquité au XIX<sup>ième</sup> siècle</p> 	</li><li><p> 	l\'architecture 	moderne et contemporaine (constructions, architectes célèbres...).</p> </li></ul>
+<ul><p> 	</p></ul>
+ Les généralités et l\'art du paysage représentent un tiers de la collection, et intègrent notamment les contenus relatifs à l\'histoire générale de l\'architecture<em>.</em>
+ L\'architecture ancienne de l\'antiquité au XIX siècle représente 20% de la collection et offre une vision synthétique de l\'histoire de l\'architecture à travers le monde, depuis l\'Antiquité (Egypte ancienne, Rome...), l\'architecture Romane et Gothique, la Renaissance, l\'âge Baroque et l\'architecture classique, jusqu\'à la fin du XIX° siècle.
+ L\'architecture moderne et contemporaine représente près de la moitié de la collection. On y retrouve ses différents mouvements, tels que le modernisme, le post-modernisme, l\'architecture hightech, le Blob architecture, etc.
+<strong><span style="font-style: normal"><span style="font-weight: normal">Chacune des thèmatiques comprend des ouvrages généralistes ainsi que des monographies pouvant être consacrées à un architecte, à un mouvement ou à une réalisation particulière. Une attention est accordée à l\'actualité médiatique et à la question de la construction individuelle la plus contemporaine.</span></span></strong>
+ Le fonds est essentiellement composé de documents de vulgarisation, hormis quelques essais ou documents à caractère techniques, pour autant non destinés aux professionnels et/ou experts de l\'architecture.
+ <strong><span style="font-style: normal"><span style="font-weight: normal">Le support livre est prépondérant, même si les documents audiovisuels et multimédias la complètent avantageusement, en restituant notamment la dimension &quot;spatiale&quot; propre à cet art. Une </span></span></strong><strong><span style="font-style: normal"><span style="font-weight: normal">sélection de sites web,</span></span></strong><strong><span style="font-style: normal"><span style="font-weight: normal"> ainsi que des revues et ressources multimédias en ligne enrichissent la collection du réseau et participent de son actualisation.</span></span></strong>
+ <strong><span style="font-style: normal"><span style="font-weight: normal">Il est à noter que le domaine &quot;architecture&quot;, bien que discipline à part entière, peut entrer en connexion et en résonance avec d\'autres champs disciplinaires et professionnels, tels que ceux de l\'écologie (éco-construction, environnement, jardins), de l\'urbanisme (logement, transport, amènagement urbain), de la géographie ou encore de la sociologie. Ces domaines voisins sont pour la plupart représentés dans les autres départements documentaires du réseau, que sont &quot;Société et Civilisation&quot; et &quot;Science, Sport, Vie pratique&quot;. </span></span></strong>
+<strong><span style="font-style: normal"><span style="font-weight: normal">Afin de suivre l\'actualité et la diversité de la production éditoriale, les commandes de documents sont mensuelles. </span></span></strong>
+ La collection, dans son ensemble, s\'adresse à un large public, désireux de mieux comprendre cet &quot;art de bâtir&quot; qui l\'environne et façonne de plus en plus son rapport à l\'espace, tant public que privé.
+'],
+						[ 'uid' => 14477,
+						 'starttime' => 0,
+						 'endtime' => 0,
+						 'hidden' => 0,
+						 'crdate' => 1359562198,
+						 'cruser_id' => 22,
+						 'short' => null,
+						 'category' => 1,
+						 'image' => '',
+						 'tx_danpextendnews_tags' => '',
+						 'title' => 'Architecture summary',
+						 'bodytext' => '<strong><span style="font-style: normal"><span style="font-weight: normal">Afin de suivre l\'actualité et la diversité de la production éditoriale, les commandes de documents sont mensuelles. </span></span></strong>
+ La collection, dans son ensemble, s\'adresse à un large public, désireux de mieux comprendre cet &quot;art de bâtir&quot; qui l\'environne et façonne de plus en plus son rapport à l\'espace, tant public que privé.
+'],
+						[ 'uid' => 14478,
+						 'starttime' => 0,
+						 'endtime' => 0,
+						 'hidden' => 0,
+						 'image' => '',
+						 'crdate' => 1359562208,
+						 'cruser_id' => 22,
+						 'short' => null,
+						 'category' => 1,
+						 'tx_danpextendnews_tags' => '',
+						 'title' => 'Onyx tower event',
+						 'bodytext' => 'Welcome to Onyx tower !
Prepare to fight !'],
+					[ 'uid' => 14488,
+						 'starttime' => 0,
+						 'endtime' => 0,
+						 'hidden' => 0,
+						 'crdate' => 1357562208,
+						 'cruser_id' => 22,
+						 'image' => '',
+						 'short' => null,
+						 'category' => 1,
+						 'tx_danpextendnews_tags' => '',
+						 'title' => 'Wiki Bokeh',
+						 'bodytext' => 'Welcome to <link http://wiki.bokeh-library-portal.org _blank external-link-new-window>Wiki Bokeh</link>'],
+						[ 'uid' => 14888,
+						 'starttime' => 0,
+						 'endtime' => 0,
+						 'hidden' => 0,
+						 'crdate' => 1357562208,
+						 'cruser_id' => 22,
+						 'short' => null,
+						 'category' => 1,
+						 'image' => '',
+						 'tx_danpextendnews_tags' => '',
+						 'title' => null,
+						 'bodytext' => 'Welcome it is an error'],
+						['uid' => 84888,
+						 'starttime' => 0,
+						 'endtime' => 0,
+						 'hidden' => 0,
+						 'image' => '',
+						 'crdate' => 1357592208,
+						 'cruser_id' => 22,
+						 'short' => null,
+						 'category' => 1,
+						 'tx_danpextendnews_tags' => '',
+						 'title' => 'Realy ? you crash ?',
+						 'bodytext' => '<span style="font-weight: bold;">Titre :</span> No Longer at Ease<br /><span style="font-weight: bold;">Interprètes :</span>&nbsp; NNEKA<br /><span style="font-weight: bold;">Compositeur :</span> NNEKA<br /><span style="font-weight: bold;">Label&nbsp;:</span> Yo Mamma Records<br /><span style="font-weight: bold;">Date&nbsp;:</span> 2008<br /><span style="font-weight: bold;">Genre&nbsp;:</span> Nu Soul<br /><span style="font-weight: bold;">Support :</span> CD<br /><span style="font-weight: bold;">Localisation&nbsp;:</span> Fos-sur-Mer, Miramas <br /><link http://koha.mediathequeouestprovence.fr/cgi-bin/koha/opac-detail.pl?biblionumber=174002 _top external-link-new-window>Lien direct au catalogue</link><br /><br />
+<p style="width: 220px; height: 55px;"><object height="55" width="220"><param name="movie" value="http://www.deezer.com/embedded/small-widget-v2.swf?idSong=607896&colorBackground=0x555552&textColor1=0xFFFFFF&colorVolume=0x99FFFF&autoplay=0"></param><embed src="http://www.deezer.com/embedded/small-widget-v2.swf?idSong=607896&amp;colorBackground=0x555552&amp;textColor1=0xFFFFFF&amp;colorVolume=0x99FFFF&amp;autoplay=0" type="application/x-shockwave-flash" height="55" width="220"></embed></object><br />D&eacute;couvrez <link http://www.deezer.com/fr/nneka.html>Nneka</link>!</p>
+<br /><br /><span style="font-weight: bold;">Notre avis :</span><br /><span style="font-style: italic;">No Longer at Ease</span> est un album qui parle de vérité, de sincérité et de lucidité sur ses propres erreurs. Nneka née au Nigéria, a grandi en Allemagne sur les sons de Fela Kuti, de Bob Marley, de Nas ou encore de Lauryn Hill.<br />Entre Reggae, Soul et électro elle incorpore aussi une pincée de rock à ses productions.<br />Le détonnant <span style="font-style: italic;">Heartbeat</span> en témoigne et délivre un message sur la guerre qui laisse le libre arbitre à chacun. <br />Si vous aimez la voix cassée de Neneh Cherry, l\'énergie de Amy Winehouse et le message universaliste de Bob Marley vous aimerez Nneka !<br />&nbsp;<br /><span style="font-weight: bold;">Si vous avez aimé, vous aimerez aussi :</span><br /><link http://koha.mediathequeouestprovence.fr/cgi-bin/koha/opac-search.pl?q=au:Alicia%20Keys _blank external-link-new-window>Alicia Keys</link> - <link http://koha.mediathequeouestprovence.fr/cgi-bin/koha/opac-search.pl?q=au:Bob%20Marley%20and%20The%20Wailers _blank external-link-new-window>Bob Marley</link> - <link http://koha.mediathequeouestprovence.fr/cgi-bin/koha/opac-search.pl?test=test&q=au:Neneh%20Cherry _blank external-link-new-window>Neneh Cherry</link> - <link http://koha.mediathequeouestprovence.fr/cgi-bin/koha/opac-search.pl?q=Amy%20Winehouse _blank external-link-new-window>Amy Winehouse</link>'],
+						['uid' => 123,
+						 'starttime' => 0,
+						 'endtime' => 0,
+						 'hidden' => 0,
+						 'crdate' => 1357592258,
+						 'cruser_id' => 22,
+						 'short' => null,
+						 'image' => '',
+						 'category' => 1,
+						 'tx_danpextendnews_tags' => '',
+						 'title' => 'Deezer ?',
+						 'bodytext' => '<link http://www.deezer.com/fr/nneka.html>Nneka</link>'],
+						['uid' => 13973,
+						 'starttime' => 0,
+						 'endtime' => 0,
+						 'hidden' => 0,
+						 'crdate' => 1357592258,
+						 'cruser_id' => 22,
+						 'short' => null,
+	 					 'image' => '',
+						 'category' => 1,
+						 'tx_danpextendnews_tags' => '',
+						 'title' => 'L\'artothèque',
+						 'bodytext' => '<p style="color: rgb(0, 0, 0); font-family: Verdana, sans-serif; font-size: 12px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;">L\'artothèque de la Médiathèque Intercommunale propose au public sur les sites du réseau, une collection de 1 825 œuvres d\'art contemporain constituant un large panorama de la création plastique et photographique des quarante dernières années.</p>
+<p style="color: rgb(0, 0, 0); font-family: Verdana, sans-serif; font-size: 12px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;"><link http://koha.mediathequeouestprovence.fr/cgi-bin/koha/opac-search.pl?q=test&idx=kw&idx=kw&idx=kw&limit=mc-itemtype%3AOART&sort_by=relevance&do=Rechercher _blank external-link-new-window>Un catalogue iconographique</link>&nbsp;est spécialement dédié à cette collection; il est également consultable dans tous les pôles&nbsp;<span style="font-style: italic;">Art</span>,<span style="font-style: italic;">&nbsp;</span>Musique, Cinéma&nbsp;du réseau.<br /><br />Outre le prêt d\'œuvres d\'art, l\'artothèque organise aussi des expositions, réalise des animations, accueille des groupes, édite des catalogues..., favorisant ainsi la diffusion et la promotion de l\'art et des artistes d\'aujourd\'hui.</p>']
+];
+	}
+
+	public function findAllForeignUidForNewsCat($uid) {
+		return [
+						['uid_foreign' => 81],
+						['uid_foreign' => 43],
+						['uid_foreign' => 82]
+		];
+	}
+
+	public function findAllForeignUidForCalendarEventCategory($uid) {
+		return [
+						['uid_foreign' => 26],
+		];
+	}
+
+
+	public function findAllCalendarEvents() {
+		return [
+						['uid' => 21,
+						 'crdate' => 1189587056,
+						 'starttime' => 0,
+						 'endtime' => 0,
+						 'start_date' => 20071002,
+						 'end_date' => 20071002,
+						 'start_time' => 66600,
+						 'end_time' => 72000,
+						 'cruser_id' => 20,
+						 'image' => '',
+						 'allday' => 0,
+						 'description' => 'Dans le cadre de la semaine "Découverte du Bridge", le Bridge Club de Miramas propose des initiations et démonstrations gratuites pour les jeunes.',
+						 'title' => 'Découvrir le Bridge en 10 minutes',
+						 'category_id' => 6,
+						 'hidden' => 0],
+						['uid' => 22,
+						 'crdate' => 1189589056,
+						 'starttime' => 0,
+						 'endtime' => 0,
+						 'image' => '',
+						 'start_date' => 20071102,
+						 'end_date' => 20071102,
+						 'start_time' => 66600,
+						 'end_time' => 72000,
+						 'cruser_id' => 20,
+						 'allday' => 0,
+						 'description' => 'Discover museum for free every saturday.
All familly are welcome.',
+						 'title' => 'Discover Museum',
+						 'category_id' => 6,
+						 'hidden' => 0],
+						['uid' => 23,
+						 'crdate' => 1189519056,
+						 'starttime' => 0,
+						 'endtime' => 0,
+						 'image' => '',
+						 'start_date' => 20081102,
+						 'end_date' => 20081102,
+						 'start_time' => 66600,
+						 'end_time' => 72000,
+						 'cruser_id' => 20,
+						 'allday' => 0,
+						 'description' => '',
+						 'title' => 'Discover Space',
+						 'category_id' => 6,
+						 'hidden' => 0],
+						['uid' => 34,
+						 'crdate' => 1251212980,
+						 'starttime' => 0,
+						 'start_time' => 54000,
+						 'endtime' => 0,
+						 'end_time' => 57600,
+						 'image' => '',
+						 'start_date' => 20090916,
+						 'end_date' => 20090916,
+						 'cruser_id' => 20,
+						 'allday' => 0,
+						 'description' => '<span style="font-weight: bold;">FILM</span><br /><span style="font-weight: bold;"><span style="font-style: italic;"></span><span style="font-style: italic;"><link http://koha.mediathequeouestprovence.fr/cgi-bin/koha/opac-detail.pl?biblionumber=153045 _top external-link-new-window>L\'île de Black Mor</link></span><span style="font-style: italic;"></span> réalisé par <link http://koha.mediathequeouestprovence.fr/cgi-bin/koha/opac-search.pl?kw=kw&q=r%C3%A9al.%20Jean-Fran%C3%A7ois%20Laguionie _top external-link-new-window>Jean-François Laguionie</link></span><br /><span style="font-weight: bold;">Pour les enfants à partir de 5 ans</span><br /><span style="font-weight: bold;">Entrée libre</span><br /><span style="font-weight: bold;">15h - Médiathèque de Fos-sur-Mer</span><br /><br />Dans le cadre des Rendez-vous du mercredi.',
+						 'title' => 'Rendez-vous Cinéma',
+						 'category_id' => 6,
+						 'hidden' => 0
+
+						]
+
+		];
+	}
+
+	public function findAllContents() {
+		return [
+						[
+						 'uid' => 32,
+						 'crdate' => 1298390490,
+						 'starttime' => 1298389541,
+						 'endtime' => 0,
+						 'hidden' => 0,
+						 'cruser_id' => 1,
+						 'image' => '',
+						 'title' => '',
+						 'bodytext' => 'Initiées en 2008 par le pôle Langues et Littérature de Miramas, les lectures à voix haute ou Lectures impromptues ont évolué au fil du temps. En 2010, elles ont joué les variations, de lectures surprises solo en lectures thématiques à plusieurs voix en passant par le café-lecture où les bibliothécaires présentent <link http://koha.mediathequeouestprovence.fr/cgi-bin/koha/opac-shelves.pl?viewshelf=1556&sortfield=title%20%3Ccid:part1.08080603.08080008@ouestprovence.fr%3E _blank external-link-new-window>des textes</link> et en lisent parfois des extraits.<br /><br />Autant d\'autres façons de lire, de donner envie de lire, d\'échanger sur ses lectures...que nous poursuivons en 2011.<br /><br />Les bibliothécaires lisent pour vous, adhérents ou non adhérents, amateurs de littérature, jeunes et moins jeunes...Il n\'est pas nécessaire d\'avoir lu les textes, d\'avoir des connaissances particulières...juste le goût des histoires, d\'échanger, l\'envie de partager ses propres lectures et même, pourquoi pas, de lire soi-même...<br /><br />Les bibliothécaires lisent surtout des extraits de romans ou des nouvelles complètes, parfois un conte ou des poèmes, à une ou plusieurs voix. En fin de séance, chacun peut échanger et les textes peuvent être empruntés.<br />Les rendez-vous sont mensuels : un signet aux couleurs des lectures est distribué pour vousaider à mémoriser les dates et lieux de ces instants littéraires.<br /><br />Il n\'est pas besoin de s\'inscrire ; n\'hésitez pas à prendre un moment pour venir écouter, découvrir des auteurs, des histoires variées ; le montage vidéo qui accompagne ce billet est une modeste invitation à ce partage.',
+						 'header' => 'A haute voix... le roman se fait entendre...'
+						]
+		];
+	}
+
+
+	public function findPageTitle($uid) {
+		if ($uid == 49)
+			return '2014';
+		return '';
+	}
+
+}
diff --git a/tests/library/Class/Import/Typo3Test.php b/tests/library/Class/Import/Typo3Test.php
new file mode 100644
index 0000000000000000000000000000000000000000..aaa8b69f7e6028decc576e687c4e9472437d9a4e
--- /dev/null
+++ b/tests/library/Class/Import/Typo3Test.php
@@ -0,0 +1,615 @@
+<?php
+/**
+ * Copyright (c) 2012-2014, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * AFI-OPAC 2.0 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.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * AFI-OPAC 2.0 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 AFI-OPAC 2.0; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+include_once("Typo3Fixture.php");
+
+
+
+abstract class Import_Typo3TestCase extends ModelTestCase {
+	protected $migration;
+
+
+	public function setUp() {
+		parent::setUp();
+		Class_Import_Typo3_Logs::getInstance()->cleans();
+
+    Class_Article::beVolatile();
+    Class_Users::beVolatile();
+    Class_ArticleCategorie::beVolatile();
+    Class_Catalogue::beVolatile();
+    Class_CodifThesaurus::beVolatile();
+    Class_Sitotheque::beVolatile();
+    Class_SitothequeCategorie::beVolatile();
+		$this->mock_sql = Storm_Test_ObjectWrapper::mock();
+		$this->old_sql = Zend_Registry::get('sql');
+		Zend_Registry::set('sql', $this->mock_sql);
+
+		$this->mock_sql->whenCalled('execute')
+									 ->answers(true);
+		$this->migration = new Class_Import_Typo3();
+		$this->migration->setTypo3DB(new MockTypo3DB());
+
+	}
+
+
+	public function tearDown() {
+		Zend_Registry::set('sql', $this->old_sql);
+		parent::tearDown();
+	}
+
+}
+
+
+
+class Import_Typo3UsersTest extends Import_Typo3TestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->migration->run('users');
+	}
+
+
+	/** @test */
+	public function userShouldBeInsertedInDB() {
+		$this->assertEquals('toto', Class_Users::findFirstBy(['login' => 'toto'])->getLogin());
+	}
+
+
+	/** @test */
+	public function totoShouldBeAdmin() {
+		$this->assertTrue(Class_Users::findFirstBy(['login' => 'toto'])->isAdmin());
+	}
+
+
+	/** @test */
+	public function jeanShouldBeModoPortail() {
+		$this->assertEquals(ZendAfi_Acl_AdminControllerRoles::MODO_PORTAIL, Class_Users::findFirstBy(['login' => 'jean'])->getRoleLevel());
+	}
+}
+
+
+
+class Import_Typo3CategoryTest extends Import_Typo3TestCase {
+
+	public function setUp() {
+		parent::setUp();
+		$this->migration->import_categories();
+	}
+
+	/** @test */
+	public function categoryArtShouldBeExistInDB() {
+		$parent = Class_ArticleCategorie::findFirstBy(['libelle' => 'Art']);
+		$this->assertNotNull($parent);
+	}
+
+
+	/** @test */
+	public function sitothequeArtShouldBeExistInDB() {
+		$parent = Class_SitothequeCategorie::findFirstBy(['libelle' => 'Art']);
+		$this->assertNotNull($parent);
+	}
+
+
+	/** @test */
+	public function categoryMusiqueCinemaShouldBeChildOfArt() {
+		$this->assertEquals('Art',Class_ArticleCategorie::findFirstBy(['libelle' => 'Musique & Cinéma'])->getParent()->getLibelle());
+	}
+
+
+	/** @test */
+	public function sitothequeMusiqueCinemaShouldBeChildOfArt() {
+		$this->assertEquals('Art',Class_SitothequeCategorie::findFirstBy(['libelle' => 'Musique & Cinéma'])->getParent()->getLibelle());
+	}
+
+
+	/** @test */
+	public function domaineArtShouldBeExistInDB() {
+		$parent = Class_Catalogue::findFirstBy(['libelle' => 'Art']);
+		$this->assertNotNull($parent);
+	}
+
+
+	/** @test */
+	public function domaineMusiqueCinemaShouldBeChildOfDomaineArt() {
+		$this->assertEquals('Art',Class_Catalogue::findFirstBy(['libelle' => 'Musique & Cinéma'])->getParent()->getLibelle());
+	}
+
+
+	/** @test */
+	public function mainCategoryAgendaShouldExist() {
+		$parent = Class_ArticleCategorie::findFirstBy(['libelle' => 'Agenda']);
+		$this->assertNotNull($parent);
+	}
+
+
+	/** @test */
+	public function atelierAgendaCategoryShouldExist() {
+		$parent = Class_ArticleCategorie::findFirstBy(['libelle' => 'Atelier']);
+		$this->assertNotNull($parent);
+	}
+
+
+	/** @test */
+	public function atelierAgendaCategoryShouldBeChildOfAgenda() {
+		$this->assertEquals('Agenda',Class_ArticleCategorie::findFirstBy(['libelle' => 'Atelier'])->getParent()->getLibelle());
+	}
+
+
+	/** @test */
+	public function LectureAgendaCategoryShouldBeChildOfAtelier() {
+		$this->assertEquals('Atelier',Class_ArticleCategorie::findFirstBy(['libelle' => 'Lecture'])->getParent()->getLibelle());
+	}
+
+
+	/** @test */
+	public function mainDomaineAgendaShouldBeExistInDB() {
+		$parent = Class_Catalogue::findFirstBy(['libelle' => 'Agenda']);
+		$this->assertNotNull($parent);
+	}
+
+
+	/** @test */
+	public function atelierAgendaDomaineShouldBeChildOfAgenda() {
+		$this->assertEquals('Agenda',
+												Class_Catalogue::findFirstBy(['libelle' => 'Atelier'])->getParent()->getLibelle());
+	}
+
+
+	/** @test */
+	public function lectureAgendaDomaineShouldBeChildOfAtelier() {
+		$this->assertEquals('Atelier',Class_Catalogue::findFirstBy(['libelle' => 'Lecture'])->getParent()->getLibelle());
+	}
+}
+
+
+
+class Import_Typo3ArticleTest extends Import_Typo3TestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->fixture('Class_ArticleCategorie', ['id' => 1,
+																							'libelle' => 'Infos',
+																							'sous_categories' => []]);
+
+		$this->fixture('Class_Exemplaire',
+									 ['id' => 3,
+										'id_origine' => 174002,
+										'notice' => $this->fixture('Class_Notice',
+																							 ['id' => 5,
+																								'clef_alpha' => 'BOBMARLEY'])]);
+
+		$this->migration->import_user();
+		$this->migration->import_categories();
+		$this->migration->importArticles();
+
+		$this->bateau_pirate = Class_Article::findFirstBy(['titre' => 'A bord du bateau pirate']);
+	}
+
+
+	/** @test */
+	public function articleABordDuBateauPirateShouldBeImported() {
+		$this->assertNotNull($this->bateau_pirate);
+	}
+
+
+	/** @test */
+	public function articleABordDuBateauPirateShouldBelongToSerge() {
+		$id_user = $this->bateau_pirate->getIdUser();
+		$this->assertEquals('sserge', Class_Users::findFirstBy(['id_user' => $id_user])->getLogin());
+	}
+
+
+	/** @test */
+	public function articleABordDuBateauPirateShouldBeInDomainAutres() {
+		$this->assertEquals(['Société & Civilisation', 'Divers', 'NOS TOPS 5', 'Autres'],
+												$this->bateau_pirate->getDomaineLibelles());
+	}
+
+
+	/** @test */
+	public function articleABordDuBateauPirateCategoryShouldBeAutres() {
+		$this->assertEquals('Autres',
+												$this->bateau_pirate->getCategorieLibelle());
+	}
+
+ /** @test */
+	public function articleABordDuBateauPirateShouldContainsImageWithBateau() {
+		$this->assertContains('<img src="'.Class_Import_Typo3::BOKEH_IMG_URL.'pics/bateau.jpg" alt=""/>',$this->bateau_pirate->getContenu());
+	}
+
+ /** @test */
+	public function articleABordDuBateauPirateShouldContainsImageWithBateauInSummary() {
+		$this->assertContains('<img src="'.Class_Import_Typo3::BOKEH_IMG_URL.'pics/bateau.jpg" alt=""/>',$this->bateau_pirate->getDescription());
+	}
+
+
+ /** @test */
+	public function articleABordDuBateauPirateShouldConvertImageWithUploads() {
+		$this->assertContains('<img style="padding-right: 10px; padding-bottom: 10px; float: left;" src="'.Class_Import_Typo3::BOKEH_IMG_URL.'RTEmagicC_5_37.jpg.jpg" height="76" width="133" alt="" />',$this->bateau_pirate->getContenu());
+	}
+
+	/** @test */
+	public function articleABordDuBateauPirateShouldConvertFileWithAttachments() {
+		$this->assertContains('<a href="'.Class_Import_Typo3::BOKEH_ATTACHMENT_URL.'fichiers_joints/reglement_interieur/REGLEMENT-INTERIEUR-ANNEXE-LISEUSES.pdf">',$this->bateau_pirate->getContenu());
+	}
+
+
+	/** @test */
+	public function articleABordDuBateauPirateShouldConvertFileWithAttachmentActionCulturelle() {
+		$this->assertContains('<a href="'.Class_Import_Typo3::BOKEH_ATTACHMENT_URL.'action_culturelle/circuit.pdf">',$this->bateau_pirate->getContenu());
+	}
+
+
+ /** @test */
+	public function articleABordDuBateauPirateShouldConvertFileWithAttachmentsWithHttpLink() {
+		$this->assertContains('<a href="'.Class_Import_Typo3::BOKEH_ATTACHMENT_URL.'fichiers_joints/formulaires/memento_communique_sans_danger.pdf">',$this->bateau_pirate->getContenu());
+	}
+
+ /** @test */
+	public function articleABordDuBateauPirateShouldConvertUserUploadWithFileAdmin() {
+		$this->assertContains('<a href="'.Class_Import_Typo3::BOKEH_FILEADMIN_URL.'jeunesse/formulaire_inscription_atelier_internet_espace_J.pdf">',$this->bateau_pirate->getContenu());
+	}
+
+
+
+	/** @test */
+	public function bodyArticleForArchitectureShouldReplacedLFByBR() {
+		$this->assertEquals('<strong><span style="font-style: normal"><span style="font-weight: normal">Afin de suivre l\'actualité et la diversité de la production éditoriale, les commandes de documents sont mensuelles. </span></span></strong><br /> La collection, dans son ensemble, s\'adresse à un large public, désireux de mieux comprendre cet &quot;art de bâtir&quot; qui l\'environne et façonne de plus en plus son rapport à l\'espace, tant public que privé.<br />',
+												Class_Article::findFirstBy(['titre' => 'Architecture summary'])->getContenu());
+	}
+
+
+	/** @test */
+	public function decouvrirBridgeEventShouldNotBeImported() {
+		$this->assertNull(Class_Article::findFirstBy(['titre' => 'Découvrir le Bridge en 10 minutes']));
+	}
+
+
+	/** @test */
+	public function discoverTowerEventContentShouldBeWrappedWithAHtmlTagP() {
+		$this->assertContains('<p>Welcome to Onyx tower !<br />Prepare to fight !</p>', Class_Article::findFirstBy(['titre' => 'Onyx tower event'])->getContenu());
+	}
+
+
+	/** @test */
+	public function bokehWikiContentShouldContainsAnchorWithExpectedAttributs() {
+		$this->assertEquals('<p>Welcome to <a href="http://wiki.bokeh-library-portal.org">Wiki Bokeh</a></p>', Class_Article::findFirstBy(['titre' => 'Wiki Bokeh'])->getContenu());
+
+	}
+
+
+	/** @test */
+	public function contentOfWikiBokehShouldNotContainsLink() {
+		$this->assertNotContains('<link', Class_Article::findFirstBy(['titre' => 'Wiki Bokeh'])->getContenu());
+	}
+
+
+	/** @test */
+	public function contentOfRealyYouCrashShouldReplaceSearchAuthors() {
+		$this->assertContains('<a href="/miop-test.net/recherche/simple/rech_auteurs/Bob%20Marley%20and%20The%20Wailers">Bob Marley</a>', Class_Article::findFirstBy(['titre' => 'Realy ? you crash ?'])->getContenu());
+	}
+
+
+	/** @test */
+	public function contentOfRealyYouCrashShouldReplaceSimpleSearches() {
+		$this->assertContains('<a href="/miop-test.net/recherche/simple/expressionRecherche/Amy%20Winehouse">',
+													Class_Article::findFirstBy(['titre' => 'Realy ? you crash ?'])->getContenu());
+	}
+
+
+	/** @test */
+	public function contentOfRealyYouCrashShouldContainsLinkToNoticeBobMarley() {
+		$this->assertContains('<a href="/miop-test.net/recherche/viewnotice/clef/BOBMARLEY">Lien direct au catalogue</a>',
+													Class_Article::findFirstBy(['titre' => 'Realy ? you crash ?'])->getContenu());
+	}
+
+
+	/** @test */
+	public function contentOfRealyyouCrashShouldNotContainsLink() {
+		$this->assertNotContains('<link', Class_Article::findFirstBy(['titre' => 'Realy ? you crash ?'])->getContenu());
+	}
+
+
+	/** @test */
+	public function deezerContentShouldBeAnAnchor() {
+		$this->assertEquals('<a href="http://www.deezer.com/fr/nneka.html">Nneka</a>', Class_Article::findFirstBy(['titre' => 'Deezer ?'])->getContenu());
+	}
+
+
+	/** @test */
+	public function lartothequeArticleShouldBeTranformed() {
+		$this->assertContains('<a href="http://koha.mediathequeouestprovence.fr/cgi-bin/koha/opac-search.pl?q=test&idx=kw&idx=kw&idx=kw&limit=mc-itemtype%3AOART&sort_by=relevance&do=Rechercher">Un catalogue iconographique</a>', Class_Article::findFirstBy(['titre' => 'L\'artothèque'])->getContenu());
+	}
+}
+
+
+
+class Import_Typo3CalendarTest extends Import_Typo3TestCase {
+	public function setUp() {
+		parent::setUp();
+
+		$this->migration->import_user();
+		$this->migration->import_categories();
+		$this->migration->importCalendar();
+
+		$this->event_bridge = Class_Article::findFirstBy(['titre' => 'Découvrir le Bridge en 10 minutes']);
+	}
+
+
+	/** @test */
+	public function decouvrirBridgeEventShouldBeImported() {
+		$this->assertNotNull($this->event_bridge);
+	}
+
+
+	/** @test */
+	public function decouvrirBridgeEventShouldBelongToToto() {
+		$id_user = $this->event_bridge->getIdUser();
+		$this->assertEquals('toto', Class_Users::findFirstBy(['id_user' => $id_user])->getLogin());
+	}
+
+
+	/** @test */
+	public function decouvrirBridgeCreationDateShouldBe12Septembre2007() {
+		$this->assertEquals('2007-09-12 10:50:56', $this->event_bridge->getDateCreation());
+	}
+
+
+	/** @test */
+	public function decouvrirBridgeShouldHaveCategoryAtelier() {
+		$id_cat = $this->event_bridge->getIdCat();
+		$this->assertEquals('Pont', Class_ArticleCategorie::findFirstBy(['id' => $id_cat])->getLibelle());
+	}
+
+
+	/** @test */
+	public function decouvrirBridgeShouldBeInDomainsAtelierPont() {
+	$this->assertEquals(['Atelier', 'Pont'],
+												$this->event_bridge->getDomaineLibelles());
+	}
+
+
+	/** @test */
+	public function discoverMuseumContentShouldContainsHtml() {
+		$this->assertEquals('<p>Discover museum for free every saturday.<br />All familly are welcome.</p>', Class_Article::findFirstBy(['titre' => 'Discover Museum'])->getContenu());
+	}
+
+
+	/** @test */
+	public function discoverSpaceContentShouldContainsExpectedTitleWithHTML() {
+		$this->assertEquals('<p>Discover Space</p>', Class_Article::findFirstBy(['titre' => 'Discover Space'])->getContenu());
+	}
+
+
+
+	/** @test */
+	public function logsShouldContainsUrlKohaNotFoundForCalendar() {
+		$this->assertContains('Class_Article id:4 , typo3 uid: 34, unknown URL: http://koha.mediathequeouestprovence.fr/cgi-bin/koha/opac-search.pl?kw=kw&q=r%C3%A9al.%20Jean-Fran%C3%A7ois%20Laguionie',
+													Class_Import_Typo3_Logs::getInstance()->getLogs());
+	}
+
+
+
+}
+
+
+
+class Import_Typo3SitothequeTest extends Import_Typo3TestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->fixture('Class_Exemplaire',
+									 ['id' => 3,
+										'id_origine' => 268360,
+										'notice' => $this->fixture('Class_Notice',
+																							 ['id' => 5,
+																								'clef_alpha' => 'OUEST_CANADIEN'])]);
+
+
+		$this->migration->import_categories();
+		$this->migration->importArticlesPages();
+		$this->migration->importSites();
+
+		$this->sito = Class_Sitotheque::findFirstBy(['titre' => 'MuséoParc Alésia']);
+	}
+
+
+	/** @test */
+	public function MuseoParcAlesiaShouldBeImported() {
+		$this->assertNotNull($this->sito);
+	}
+
+
+	/** @test */
+	public function MuseoParcAlesiaShouldHaveCategoryMyCategory() {
+		$id_cat = $this->sito->getIdCat();
+		$this->assertEquals('2014',
+												Class_SitothequeCategorie::findFirstBy(['id' => $id_cat])->getLibelle());
+	}
+
+
+	/** @test */
+	public function museoParcShouldBeInDomainAutres() {
+		$this->assertEquals(['My category', 'Divers', 'NOS TOPS 5', 'Autres'],
+												$this->sito->getDomaineLibelles());
+	}
+
+
+	/** @test */
+	public function museoParcAlesiaShouldBeInNosTops5_Art_Autres_2014() {
+		$this->assertEquals('/NOS TOPS 5/Autres/2014',
+												$this->sito->getCategorie()->getPath());
+	}
+
+/** @test */
+	public function ouestCanadienShouldContainsUrlBokehSearch() {
+		$this->assertEquals('/miop-test.net/recherche/viewnotice/clef/OUEST_CANADIEN', Class_Sitotheque::findFirstBy(['titre' => 'L\'ouest canadien'])->getUrl());
+
+	}
+}
+
+
+
+class Import_Typo3ContentTest extends Import_Typo3TestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->migration->import_categories();
+		$this->migration->importArticlesPages();
+	}
+
+
+	/** @test */
+	public function aAhuteVoixShouldBeImported() {
+		$this->assertNotNull(Class_Article::findFirstBy(['titre' => 'A haute voix... le roman se fait entendre...']));
+	}
+
+
+	/** @test */
+	public function aAhuteVoixShouldHaveCategoryPagesFixes() {
+		$id_cat = Class_Article::findFirstBy(['titre' => 'A haute voix... le roman se fait entendre...'])->getIdCat();
+		$this->assertEquals('Pages fixes', Class_ArticleCategorie::findFirstBy(['id' => $id_cat])->getLibelle());
+	}
+}
+
+
+
+class Import_Typo3LogsTest extends Import_Typo3TestCase {
+	public function setUp() {
+		parent::setUp();
+		$this->migration->run();
+	}
+
+
+	/** @test */
+	public function logsShouldReturn4UsersFound() {
+		$this->assertContains('User(s) found: 4', Class_Import_Typo3_Logs::getInstance()->getLogs());
+	}
+
+
+	/** @test */
+	public function logsShouldContainsUrlKohaNotFoundForArticle23() {
+		$this->assertContains('Class_Article id:1 , typo3 uid: 32, unknown URL: http://koha.mediathequeouestprovence.fr/cgi-bin/koha/opac-shelves.pl?viewshelf=1556&sortfield=title%20%3Ccid:part1.08080603.08080008@ouestprovence.fr',
+													Class_Import_Typo3_Logs::getInstance()->getLogs());
+	}
+
+
+	/** @test */
+	public function logsShouldContainsUrlKohaNotFoundForArticle9() {
+		$this->assertContains('Class_Article id:9 , typo3 uid: 13973, unknown URL: http://koha.mediathequeouestprovence.fr/cgi-bin/koha/opac-search.pl?q=test&idx=kw&idx=kw&idx=kw',
+													Class_Import_Typo3_Logs::getInstance()->getLogs());
+	}
+
+
+
+	/** @test */
+	public function logsShouldContainsUrlKohaNotFoundForSito() {
+		$this->assertContains('Class_Sitotheque id:3 , typo3 uid: 144333, unknown URL: http://koha.mediathequeouestprovence.fr/cgi-bin/koha/opac-detail.pl?foo=bar',
+													Class_Import_Typo3_Logs::getInstance()->getLogs());
+	}
+
+
+	/** @test */
+	public function lgosShouldReturn3UsersAdded() {
+		$this->assertContains('User(s) added: 3', Class_Import_Typo3_Logs::getInstance()->getLogs());
+	}
+
+
+	/** @test */
+	public function logsShouldReturn0UserUpdated() {
+		$this->assertContains('User(s) updated: 0', Class_Import_Typo3_Logs::getInstance()->getLogs());
+	}
+
+
+	/** @test */
+	public function logsShouldReturn1UserRejected() {
+		$this->assertContains('User(s) rejected: 1', Class_Import_Typo3_Logs::getInstance()->getLogs());
+	}
+
+
+	/** @test */
+	public function logsShouldReturnAnErrorForSavingTom() {
+		$this->assertContains("user with uid 123456789, ( Tom ) can't be saved:", Class_Import_Typo3_Logs::getInstance()->getErrors());
+	}
+
+
+	/** @test */
+	public function reportShouldContainsLogs() {
+		$this->assertContains('User(s) rejected: 1', Class_Import_Typo3_Logs::getInstance()->report());
+	}
+
+
+	/** @test */
+	public function reportShouldContainsErrors() {
+		$this->assertContains("user with uid 123456789, ( Tom ) can't be saved:", Class_Import_Typo3_Logs::getInstance()->report());
+	}
+
+
+	/** @test */
+	public function logsShouldContains8T3Categories() {
+		$this->assertContains("Typo3 categories found: 8", Class_Import_Typo3_Logs::getInstance()->getLogs());
+	}
+
+
+	/** @test */
+	public function logsShouldContainsSavedSitoCategories() {
+		$this->assertContains("Typo3 Sito categories saved: 7", Class_Import_Typo3_Logs::getInstance()->getLogs());
+	}
+
+
+	/** @test */
+	public function logsShouldContainsSavedArticleCategories() {
+		$this->assertContains("Typo3 Article categories saved: 10",
+													 Class_Import_Typo3_Logs::getInstance()->getLogs());
+	}
+
+
+	/** @test */
+	public function logsShouldContainsRejected1SitoCategory() {
+		$this->assertContains("Typo3 Sito categories rejected: 1", Class_Import_Typo3_Logs::getInstance()->getLogs());
+	}
+
+
+	/** @test */
+	public function logsShouldContains1RejectedArticleCategory() {
+		$this->assertContains("Typo3 Article categories rejected: 1", Class_Import_Typo3_Logs::getInstance()->getLogs());
+	}
+
+
+	/** @test */
+	public function logsShouldContainsSavedDomains() {
+		$this->assertContains("Typo3 Domains saved: 11", Class_Import_Typo3_Logs::getInstance()->getLogs());
+	}
+
+
+	/** @test */
+	public function logsShouldContains8SavedArticles() {
+		$this->assertContains("Typo3 Articles saved: 8", Class_Import_Typo3_Logs::getInstance()->getLogs());
+	}
+
+
+	/** @test */
+	public function logsShouldContainsRejectedArticles() {
+		$this->assertContains("Typo3 Articles rejected: 1", Class_Import_Typo3_Logs::getInstance()->getLogs());
+	}
+
+
+	/** @test */
+	public function logsShouldContainsErrorForSavingArticle() {
+		$this->assertContains('Article 14888  can not be saved', Class_Import_Typo3_Logs::getInstance()->getErrors());
+	}
+}
diff --git a/tests/library/ZendAfi/Validate/UrlTest.php b/tests/library/ZendAfi/Validate/UrlTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..351bdcba81de7e242033ccc160375e343838d4f3
--- /dev/null
+++ b/tests/library/ZendAfi/Validate/UrlTest.php
@@ -0,0 +1,63 @@
+<?php
+/**
+ * Copyright (c) 2012, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * AFI-OPAC 2.0 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.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * AFI-OPAC 2.0 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 AFI-OPAC 2.0; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+class UrlTest extends PHPUnit_Framework_TestCase {
+	public function setUp() {
+		$this->_validator = new ZendAfi_Validate_Url();
+	}
+
+
+	public function validUrls() {
+		return [
+						['/images/manchot.png'],
+						['http://www.linuxfr.org'],
+						['https://www.linux.fr.org/news'],
+						];
+	}
+
+
+	public function invalidUrls() {
+		return [ ['images/manchots.png'],
+						['1234'],
+						['blob']
+		];
+	}
+
+
+	/**
+	 * @test
+	 * @dataProvider validUrls
+	 */
+	public function testurlShouldBeValid($url)    {
+		$this->assertTrue($this->_validator->isValid($url));
+	}
+
+
+	/**
+	 * @test
+	 * @dataProvider invalidUrls
+	 */
+	public function urlShouldNotBeValid($url)    {
+		$this->assertFalse($this->_validator->isValid($url));
+	}
+}
+
+?>
\ No newline at end of file
diff --git a/tests/library/ZendAfi/View/Helper/WebThumbnailTest.php b/tests/library/ZendAfi/View/Helper/WebThumbnailTest.php
index 7c525e026292e86f78329fbc93db9066c5b5328e..45fca09fefca11d91284f30567b9356613ad3271 100644
--- a/tests/library/ZendAfi/View/Helper/WebThumbnailTest.php
+++ b/tests/library/ZendAfi/View/Helper/WebThumbnailTest.php
@@ -72,7 +72,7 @@ $this->google_thumbnail_path = USERFILESPATH.'/web_thumbnails/www_google_com.jpg
 			->answers(true);
 
 		$url = $this->helper->webThumbnail('http://www.google.com');
-		$this->assertContains('/sito/webthumbnail/url/'.urlencode('http://www.google.com'),
+		$this->assertContains('/sito/webthumbnail?url='.urlencode('http://www.google.com'),
 												$url);
 	}
 
@@ -167,7 +167,7 @@ $this->google_thumbnail_path = USERFILESPATH.'/web_thumbnails/www_google_com.jpg
 			->answers(false);
 
 		$url = $this->helper->webThumbnail('http://www.google.com');
-		$this->assertContains(BASE_URL.'/sito/webthumbnail/url/'.urlencode('http://www.google.com'),	$url);
+		$this->assertContains(BASE_URL.'/sito/webthumbnail?url='.urlencode('http://www.google.com'),	$url);
 	}
 
 }