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()); }