diff --git a/FEATURES/90497 b/FEATURES/90497
new file mode 100644
index 0000000000000000000000000000000000000000..16abfd372ed7616ef2e3cc33d21f2a3ffbae57a6
--- /dev/null
+++ b/FEATURES/90497
@@ -0,0 +1,10 @@
+        '90497' =>
+            ['Label' => $this->_('Administration : Outils de purge du dossier userfiles'),
+             'Desc' => $this->_('Purge des fichiers non utilisés dans les articles'),
+             'Image' => '',
+             'Video' => '',
+             'Category' => 'Administration',
+             'Right' => function($feature_description, $user) {return true;},
+             'Wiki' => 'http://wiki.bokeh-library-portal.org/index.php?title=Explorateur_de_fichiers',
+             'Test' => '',
+             'Date' => '2019-06-17'],
\ No newline at end of file
diff --git a/VERSIONS_WIP/90497 b/VERSIONS_WIP/90497
new file mode 100644
index 0000000000000000000000000000000000000000..14e2b2f4743b43bebdfa13f9c2403a31801acfba
--- /dev/null
+++ b/VERSIONS_WIP/90497
@@ -0,0 +1 @@
+ dev#90497 : Administration : Ajout d'un outil de purge des fichiers non-utilisés dans l'explorateur de fichiers.
\ No newline at end of file
diff --git a/application/modules/admin/controllers/FileManagerController.php b/application/modules/admin/controllers/FileManagerController.php
index 964edf6b9bdc0daf9f1a9f765280ef8ac02888d6..6768b9510c24fef200c13b576d5d88e051f816f8 100644
--- a/application/modules/admin/controllers/FileManagerController.php
+++ b/application/modules/admin/controllers/FileManagerController.php
@@ -131,6 +131,55 @@ class Admin_FileManagerController extends ZendAfi_Controller_Action {
   }
 
 
+  public function purgeAction() {
+    $item = $this->_findItemFromParams();
+    if (!$this->_checkPurgeValidDirectory($item))
+      return;
+
+    $this->view->to_delete = $this->_collectToDeleteFiles($item);
+    $this->view->titre = $this->_('Nettoyer le répertoire %s', $item->getPath());
+    $this->view->message = $this->_('Supprimer %s fichier(s)', count($this->view->to_delete));
+  }
+
+
+  public function forcePurgeAction() {
+    $item = $this->_findItemFromParams();
+    if (!$this->_checkPurgeValidDirectory($item))
+      return;
+
+    $to_delete = $this->_collectToDeleteFiles($item);
+    $message = [];
+    foreach ($to_delete as $item) {
+      if ($success = Class_FileManager::delete($item))
+        continue;
+
+      $message[] = $this->_('Impossible de supprimer "%s".', $item->getPath());
+    }
+
+    $message[] = $this->_('%d fichier(s) ont été supprimés',
+                          count($to_delete) - count($message));
+    $message = implode('<br>',$message);
+    $this->_redirectToPathWithMessage($item->getPath(), $message);
+  }
+
+
+  protected function _checkPurgeValidDirectory($item) {
+    if (preg_match("/userfiles\/(bannieres|image|file)/", $item->getPath()))
+      return true;
+
+    $this->_redirectToPathWithMessage($item->getPath(),
+                                      $this->_('Seuls les dossiers "bannieres", "image" et "file" peuvent être purgés'));
+    return false;
+  }
+
+
+  protected function _collectToDeleteFiles($item) {
+    return array_filter($item->getFiles(),
+                        function($file)
+                        { return empty($file->getModels());});
+  }
+
+
   public function forceDeleteAction() {
     if(!$item = $this->_findItemFromParams())
       return;
diff --git a/application/modules/admin/views/scripts/file-manager/purge.phtml b/application/modules/admin/views/scripts/file-manager/purge.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..5259359dba3bdcdfa378442f00a821b3445eb7b9
--- /dev/null
+++ b/application/modules/admin/views/scripts/file-manager/purge.phtml
@@ -0,0 +1,15 @@
+<?php
+$disabled = $this->models ? ['disabled' => 'disabled'] : [];
+
+echo $this->tag('p', $this->_('Seuls les fichiers non utilisés dans les contenus du site seront supprimés.'));
+
+echo $this->button((new Class_Entity)
+                   ->setText($this->message)
+                   ->setUrl($this->url(['action' => 'force-purge']))
+                   ->setAttribs(array_merge(['data-popup' => 'true'],
+                                            $disabled)));
+
+echo $this->button((new Class_Entity)
+                   ->setText('Annuler')
+                   ->setAttribs(['onclick' => 'opacDialogClose();return false;',
+                                 'title' => $this->_('Ne rien faire')]));
diff --git a/library/Class/FileManager.php b/library/Class/FileManager.php
index 89de300e1dfa799267b4295d40a1cda068157c24..8fd43d870d193ec5003353ca8f3d246b8a131fcc 100644
--- a/library/Class/FileManager.php
+++ b/library/Class/FileManager.php
@@ -574,6 +574,13 @@ class Class_FileManager extends Class_Entity {
   }
 
 
+  public function getFiles() {
+    return $this->isFile()
+      ? []
+      : static::getFileSystem()->filesAt($this->getPath());
+  }
+
+
   public function readfile() {
     return static::getFileSystem()->readfile($this->getRealpath());
   }
diff --git a/library/ZendAfi/Controller/Plugin/Manager/FileManager.php b/library/ZendAfi/Controller/Plugin/Manager/FileManager.php
index 6e7e06b3983f5324e8612dac345b66c29b49d5ac..2ae620f9b705f097ca2f15f58b0966337715f2af 100644
--- a/library/ZendAfi/Controller/Plugin/Manager/FileManager.php
+++ b/library/ZendAfi/Controller/Plugin/Manager/FileManager.php
@@ -113,6 +113,18 @@ class ZendAfi_Controller_Plugin_Manager_FileManager extends ZendAfi_Controller_P
                                               'data-popup' => 'true',
                                               'class' => $model->isWritable() ? null : 'disabled'])],
 
+            ['url' => ['action' => 'purge',
+                       'item' => $model->getId()],
+             'icon'  => '',
+             'caption' => function($model) {
+                return $this->_view->tag('i', '', ['class' => 'fa fa-recycle']) . $this->_view->tag('span', $this->_('Purger'));
+              },
+             'label' => $this->_('Supprimer les fichiers inutilisés du répertoire "%s"', $model->getName()),
+             'condition' => function($model) { return Class_Users::getIdentity()->isAdmin(); },
+             'anchorOptions' => array_filter(['title' => $this->_('Supprimer les fichiers inutilisés du répertoire "%s"', $model->getName()),
+                                              'data-popup' => 'true',
+                                              'class' => $model->isWritable() ? null : 'disabled'])],
+
             ['url' => $model->getUrl(),
              'icon'  => '',
              'caption' => function($model) {
diff --git a/tests/application/modules/admin/controllers/FileManagerControllerTest.php b/tests/application/modules/admin/controllers/FileManagerControllerTest.php
index afd4d470be06f0e5e6c3d82b0349cdebdaea5262..202d28404d7bec1989535f04d215f3cda66e8189 100644
--- a/tests/application/modules/admin/controllers/FileManagerControllerTest.php
+++ b/tests/application/modules/admin/controllers/FileManagerControllerTest.php
@@ -149,6 +149,18 @@ abstract class FileManagerControllerTestCase extends Admin_AbstractControllerTes
                  ->with('userfiles')
                  ->answers([$this->_dir])
 
+                 ->whenCalled('directoriesAt')
+                 ->with('userfiles/image')
+                 ->answers([])
+
+                 ->whenCalled('directoryAt')
+                 ->with('userfiles/image')
+                 ->answers($skins_dir)
+
+                 ->whenCalled('filesAt')
+                 ->with('userfiles/image')
+                 ->answers([])
+
                  ->whenCalled('directoryAt')
                  ->with('userfiles')
                  ->answers($this->_userfiles_dir)
@@ -486,6 +498,8 @@ class FileManagerControllerDropDispatchTest extends FileManagerControllerTestCas
       ->setParentPath('userfiles/image')
       ->setPath('userfiles/image/colors');
 
+
+
     $disk = Class_FileManager::getFileSystem()
       ->whenCalled('directoryAt')
       ->with('userfiles/image/')
@@ -512,10 +526,6 @@ class FileManagerControllerDropDispatchTest extends FileManagerControllerTestCas
       ->answers([$white_dir,
                  $colors_dir])
 
-      ->whenCalled('filesAt')
-      ->with('userfiles/image/')
-      ->answers([])
-
       ->whenCalled('renameDir')
       ->with('userfiles/image/white', 'userfiles/image/colors/white')
       ->answers(true)
@@ -685,6 +695,183 @@ class FileManagerControllerDropDispatchTest extends FileManagerControllerTestCas
 
 
 
+class FileManagerControllerPurgeDirectoryImagesTest extends Admin_AbstractControllerTestCase {
+    protected
+    $_storm_default_to_volatile = true,
+    $_userfiles_dir,
+    $_dir,
+    $_disk;
+
+  public function setUp() {
+    parent::setUp();
+
+    $banner = $this->fixture('Class_Article',
+                             ['id' => 1,
+                              'titre' => 'Banner article',
+                              'contenu' => '<img src="https:// ' . BASE_URL . 'userfiles/image/used_image.png"></>']);
+
+
+    $this->onLoaderOfModel('Class_Article')
+      ->whenCalled('findAllby')
+      ->with(['where' => 'contenu like "%userfiles/image/used_image.png%"'])
+      ->answers([$banner])
+      ->whenCalled('findAllby')
+      ->with(['where' => 'description like "%userfiles/image/used_image2.png%"'])
+      ->answers([$banner])
+      ->whenCalled('findAllby')
+      ->answers([]);
+
+    $this->_userfiles_dir = (new Class_FileManager)
+      ->setId('userfiles')
+      ->setDir(true)
+      ->setName('userfiles')
+      ->setPath('userfiles');
+
+    $this->_dir = (new Class_FileManager)
+      ->setId('userfiles/image')
+      ->setParentPath('userfiles')
+      ->setPath('userfiles/image')
+      ->setName('image')
+      ->setDir(true);
+
+    $album_dir = (new Class_FileManager)
+      ->setId('userfiles/album')
+      ->setParentPath('userfiles')
+      ->setPath('userfiles/album')
+      ->setName('album')
+      ->setDir(true);
+
+    $image_png = (new Class_FileManager)
+      ->setId('userfiles/image/image.png')
+      ->setParentPath('userfiles/image')
+      ->setPath('userfiles/image/image.png')
+      ->setName('image.png')
+      ->setExtension('png')
+      ->setDir(false);
+
+    $used_image_png = (new Class_FileManager)
+      ->setId('userfiles/image/used_image.png')
+      ->setParentPath('userfiles/image')
+      ->setPath('userfiles/image/used_image.png')
+      ->setName('used_image.png')
+      ->setExtension('png')
+      ->setDir(false);
+
+    $used_image2_png = (new Class_FileManager)
+      ->setId('userfiles/image/used_image2.png')
+      ->setParentPath('userfiles/image')
+      ->setPath('userfiles/image/used_image2.png')
+      ->setName('used_image2.png')
+      ->setExtension('png')
+      ->setDir(false);
+
+
+    $this->_disk = $this->mock()
+                 ->whenCalled('diskSpaceInfo')
+                 ->answers((new Class_Entity)
+                           ->setId('userfiles')
+                           ->setFree('2 GO')
+                           ->setUsed('153 GO')
+                           ->setTotal('155 GO')
+                           ->setPercent('99%')
+                           ->whenCalledDo('isFull', function(){return false;}))
+
+                 ->whenCalled('clearCache')
+                 ->answers(true)
+
+                 ->whenCalled('isOversized')
+                 ->answers(false)
+
+                 ->whenCalled('directoriesAt')
+                 ->with('userfiles')
+                 ->answers([$this->_dir])
+
+                 ->whenCalled('directoriesAt')
+                 ->with('userfiles/image')
+                 ->answers([])
+
+                 ->whenCalled('directoryAt')
+                 ->with('userfiles/image')
+                 ->answers($this->_dir)
+
+                 ->whenCalled('directoryAt')
+                 ->with('userfiles/album')
+                 ->answers($album_dir)
+
+                 ->whenCalled('fileAt')
+                 ->with('userfiles/album')
+                 ->answers([])
+
+                 ->whenCalled('filesAt')
+                 ->with('userfiles/image')
+                 ->answers([$image_png,$used_image_png, $used_image2_png])
+
+                 ->whenCalled('directoryAt')
+                 ->with('userfiles')
+                 ->answers($this->_userfiles_dir)
+
+                 ->whenCalled('filesAt')
+                 ->with('userfiles')
+                 ->answers($this->_dir)
+      ;
+
+    Class_FileManager::setFileSystem($this->_disk);
+  }
+
+
+  public function tearDown() {
+    Class_FileManager::setFileSystem(null);
+    parent::tearDown();
+  }
+
+
+  /** @test */
+  public function purgeButtonShouldBePresent() {
+    $this->dispatch('/admin/file-manager/index?browser=userfiles%2Fimage', true);
+    $this->assertXPathContentContains('//div//a[contains(@href, "/file-manager/purge")][contains(@href, "&item=userfiles%2Fimage")]', 'Purger');
+  }
+
+
+  /** @test */
+  public function purgeButtonShouldNotBePresentIfNotAdminPortail() {
+    $redac = $this->fixture('Class_Users',
+                            ['id' => 3,
+                             'login' => 'redac',
+                             'password' => 'redac']);
+    $redac->beAdminBib();
+    ZendAfi_Auth::getInstance()->logUser($redac);
+
+    $this->dispatch('/admin/file-manager/index?browser=userfiles%2Fimage', true);
+    $this->assertNotXPathContentContains('//div//a[contains(@href, "/file-manager/purge")][contains(@href, "&item=userfiles%2Fimage")]', 'Purger');
+  }
+
+
+  /** @test */
+  public function dispatchPurgeFilesShouldReturnErrorOnNonValidDirectory() {
+    $this->dispatch('/admin/file-manager/purge?item=userfiles%2Falbum', true);
+    $this->assertRedirect('/admin/file-manager/index?browser=userfiles%2Falbum');
+    $this->assertFlashMessengerContentContains('Seuls les dossiers "bannieres", "image" et "file" peuvent être purgés');
+  }
+
+
+  /** @test */
+  public function dispatchPurgeFilesShouldReturnErrorOnUnwritableDirectory() {
+    $this->_disk->whenCalled('delete')
+                ->answers(false);
+
+    $this->dispatch('/admin/file-manager/force-purge?item=userfiles%2Fimage', true);
+    $this->assertRedirect('/admin/file-manager/index?browser=userfiles%2Fimage');
+    $this->assertFlashMessengerContentContains('Impossible de supprimer ');
+  }
+
+
+  /** @test */
+  public function dispatchPurgeFilesShouldDeleteUnusedFiles() {
+    $this->dispatch('/admin/file-manager/purge?item=userfiles%2Fimage', true);
+    $this->assertXPathContentContains('//button[contains(@data-url, "/admin/file-manager/force-purge")]', 'Supprimer 1 fichier(s)',$this->_response->getBody());
+  }
+
+}
 
 
 class FileManagerControllerSplitIndexDispatchTest extends FileManagerControllerTestCase {
@@ -855,7 +1042,7 @@ class FileManagerControllerWallDispatchTest extends FileManagerControllerTestCas
 
   /** @test */
   public function thumbnailShouldBePresent() {
-    $this->assertXPath('//div//a[contains(@href, "&display_mode_browser=wall")][contains(@href, "?browser=userfiles%2Fimage.png")]//i[contains(@title, "Erreur lors du vignettage de l\'image")]');
+    $this->assertXPath('//div//a[contains(@href, "&display_mode_browser=wall")][contains(@href, "?browser=userfiles%2Fimage.png")]//i[contains(@title, "Erreur lors du vignettage de l\'image")]',$this->_response->getBody());
   }