From 8f6b6625d7bd69b19c4f40d909452ccc6ef855f3 Mon Sep 17 00:00:00 2001 From: Wim Vandersmissen Date: Thu, 14 Aug 2014 14:00:16 +0200 Subject: [PATCH 1/2] Folder is nested tree / PDF previews / Keep media name / Move media / Move folder --- .travis.yml | 1 + .../ItemAction/MediaDeleteItemAction.php | 2 +- AdminList/MediaAdminListConfigurator.php | 24 +- Changelog.md | 9 + Command/CreatePdfPreviewCommand.php | 44 +++ Command/MigrateNameCommand.php | 58 ++++ Command/RebuildFolderTreeCommand.php | 32 +++ Controller/AviaryController.php | 15 +- Controller/ChooserController.php | 49 ++-- Controller/FolderController.php | 93 +++--- Controller/MediaController.php | 61 ++-- DataFixtures/ORM/FolderFixtures.php | 39 ++- .../KunstmaanMediaExtension.php | 23 +- Entity/Folder.php | 173 ++++++++++-- Entity/Media.php | 51 +++- Form/File/FileType.php | 31 +- Form/FolderType.php | 38 +-- Form/RemoteAudio/RemoteAudioType.php | 32 ++- Form/RemoteSlide/RemoteSlideType.php | 30 ++ Form/RemoteVideo/RemoteVideoType.php | 36 ++- Form/Type/IconFontType.php | 15 +- Form/Type/IdToMediaTransformer.php | 19 +- Form/Type/MediaType.php | 16 +- Helper/File/FileHandler.php | 8 +- Helper/File/FileHelper.php | 46 ++- Helper/File/PdfHandler.php | 102 +++++++ Helper/File/SVGMimeTypeGuesser.php | 5 - Helper/FolderManager.php | 62 ++++ Helper/Image/ImageHandler.php | 1 - Helper/Media/AbstractMediaHandler.php | 24 +- Helper/MediaManager.php | 13 +- Helper/Menu/MediaMenuAdaptor.php | 155 ++-------- Helper/Remote/AbstractRemoteHelper.php | 16 ++ Helper/Remote/RemoteInterface.php | 61 +++- Helper/RemoteAudio/RemoteAudioHandler.php | 31 +- Helper/RemoteSlide/RemoteSlideHandler.php | 53 ++-- Helper/RemoteVideo/RemoteVideoHandler.php | 64 +++-- Helper/Transformer/PdfTransformer.php | 52 ++++ .../PreviewTransformerInterface.php | 17 ++ README.md | 24 +- Repository/FolderRepository.php | 265 ++++++++++++++++-- Repository/MediaRepository.php | 6 +- Resources/config/handlers.yml | 82 +++--- Resources/config/liip_imagine.yml | 2 +- Resources/config/services.yml | 31 +- .../views/Chooser/chooserShowFolder.html.twig | 13 +- .../Chooser/chooserShowRecTreeView.html.twig | 10 +- Resources/views/Folder/breadcrumbs.html.twig | 12 + .../views/Folder/foldertreeview.html.twig | 10 + Resources/views/Folder/show.html.twig | 38 ++- Resources/views/Media/create.html.twig | 2 +- Resources/views/Media/show.html.twig | 25 ++ Tests/Entity/FolderTest.php | 216 +++++++++++++- Tests/Entity/MediaTest.php | 89 +++++- Tests/Files/sample.pdf | Bin 0 -> 31460 bytes Tests/Form/AbstractTypeTest.php | 1 + Tests/Helper/File/FileHelperTest.php | 70 +++-- Tests/Helper/File/PdfHandlerTest.php | 112 ++++++++ Tests/Helper/FolderManagerTest.php | 138 +++++++++ Tests/Helper/MediaManagerTest.php | 240 +++++++++++++--- .../RemoteAudio/RemoteAudioHelperTest.php | 47 ++++ .../RemoteSlide/RemoteSlideHelperTest.php | 88 +----- .../RemoteVideo/RemoteVideoHelperTest.php | 81 +----- .../Helper/Transformer/PdfTransformerTest.php | 109 +++++++ UPGRADE.md | 24 ++ composer.json | 7 +- 66 files changed, 2548 insertions(+), 795 deletions(-) create mode 100644 Changelog.md create mode 100644 Command/CreatePdfPreviewCommand.php create mode 100644 Command/MigrateNameCommand.php create mode 100644 Command/RebuildFolderTreeCommand.php create mode 100644 Helper/File/PdfHandler.php create mode 100644 Helper/FolderManager.php create mode 100644 Helper/Transformer/PdfTransformer.php create mode 100644 Helper/Transformer/PreviewTransformerInterface.php create mode 100644 Resources/views/Folder/breadcrumbs.html.twig create mode 100644 Resources/views/Folder/foldertreeview.html.twig create mode 100644 Tests/Files/sample.pdf create mode 100644 Tests/Helper/File/PdfHandlerTest.php create mode 100644 Tests/Helper/FolderManagerTest.php create mode 100644 Tests/Helper/RemoteAudio/RemoteAudioHelperTest.php create mode 100644 Tests/Helper/Transformer/PdfTransformerTest.php create mode 100644 UPGRADE.md diff --git a/.travis.yml b/.travis.yml index deeba3f5..b1e99e54 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,7 @@ matrix: before_script: + - sudo apt-get install -qq imagemagick ghostscript - composer selfupdate - composer --prefer-source install diff --git a/AdminList/ItemAction/MediaDeleteItemAction.php b/AdminList/ItemAction/MediaDeleteItemAction.php index 958844f4..55d78c05 100644 --- a/AdminList/ItemAction/MediaDeleteItemAction.php +++ b/AdminList/ItemAction/MediaDeleteItemAction.php @@ -14,7 +14,7 @@ class MediaDeleteItemAction implements ItemActionInterface /** * @param string $redirectUrl */ - function __construct($redirectUrl) + public function __construct($redirectUrl) { $this->redirectUrl = $redirectUrl; } diff --git a/AdminList/MediaAdminListConfigurator.php b/AdminList/MediaAdminListConfigurator.php index 3482c37d..05790cfd 100644 --- a/AdminList/MediaAdminListConfigurator.php +++ b/AdminList/MediaAdminListConfigurator.php @@ -35,19 +35,17 @@ class MediaAdminListConfigurator extends AbstractDoctrineORMAdminListConfigurato /** * @param EntityManager $em The entity manager - * @param AclHelper $aclHelper The acl helper * @param MediaManager $mediaManager The media manager * @param Folder $folder The current folder * @param Request $request The request object */ public function __construct( - EntityManager $em, - AclHelper $aclHelper = null, - MediaManager $mediaManager, - Folder $folder, - Request $request + EntityManager $em, + MediaManager $mediaManager, + Folder $folder, + Request $request ) { - parent::__construct($em, $aclHelper); + parent::__construct($em); $this->setAdminType(new MediaType($mediaManager, $em)); $this->folder = $folder; @@ -168,26 +166,26 @@ public function adaptQueryBuilder(QueryBuilder $queryBuilder) switch ($type) { case 'file': $queryBuilder->andWhere('b.location = :location') - ->setParameter('location', 'local'); + ->setParameter('location', 'local'); break; case 'image': $queryBuilder->andWhere('b.contentType LIKE :ctype') - ->setParameter('ctype', '%image%'); + ->setParameter('ctype', '%image%'); break; case RemoteAudioHandler::TYPE: $queryBuilder->andWhere('b.contentType = :ctype') - ->setParameter('ctype', RemoteAudioHandler::CONTENT_TYPE); + ->setParameter('ctype', RemoteAudioHandler::CONTENT_TYPE); break; case RemoteSlideHandler::TYPE: $queryBuilder->andWhere('b.contentType = :ctype') - ->setParameter('ctype', RemoteSlideHandler::CONTENT_TYPE); + ->setParameter('ctype', RemoteSlideHandler::CONTENT_TYPE); break; case RemoteVideoHandler::TYPE: $queryBuilder->andWhere('b.contentType = :ctype') - ->setParameter('ctype', RemoteVideoHandler::CONTENT_TYPE); + ->setParameter('ctype', RemoteVideoHandler::CONTENT_TYPE); break; } } } } -} +} \ No newline at end of file diff --git a/Changelog.md b/Changelog.md new file mode 100644 index 00000000..96b64137 --- /dev/null +++ b/Changelog.md @@ -0,0 +1,9 @@ +# Changelog + +### dev (2014-08-13) + +* The originalFilename property has been added to Media, to store the original filename (so the name property can +be used to add a meaningful name for use in the backend) +* The Folder entity has been converted to a nested tree for performance reasons. +* A command 'kuma:media:rebuild-folder-tree' was added to (re)build the folder tree. +* Preview images for PDF documents will be created when you upload them (if you have PDF support \ No newline at end of file diff --git a/Command/CreatePdfPreviewCommand.php b/Command/CreatePdfPreviewCommand.php new file mode 100644 index 00000000..a66b706e --- /dev/null +++ b/Command/CreatePdfPreviewCommand.php @@ -0,0 +1,44 @@ +setName('kuma:media:create-pdf-previews') + ->setDescription('Create preview images for PDFs that have already been uploaded') + ->setHelp( + "The kuma:media:create-pdf-previews command can be used to create preview images for PDFs that have already been uploaded." + ); + } + + public function execute(InputInterface $input, OutputInterface $output) + { + $output->writeln('Creating PDF preview images...'); + + $pdfTransformer = $this->getContainer()->get('kunstmaan_media.pdf_transformer'); + $webPath = realpath($this->getContainer()->get('kernel')->getRootDir() . '/../web') . DIRECTORY_SEPARATOR; + + /** + * @var EntityManager + */ + $em = $this->getContainer()->get('doctrine.orm.entity_manager'); + $medias = $em->getRepository('KunstmaanMediaBundle:Media')->findBy( + array('contentType' => 'application/pdf', 'deleted' => false) + ); + /** @var Media $media */ + foreach ($medias as $media) { + $pdfTransformer->apply($webPath . $media->getUrl()); + } + $output->writeln('PDF preview images have been created.'); + } +} \ No newline at end of file diff --git a/Command/MigrateNameCommand.php b/Command/MigrateNameCommand.php new file mode 100644 index 00000000..48f83883 --- /dev/null +++ b/Command/MigrateNameCommand.php @@ -0,0 +1,58 @@ +setName('kuma:media:migrate-name') + ->setDescription('Migrate media name to new column.') + ->setHelp( + "The kuma:media:migrate-name command can be used to migrate the media name to the newly added column." + ); + } + + public function execute(InputInterface $input, OutputInterface $output) + { + $output->writeln('Migrating media name...'); + /** + * @var EntityManager + */ + $em = $this->getContainer()->get('doctrine.orm.entity_manager'); + + $medias = $em->getRepository('KunstmaanMediaBundle:Media')->findAll(); + $updates = 0; + try { + $em->beginTransaction(); + /** @var Media $media */ + foreach ($medias as $media) { + $filename = $media->getOriginalFilename(); + if (empty($filename)) { + $media->setOriginalFilename($media->getName()); + $em->persist($media); + $updates++; + } + } + $em->flush(); + $em->commit(); + } catch (\Exception $e) { + $em->rollback(); + $output->writeln('An error occured while migrating media name : ' . $e->getMessage() . ''); + } + $output->writeln('' . $updates . ' media files have been migrated.'); + } +} \ No newline at end of file diff --git a/Command/RebuildFolderTreeCommand.php b/Command/RebuildFolderTreeCommand.php new file mode 100644 index 00000000..4540e586 --- /dev/null +++ b/Command/RebuildFolderTreeCommand.php @@ -0,0 +1,32 @@ +setName('kuma:media:rebuild-folder-tree') + ->setDescription('Rebuild the media folder tree.') + ->setHelp("The kuma:media:rebuild-folder-tree will loop over all media folders and update the media folder tree."); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $em = $this->getContainer()->get('doctrine.orm.entity_manager'); + $em->getRepository('KunstmaanMediaBundle:Folder')->rebuildTree(); + $output->writeln('Updated all folders'); + } +} \ No newline at end of file diff --git a/Controller/AviaryController.php b/Controller/AviaryController.php index f6d5e7e8..482c4e18 100644 --- a/Controller/AviaryController.php +++ b/Controller/AviaryController.php @@ -2,7 +2,6 @@ namespace Kunstmaan\MediaBundle\Controller; - use Kunstmaan\MediaBundle\Entity\Folder; use Kunstmaan\MediaBundle\Entity\Media; use Kunstmaan\MediaBundle\Helper\MediaManager; @@ -12,7 +11,7 @@ use Symfony\Component\HttpFoundation\Request; /** - * controllerclass which Aviary can use to upload the edited image and add it to the database + * Controller class which Aviary can use to upload the edited image and add it to the database */ class AviaryController extends Controller { @@ -35,18 +34,18 @@ public function indexAction(Request $request, $folderId, $mediaId) $media = $em->getRepository('KunstmaanMediaBundle:Media')->getMedia($mediaId); /* @var MediaManager $mediaManager */ $mediaManager = $this->get('kunstmaan_media.media_manager'); - $handler = $mediaManager->getHandler($media); $fileHelper = $handler->getFormHelper($media); $fileHelper->getMediaFromUrl($request->get('url')); $media = $fileHelper->getMedia(); - $em->persist($media); $em->flush(); - return new RedirectResponse($this->generateUrl( - 'KunstmaanMediaBundle_folder_show', - array('folderId' => $folder->getId()) - )); + return new RedirectResponse( + $this->generateUrl( + 'KunstmaanMediaBundle_folder_show', + array('folderId' => $folder->getId()) + ) + ); } } diff --git a/Controller/ChooserController.php b/Controller/ChooserController.php index 9195b877..ba74f15f 100644 --- a/Controller/ChooserController.php +++ b/Controller/ChooserController.php @@ -6,6 +6,7 @@ use Kunstmaan\MediaBundle\AdminList\MediaAdminListConfigurator; use Kunstmaan\MediaBundle\Entity\Folder; use Kunstmaan\MediaBundle\Entity\Media; +use Kunstmaan\MediaBundle\Helper\Media\AbstractMediaHandler; use Kunstmaan\MediaBundle\Helper\MediaManager; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; @@ -28,18 +29,18 @@ class ChooserController extends Controller */ public function chooserIndexAction(Request $request) { - $em = $this->getDoctrine()->getManager(); - $session = $request->getSession(); + $em = $this->getDoctrine()->getManager(); + $session = $request->getSession(); + $folderId = false; $type = $request->get('type'); $cKEditorFuncNum = $request->get('CKEditorFuncNum'); $linkChooser = $request->get('linkChooser'); - $folderId = false; // Go to the last visited folder if ($session->get('last-media-folder')) { try { - $folder = $em->getRepository('KunstmaanMediaBundle:Folder')->getFolder($session->get('last-media-folder')); + $em->getRepository('KunstmaanMediaBundle:Folder')->getFolder($session->get('last-media-folder')); $folderId = $session->get('last-media-folder'); } catch (EntityNotFoundException $e) { $folderId = false; @@ -54,10 +55,10 @@ public function chooserIndexAction(Request $request) } $params = array( - 'folderId' => $folderId, - 'type' => $type, - 'CKEditorFuncNum' => $cKEditorFuncNum, - 'linkChooser' => $linkChooser + 'folderId' => $folderId, + 'type' => $type, + 'CKEditorFuncNum' => $cKEditorFuncNum, + 'linkChooser' => $linkChooser ); return $this->redirect($this->generateUrl('KunstmaanMediaBundle_chooser_show_folder', $params)); @@ -97,9 +98,8 @@ public function chooserShowFolderAction(Request $request, $folderId) /* @var Folder $folder */ $folder = $em->getRepository('KunstmaanMediaBundle:Folder')->getFolder($folderId); - /* @var array $mediaHandler */ - $folders = $em->getRepository('KunstmaanMediaBundle:Folder')->getAllFolders(); + /** @var AbstractMediaHandler $handler */ $handler = null; if ($type) { $handler = $mediaHandler->getHandlerForType($type); @@ -108,7 +108,7 @@ public function chooserShowFolderAction(Request $request, $folderId) /* @var MediaManager $mediaManager */ $mediaManager = $this->get('kunstmaan_media.media_manager'); - $adminListConfigurator = new MediaAdminListConfigurator($em, null, $mediaManager, $folder, $request); + $adminListConfigurator = new MediaAdminListConfigurator($em, $mediaManager, $folder, $request); $adminList = $this->get('kunstmaan_adminlist.factory')->createList($adminListConfigurator); $adminList->bindRequest($request); @@ -125,19 +125,19 @@ public function chooserShowFolderAction(Request $request, $folderId) } return array( - 'cKEditorFuncNum' => $cKEditorFuncNum, - 'linkChooser' => $linkChooser, - 'linkChooserLink' => $linkChooserLink, - 'mediamanager' => $mediaHandler, - 'handler' => $handler, - 'type' => $type, - 'folder' => $folder, - 'folders' => $folders, - 'adminlist' => $adminList, - 'fileform' => $this->createTypeFormView($mediaHandler, "file"), - 'videoform' => $this->createTypeFormView($mediaHandler, "video"), - 'slideform' => $this->createTypeFormView($mediaHandler, "slide"), - 'audioform' => $this->createTypeFormView($mediaHandler, "audio") + 'cKEditorFuncNum' => $cKEditorFuncNum, + 'linkChooser' => $linkChooser, + 'linkChooserLink' => $linkChooserLink, + 'mediamanager' => $mediaHandler, + 'foldermanager' => $this->get('kunstmaan_media.folder_manager'), + 'handler' => $handler, + 'type' => $type, + 'folder' => $folder, + 'adminlist' => $adminList, + 'fileform' => $this->createTypeFormView($mediaHandler, 'file'), + 'videoform' => $this->createTypeFormView($mediaHandler, 'video'), + 'slideform' => $this->createTypeFormView($mediaHandler, 'slide'), + 'audioform' => $this->createTypeFormView($mediaHandler, 'audio') ); } @@ -155,5 +155,4 @@ private function createTypeFormView(MediaManager $mediaManager, $type) return $this->createForm($handler->getFormType(), $helper)->createView(); } - } diff --git a/Controller/FolderController.php b/Controller/FolderController.php index 61b704be..234462df 100644 --- a/Controller/FolderController.php +++ b/Controller/FolderController.php @@ -2,6 +2,7 @@ namespace Kunstmaan\MediaBundle\Controller; +use Doctrine\ORM\EntityManager; use Kunstmaan\MediaBundle\AdminList\MediaAdminListConfigurator; use Kunstmaan\MediaBundle\Entity\Folder; use Kunstmaan\MediaBundle\Form\FolderType; @@ -29,6 +30,7 @@ class FolderController extends Controller */ public function showAction(Request $request, $folderId) { + /** @var EntityManager $em */ $em = $this->getDoctrine()->getManager(); $session = $request->getSession(); @@ -44,10 +46,9 @@ public function showAction(Request $request, $folderId) $mediaManager = $this->get('kunstmaan_media.media_manager'); /* @var Folder $folder */ - $folder = $em->getRepository('KunstmaanMediaBundle:Folder')->getFolder($folderId); - $folders = $em->getRepository('KunstmaanMediaBundle:Folder')->getAllFolders(); + $folder = $em->getRepository('KunstmaanMediaBundle:Folder')->getFolder($folderId); - $adminListConfigurator = new MediaAdminListConfigurator($em, null, $mediaManager, $folder, $request); + $adminListConfigurator = new MediaAdminListConfigurator($em, $mediaManager, $folder, $request); $adminList = $this->get('kunstmaan_adminlist.factory')->createList($adminListConfigurator); $adminList->bindRequest($request); @@ -57,24 +58,31 @@ public function showAction(Request $request, $folderId) $editForm = $this->createForm(new FolderType($folder), $folder); if ($request->isMethod('POST')) { - $editForm->submit($request); + $editForm->handleRequest($request); if ($editForm->isValid()) { $em->getRepository('KunstmaanMediaBundle:Folder')->save($folder); $this->get('session')->getFlashBag()->add( - 'success', - 'Folder \'' . $folder->getName() . '\' has been updated!' + 'success', + 'Folder \'' . $folder->getName() . '\' has been updated!' + ); + + return new RedirectResponse( + $this->generateUrl( + 'KunstmaanMediaBundle_folder_show', + array('folderId' => $folderId) + ) ); } } return array( - 'mediamanager' => $this->get('kunstmaan_media.media_manager'), - 'subform' => $subForm->createView(), - 'editform' => $editForm->createView(), - 'folder' => $folder, - 'folders' => $folders, - 'adminlist' => $adminList + 'foldermanager' => $this->get('kunstmaan_media.folder_manager'), + 'mediamanager' => $this->get('kunstmaan_media.media_manager'), + 'subform' => $subForm->createView(), + 'editform' => $editForm->createView(), + 'folder' => $folder, + 'adminlist' => $adminList ); } @@ -87,6 +95,7 @@ public function showAction(Request $request, $folderId) */ public function deleteAction($folderId) { + /** @var EntityManager $em */ $em = $this->getDoctrine()->getManager(); /* @var Folder $folder */ @@ -94,23 +103,25 @@ public function deleteAction($folderId) $folderName = $folder->getName(); $parentFolder = $folder->getParent(); - if (empty($parentFolder)) { + if (is_null($parentFolder)) { $this->get('session')->getFlashBag()->add( - 'failure', - 'You can\'t delete the \'' . $folderName . '\' folder!' + 'failure', + 'You can\'t delete the \'' . $folderName . '\' folder!' ); } else { - $folder->setDeleted(true); - $em->persist($folder); - $em->flush(); + $em->getRepository('KunstmaanMediaBundle:Folder')->delete($folder); $this->get('session')->getFlashBag()->add('success', 'Folder \'' . $folderName . '\' has been deleted!'); $folderId = $parentFolder->getId(); } - return new RedirectResponse($this->generateUrl( - 'KunstmaanMediaBundle_folder_show', - array('folderId' => $folderId) - )); + return new RedirectResponse( + $this->generateUrl( + 'KunstmaanMediaBundle_folder_show', + array( + 'folderId' => $folderId + ) + ) + ); } /** @@ -125,6 +136,7 @@ public function deleteAction($folderId) */ public function subCreateAction(Request $request, $folderId) { + /** @var EntityManager $em */ $em = $this->getDoctrine()->getManager(); /* @var Folder $parent */ @@ -133,33 +145,38 @@ public function subCreateAction(Request $request, $folderId) $folder->setParent($parent); $form = $this->createForm(new FolderType(), $folder); if ($request->isMethod('POST')) { - $form->submit($request); + $form->handleRequest($request); if ($form->isValid()) { $em->getRepository('KunstmaanMediaBundle:Folder')->save($folder); $this->get('session')->getFlashBag()->add( - 'success', - 'Folder \'' . $folder->getName() . '\' has been created!' + 'success', + 'Folder \'' . $folder->getName() . '\' has been created!' ); - return new Response(''); + return new Response( + '' + ); } } $galleries = $em->getRepository('KunstmaanMediaBundle:Folder')->getAllFolders(); return $this->render( - 'KunstmaanMediaBundle:Folder:addsub-modal.html.twig', - array( - 'subform' => $form->createView(), - 'galleries' => $galleries, - 'folder' => $folder, - 'parent' => $parent - ) + 'KunstmaanMediaBundle:Folder:addsub-modal.html.twig', + array( + 'subform' => $form->createView(), + 'galleries' => $galleries, + 'folder' => $folder, + 'parent' => $parent + ) ); } - -} \ No newline at end of file +} diff --git a/Controller/MediaController.php b/Controller/MediaController.php index f07ee398..d9660943 100644 --- a/Controller/MediaController.php +++ b/Controller/MediaController.php @@ -10,6 +10,7 @@ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\File\File; +use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -44,29 +45,30 @@ public function showAction(Request $request, $mediaId) $form = $this->createForm($handler->getFormType(), $helper); if ($request->isMethod('POST')) { - $form->submit($request); + $form->handleRequest($request); if ($form->isValid()) { $media = $helper->getMedia(); $em->getRepository('KunstmaanMediaBundle:Media')->save($media); return new RedirectResponse($this->generateUrl( - 'KunstmaanMediaBundle_media_show', - array('mediaId' => $media->getId()) + 'KunstmaanMediaBundle_media_show', + array('mediaId' => $media->getId()) )); } } $showTemplate = $mediaManager->getHandler($media)->getShowTemplate($media); return $this->render( - $showTemplate, - array( - 'handler' => $handler, - 'mediamanager' => $this->get('kunstmaan_media.media_manager'), - 'editform' => $form->createView(), - 'media' => $media, - 'helper' => $helper, - 'folder' => $folder - ) + $showTemplate, + array( + 'handler' => $handler, + 'foldermanager' => $this->get('kunstmaan_media.folder_manager'), + 'mediamanager' => $this->get('kunstmaan_media.media_manager'), + 'editform' => $form->createView(), + 'media' => $media, + 'helper' => $helper, + 'folder' => $folder + ) ); } @@ -95,8 +97,8 @@ public function deleteAction(Request $request, $mediaId) $redirectUrl = $request->query->get('redirectUrl'); if (empty($redirectUrl)) { $redirectUrl = $this->generateUrl( - 'KunstmaanMediaBundle_folder_show', - array('folderId' => $folder->getId()) + 'KunstmaanMediaBundle_folder_show', + array('folderId' => $folder->getId()) ); } @@ -122,7 +124,7 @@ public function bulkUploadAction($folderId) } /** - * @param int $folderId + * @param int $folderId * * @Route("bulkuploadsubmit/{folderId}", requirements={"folderId" = "\d+"}, name="KunstmaanMediaBundle_media_bulk_upload_submit") * @Template() @@ -231,7 +233,11 @@ public function bulkUploadSubmitAction($folderId) $em->getRepository('KunstmaanMediaBundle:Media')->save($media); // Return Success JSON-RPC response - die('{"jsonrpc" : "2.0", "result" : "", "id" : "id"}'); + return new JsonResponse(array( + 'jsonrpc' => '2.0', + 'result' => '', + 'id' => 'id' + )); } /** @@ -310,15 +316,15 @@ private function createAndRedirect(Request $request, $folderId, $type, $redirect $form = $this->createForm($handler->getFormType(), $helper); if ($request->isMethod('POST')) { - $form->submit($request); + $form->handleRequest($request); if ($form->isValid()) { $media = $helper->getMedia(); $media->setFolder($folder); $em->getRepository('KunstmaanMediaBundle:Media')->save($media); $this->get('session')->getFlashBag()->add( - 'success', - 'Media \'' . $media->getName() . '\' has been created!' + 'success', + 'Media \'' . $media->getName() . '\' has been created!' ); $params = array('folderId' => $folder->getId()); @@ -329,9 +335,9 @@ private function createAndRedirect(Request $request, $folderId, $type, $redirect } return array( - 'type' => $type, - 'form' => $form->createView(), - 'folder' => $folder + 'type' => $type, + 'form' => $form->createView(), + 'folder' => $folder ); } @@ -360,12 +366,11 @@ public function createModalAction(Request $request, $folderId, $type) } return $this->createAndRedirect( - $request, - $folderId, - $type, - 'KunstmaanMediaBundle_chooser_show_folder', - $extraParams + $request, + $folderId, + $type, + 'KunstmaanMediaBundle_chooser_show_folder', + $extraParams ); } - } diff --git a/DataFixtures/ORM/FolderFixtures.php b/DataFixtures/ORM/FolderFixtures.php index 3e72fd70..f89199bd 100644 --- a/DataFixtures/ORM/FolderFixtures.php +++ b/DataFixtures/ORM/FolderFixtures.php @@ -21,10 +21,10 @@ class FolderFixtures extends AbstractFixture implements OrderedFixtureInterface */ public function load(ObjectManager $manager) { - $gal = new Folder($manager); + $gal = new Folder(); + $gal->setRel('media'); $gal->setName('Media'); $gal->setTranslatableLocale('en'); - $gal->setRel('media'); $manager->persist($gal); $manager->flush(); $this->addReference('media-folder-en', $gal); @@ -37,15 +37,15 @@ public function load(ObjectManager $manager) $gal->setTranslatableLocale('fr'); $manager->refresh($gal); - $gal->setName('Media'); + $gal->setName('Média'); $manager->persist($gal); $manager->flush(); - $subgal = new Folder($manager); + $subgal = new Folder(); $subgal->setParent($gal); + $subgal->setRel('image'); $subgal->setName('Images'); $subgal->setTranslatableLocale('en'); - $subgal->setRel('image'); $manager->persist($subgal); $manager->flush(); $this->addReference('images-folder-en', $subgal); @@ -62,32 +62,32 @@ public function load(ObjectManager $manager) $manager->persist($subgal); $manager->flush(); - $subgal = new Folder($manager); + $subgal = new Folder(); $subgal->setParent($gal); - $subgal->setName('Videos'); + $subgal->setRel('files'); + $subgal->setName('Files'); $subgal->setTranslatableLocale('en'); - $subgal->setRel('video'); $manager->persist($subgal); $manager->flush(); - $this->addReference('videos-folder-en', $subgal); + $this->addReference('files-folder-en', $subgal); $subgal->setTranslatableLocale('nl'); $manager->refresh($subgal); - $subgal->setName('Video\'s'); + $subgal->setName('Bestanden'); $manager->persist($subgal); $manager->flush(); $subgal->setTranslatableLocale('fr'); $manager->refresh($subgal); - $subgal->setName('Vidéos'); + $subgal->setName('Fichiers'); $manager->persist($subgal); $manager->flush(); - $subgal = new Folder($manager); + $subgal = new Folder(); $subgal->setParent($gal); + $subgal->setRel('slideshow'); $subgal->setName('Slides'); $subgal->setTranslatableLocale('en'); - $subgal->setRel('slideshow'); $manager->persist($subgal); $manager->flush(); $this->addReference('slides-folder-en', $subgal); @@ -104,24 +104,24 @@ public function load(ObjectManager $manager) $manager->persist($subgal); $manager->flush(); - $subgal = new Folder($manager); + $subgal = new Folder(); $subgal->setParent($gal); - $subgal->setName('Files'); + $subgal->setRel('video'); + $subgal->setName('Videos'); $subgal->setTranslatableLocale('en'); - $subgal->setRel('files'); $manager->persist($subgal); $manager->flush(); - $this->addReference('files-folder-en', $subgal); + $this->addReference('videos-folder-en', $subgal); $subgal->setTranslatableLocale('nl'); $manager->refresh($subgal); - $subgal->setName('Bestanden'); + $subgal->setName('Video\'s'); $manager->persist($subgal); $manager->flush(); $subgal->setTranslatableLocale('fr'); $manager->refresh($subgal); - $subgal->setName('Fichiers'); + $subgal->setName('Vidéos'); $manager->persist($subgal); $manager->flush(); } @@ -135,5 +135,4 @@ public function getOrder() { return 1; } - } \ No newline at end of file diff --git a/DependencyInjection/KunstmaanMediaExtension.php b/DependencyInjection/KunstmaanMediaExtension.php index 2de6ae38..5d994501 100644 --- a/DependencyInjection/KunstmaanMediaExtension.php +++ b/DependencyInjection/KunstmaanMediaExtension.php @@ -27,17 +27,19 @@ class KunstmaanMediaExtension extends Extension implements PrependExtensionInter public function load(array $configs, ContainerBuilder $container) { $configuration = new Configuration(); - $config = $this->processConfiguration($configuration, $configs); + $config = $this->processConfiguration($configuration, $configs); - $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); + $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); - $container->setParameter('twig.form.resources', array_merge( - $container->getParameter('twig.form.resources'), - array('KunstmaanMediaBundle:Form:formWidgets.html.twig') - )); + $container->setParameter( + 'twig.form.resources', + array_merge( + $container->getParameter('twig.form.resources'), + array('KunstmaanMediaBundle:Form:formWidgets.html.twig') + ) + ); $container->setParameter('kunstmaan_media.soundcloud_api_key', $config['soundcloud_api_key']); - $loader->load('services.yml'); $loader->load('handlers.yml'); } @@ -45,13 +47,14 @@ public function load(array $configs, ContainerBuilder $container) public function prepend(ContainerBuilder $container) { - if(!$container->hasParameter('kunstmaan_media.upload_dir')) { + if (!$container->hasParameter('kunstmaan_media.upload_dir')) { $container->setParameter('kunstmaan_media.upload_dir', '/uploads/media/'); } - $twigConfig['globals']['upload_dir'] = $container->getParameter('kunstmaan_media.upload_dir'); + $twigConfig = array(); + $twigConfig['globals']['upload_dir'] = $container->getParameter('kunstmaan_media.upload_dir'); $twigConfig['globals']['mediabundleisactive'] = true; - $twigConfig['globals']['mediamanager'] = "@kunstmaan_media.media_manager"; + $twigConfig['globals']['mediamanager'] = "@kunstmaan_media.media_manager"; $container->prependExtensionConfig('twig', $twigConfig); $liipConfig = Yaml::parse(file_get_contents(__DIR__ . '/../Resources/config/liip_imagine.yml')); diff --git a/Entity/Folder.php b/Entity/Folder.php index f6212418..f6b5d9e0 100644 --- a/Entity/Folder.php +++ b/Entity/Folder.php @@ -5,6 +5,7 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\Mapping as ORM; use Gedmo\Mapping\Annotation as Gedmo; +use Gedmo\Tree\Node as GedmoNode; use Kunstmaan\AdminBundle\Entity\AbstractEntity; use Symfony\Component\Validator\Constraints as Assert; @@ -12,12 +13,16 @@ * Class that defines a folder from the MediaBundle in the database * * @ORM\Entity(repositoryClass="Kunstmaan\MediaBundle\Repository\FolderRepository") - * @ORM\Table(name="kuma_folders") + * @ORM\Table(name="kuma_folders", indexes={ + * @ORM\Index(name="idx_internal_name", columns={"internal_name"}), + * @ORM\Index(name="idx_name", columns={"name"}), + * @ORM\Index(name="idx_deleted", columns={"deleted"}) + * }) + * @Gedmo\Tree(type="nested") * @ORM\HasLifecycleCallbacks */ -class Folder extends AbstractEntity +class Folder extends AbstractEntity implements GedmoNode { - /** * @var string * @@ -39,8 +44,9 @@ class Folder extends AbstractEntity /** * @var Folder * - * @ORM\ManyToOne(targetEntity="Folder", inversedBy="children", fetch="EAGER") + * @ORM\ManyToOne(targetEntity="Folder", inversedBy="children", fetch="LAZY") * @ORM\JoinColumn(name="parent_id", referencedColumnName="id", nullable=true) + * @Gedmo\TreeParent */ protected $parent; @@ -48,14 +54,15 @@ class Folder extends AbstractEntity * @var ArrayCollection * * @ORM\OneToMany(targetEntity="Folder", mappedBy="parent", fetch="LAZY") - * @ORM\OrderBy({"name" = "ASC"}) + * @ORM\OrderBy({"lft" = "ASC"}) */ protected $children; /** * @var ArrayCollection * - * @ORM\OneToMany(targetEntity="Media", mappedBy="folder") + * @ORM\OneToMany(targetEntity="Media", mappedBy="folder", fetch="LAZY") + * @ORM\OrderBy({"name" = "ASC"}) */ protected $media; @@ -87,6 +94,30 @@ class Folder extends AbstractEntity */ protected $internalName; + /** + * @var int + * + * @ORM\Column(name="lft", type="integer", nullable=true) + * @Gedmo\TreeLeft + */ + protected $lft; + + /** + * @var int + * + * @ORM\Column(name="lvl", type="integer", nullable=true) + * @Gedmo\TreeLevel + */ + protected $lvl; + + /** + * @var int + * + * @ORM\Column(name="rgt", type="integer", nullable=true) + * @Gedmo\TreeRight + */ + protected $rgt; + /** * @var bool * @@ -103,7 +134,15 @@ public function __construct() $this->media = new ArrayCollection(); $this->setCreatedAt(new \DateTime()); $this->setUpdatedAt(new \DateTime()); - $this->deleted = false; + $this->deleted = false; + } + + /** + * @return string + */ + public function getTranslatableLocale() + { + return $this->locale; } /** @@ -212,30 +251,30 @@ public function getParent() } /** - * Set parent + * Add a child * - * @param Folder $parent + * @param Folder $child * * @return Folder */ - public function setParent(Folder $parent) + public function addChild(Folder $child) { - $this->parent = $parent; + $this->children[] = $child; + $child->setParent($this); return $this; } /** - * Add a child + * Set parent * - * @param Folder $child + * @param Folder $parent * * @return Folder */ - public function addChild(Folder $child) + public function setParent(Folder $parent = null) { - $this->children[] = $child; - $child->setParent($this); + $this->parent = $parent; return $this; } @@ -280,13 +319,13 @@ public function getMedia($includeDeleted = false) } return $this->media->filter( - function (Media $entry) { - if ($entry->isDeleted()) { - return false; - } + function (Media $entry) { + if ($entry->isDeleted()) { + return false; + } - return true; - } + return true; + } ); } @@ -307,9 +346,11 @@ public function hasActive($id) } /** + * Get child folders + * * @param bool $includeDeleted * - * @return Folder[] + * @return ArrayCollection */ public function getChildren($includeDeleted = false) { @@ -318,18 +359,18 @@ public function getChildren($includeDeleted = false) } return $this->children->filter( - function (Folder $entry) { - if ($entry->isDeleted()) { - return false; - } + function (Folder $entry) { + if ($entry->isDeleted()) { + return false; + } - return true; - } + return true; + } ); } /** - * @param array $children + * @param ArrayCollection $children * * @return Folder */ @@ -396,6 +437,77 @@ public function setName($name) return $this; } + /** + * @param int $lft + * + * @return Folder + */ + public function setLeft($lft) + { + $this->lft = $lft; + + return $this; + } + + /** + * @return int + */ + public function getLeft() + { + return $this->lft; + } + + /** + * @param int $lvl + * + * @return Folder + */ + public function setLevel($lvl) + { + $this->lvl = $lvl; + + return $this; + } + + /** + * @return int + */ + public function getLevel() + { + return $this->lvl; + } + + /** + * @param int $rgt + * + * @return Folder + */ + public function setRight($rgt) + { + $this->rgt = $rgt; + + return $this; + } + + /** + * @return int + */ + public function getRight() + { + return $this->rgt; + } + + /** + * @return string + */ + public function getOptionLabel() + { + return str_repeat( + '-', + $this->getLevel() + ) . ' ' . $this->getName(); + } + /** * @ORM\PreUpdate */ @@ -403,5 +515,4 @@ public function preUpdate() { $this->setUpdatedAt(new \DateTime()); } - } \ No newline at end of file diff --git a/Entity/Media.php b/Entity/Media.php index 8721a9be..a063ffb7 100644 --- a/Entity/Media.php +++ b/Entity/Media.php @@ -10,7 +10,10 @@ * Media * * @ORM\Entity(repositoryClass="Kunstmaan\MediaBundle\Repository\MediaRepository") - * @ORM\Table(name="kuma_media") + * @ORM\Table(name="kuma_media", indexes={ + * @ORM\Index(name="idx_name", columns={"name"}), + * @ORM\Index(name="idx_deleted", columns={"deleted"}) + * }) * @ORM\HasLifecycleCallbacks */ class Media extends AbstractEntity @@ -117,6 +120,12 @@ class Media extends AbstractEntity */ protected $url; + /** + * @var string + * + * @ORM\Column(type="string", nullable=true, name="original_filename") + */ + protected $originalFilename; /** * @var bool @@ -315,7 +324,7 @@ public function getMetadata() * Set the specified metadata value * * @param string $key - * @param mixed $value + * @param mixed $value * * @return Media */ @@ -496,13 +505,13 @@ public function getCopyright() } /** - * @param string $description + * @param string $originalFilename * * @return Media */ - public function setDescription($description) + public function setOriginalFilename($originalFilename) { - $this->description = $description; + $this->originalFilename = $originalFilename; return $this; } @@ -510,20 +519,29 @@ public function setDescription($description) /** * @return string */ - public function getDescription() + public function getOriginalFilename() { - return $this->description; + return $this->originalFilename; } /** - * @return string + * @param string $description + * + * @return Media */ - public function getClassType() + public function setDescription($description) { - $class = explode('\\', get_class($this)); - $classname = end($class); + $this->description = $description; - return $classname; + return $this; + } + + /** + * @return string + */ + public function getDescription() + { + return $this->description; } /** @@ -534,4 +552,13 @@ public function preUpdate() $this->setUpdatedAt(new \DateTime()); } + /** + * @ORM\PrePersist + */ + public function prePersist() + { + if (empty($this->name)) { + $this->setName($this->getOriginalFilename()); + } + } } diff --git a/Form/File/FileType.php b/Form/File/FileType.php index 5ac697f9..a9bab8b7 100644 --- a/Form/File/FileType.php +++ b/Form/File/FileType.php @@ -2,6 +2,8 @@ namespace Kunstmaan\MediaBundle\Form\File; +use Doctrine\ORM\EntityRepository; +use Kunstmaan\MediaBundle\Repository\FolderRepository; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormEvent; @@ -56,13 +58,14 @@ public function buildForm(FormBuilderInterface $builder, array $options) 'required' => false ) ); + $builder->addEventListener( FormEvents::PRE_SET_DATA, function (FormEvent $event) { $helper = $event->getData(); $form = $event->getForm(); - // Only add file field as required field when creating new objects + // Make sure file field is when creating new (not persisted) objects if (!$helper || null === $helper->getMedia()->getId()) { $form->add( 'file', @@ -72,6 +75,32 @@ function (FormEvent $event) { 'required' => true ) ); + } else { + // Display original filename only for persisted objects + $form->add( + 'originalFilename', + 'text', + array( + 'required' => false, + 'attr' => array( + 'readonly' => 'readonly' + ) + ) + ); + // Allow changing folder on edit + $form->add( + 'folder', + 'entity', + array( + 'class' => 'KunstmaanMediaBundle:Folder', + 'property' => 'optionLabel', + 'query_builder' => function (FolderRepository $er) { + return $er->selectFolderQueryBuilder() + ->andWhere('f.parent IS NOT NULL'); + }, + 'required' => true, + ) + ); } } ); diff --git a/Form/FolderType.php b/Form/FolderType.php index caf41e54..f9483fd9 100644 --- a/Form/FolderType.php +++ b/Form/FolderType.php @@ -2,7 +2,10 @@ namespace Kunstmaan\MediaBundle\Form; +use Doctrine\ORM\EntityRepository; +use Doctrine\ORM\QueryBuilder; use Kunstmaan\MediaBundle\Entity\Folder; +use Kunstmaan\MediaBundle\Repository\FolderRepository; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolverInterface; @@ -41,7 +44,6 @@ public function __construct(Folder $folder = null) public function buildForm(FormBuilderInterface $builder, array $options) { $folder = $this->folder; - $type = $this; $builder ->add('name') ->add( @@ -60,40 +62,16 @@ public function buildForm(FormBuilderInterface $builder, array $options) 'parent', 'entity', array( - 'class' => 'Kunstmaan\MediaBundle\Entity\Folder', + 'class' => 'KunstmaanMediaBundle:Folder', + 'property' => 'optionLabel', 'required' => true, - 'query_builder' => function (\Doctrine\ORM\EntityRepository $er) use ($folder, $type) { - $qb = $er->createQueryBuilder('folder'); - - if ($folder != null && $folder->getId() != null) { - $ids = "folder.id != " . $folder->getId(); - $ids .= $type->addChildren($folder); - $qb->andwhere($ids); - } - $qb->andWhere('folder.deleted != true'); - - return $qb; - } + 'query_builder' => function (FolderRepository $er) use ($folder) { + return $er->selectFolderQueryBuilder($folder); + } ) ); } - /** - * @param Folder $folder - * - * @return string - */ - public function addChildren(Folder $folder) - { - $ids = ""; - foreach ($folder->getChildren() as $child) { - $ids .= " and folder.id != " . $child->getId(); - $ids .= $this->addChildren($child); - } - - return $ids; - } - /** * Returns the name of this type. * diff --git a/Form/RemoteAudio/RemoteAudioType.php b/Form/RemoteAudio/RemoteAudioType.php index c4480d7b..1fe8dba7 100644 --- a/Form/RemoteAudio/RemoteAudioType.php +++ b/Form/RemoteAudio/RemoteAudioType.php @@ -2,8 +2,12 @@ namespace Kunstmaan\MediaBundle\Form\RemoteAudio; +use Doctrine\ORM\EntityRepository; +use Kunstmaan\MediaBundle\Repository\FolderRepository; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormEvent; +use Symfony\Component\Form\FormEvents; use Symfony\Component\OptionsResolver\OptionsResolverInterface; use Symfony\Component\Validator\Constraints\NotBlank; @@ -47,7 +51,7 @@ public function buildForm(FormBuilderInterface $builder, array $options) 'type', 'choice', array( - 'choices' => array('soundcloud' => 'soundcloud'), + 'choices' => array('soundcloud' => 'soundcloud'), 'constraints' => array(new NotBlank()), 'required' => true ) @@ -66,6 +70,32 @@ public function buildForm(FormBuilderInterface $builder, array $options) 'required' => false ) ); + + $builder->addEventListener( + FormEvents::PRE_SET_DATA, + function (FormEvent $event) { + $helper = $event->getData(); + $form = $event->getForm(); + + // Make sure file field is when creating new (not persisted) objects + if (null !== $helper->getMedia()->getId()) { + // Allow changing folder on edit + $form->add( + 'folder', + 'entity', + array( + 'class' => 'KunstmaanMediaBundle:Folder', + 'property' => 'optionLabel', + 'query_builder' => function (FolderRepository $er) { + return $er->selectFolderQueryBuilder() + ->andWhere('f.parent IS NOT NULL'); + }, + 'required' => true, + ) + ); + } + } + ); } /** diff --git a/Form/RemoteSlide/RemoteSlideType.php b/Form/RemoteSlide/RemoteSlideType.php index 3c5913ad..0ebc7c76 100644 --- a/Form/RemoteSlide/RemoteSlideType.php +++ b/Form/RemoteSlide/RemoteSlideType.php @@ -2,8 +2,12 @@ namespace Kunstmaan\MediaBundle\Form\RemoteSlide; +use Doctrine\ORM\EntityRepository; +use Kunstmaan\MediaBundle\Repository\FolderRepository; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormEvent; +use Symfony\Component\Form\FormEvents; use Symfony\Component\OptionsResolver\OptionsResolverInterface; use Symfony\Component\Validator\Constraints\NotBlank; @@ -66,6 +70,32 @@ public function buildForm(FormBuilderInterface $builder, array $options) 'required' => false ) ); + + $builder->addEventListener( + FormEvents::PRE_SET_DATA, + function (FormEvent $event) { + $helper = $event->getData(); + $form = $event->getForm(); + + // Make sure file field is when creating new (not persisted) objects + if (null !== $helper->getMedia()->getId()) { + // Allow changing folder on edit + $form->add( + 'folder', + 'entity', + array( + 'class' => 'KunstmaanMediaBundle:Folder', + 'property' => 'optionLabel', + 'query_builder' => function (FolderRepository $er) { + return $er->selectFolderQueryBuilder() + ->andWhere('f.parent IS NOT NULL'); + }, + 'required' => true, + ) + ); + } + } + ); } /** diff --git a/Form/RemoteVideo/RemoteVideoType.php b/Form/RemoteVideo/RemoteVideoType.php index 406938d0..977b03a0 100644 --- a/Form/RemoteVideo/RemoteVideoType.php +++ b/Form/RemoteVideo/RemoteVideoType.php @@ -2,8 +2,12 @@ namespace Kunstmaan\MediaBundle\Form\RemoteVideo; +use Doctrine\ORM\EntityRepository; +use Kunstmaan\MediaBundle\Repository\FolderRepository; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormEvent; +use Symfony\Component\Form\FormEvents; use Symfony\Component\OptionsResolver\OptionsResolverInterface; use Symfony\Component\Validator\Constraints\NotBlank; @@ -47,7 +51,11 @@ public function buildForm(FormBuilderInterface $builder, array $options) 'type', 'choice', array( - 'choices' => array('youtube' => 'youtube', 'vimeo' => 'vimeo', 'dailymotion' => 'dailymotion'), + 'choices' => array( + 'youtube' => 'youtube', + 'vimeo' => 'vimeo', + 'dailymotion' => 'dailymotion' + ), 'constraints' => array(new NotBlank()), 'required' => true ) @@ -66,6 +74,32 @@ public function buildForm(FormBuilderInterface $builder, array $options) 'required' => false ) ); + + $builder->addEventListener( + FormEvents::PRE_SET_DATA, + function (FormEvent $event) { + $helper = $event->getData(); + $form = $event->getForm(); + + // Make sure file field is when creating new (not persisted) objects + if (null !== $helper->getMedia()->getId()) { + // Allow changing folder on edit + $form->add( + 'folder', + 'entity', + array( + 'class' => 'KunstmaanMediaBundle:Folder', + 'property' => 'optionLabel', + 'query_builder' => function (FolderRepository $er) { + return $er->selectFolderQueryBuilder() + ->andWhere('f.parent IS NOT NULL'); + }, + 'required' => true, + ) + ); + } + } + ); } /** diff --git a/Form/Type/IconFontType.php b/Form/Type/IconFontType.php index 76eb16de..95a68333 100644 --- a/Form/Type/IconFontType.php +++ b/Form/Type/IconFontType.php @@ -52,10 +52,12 @@ public function setDefaultOptions(OptionsResolverInterface $resolver) { parent::setDefaultOptions($resolver); - $resolver->setDefaults(array( - 'loader' => null, - 'loader_data' => null - )); + $resolver->setDefaults( + array( + 'loader' => null, + 'loader_data' => null + ) + ); } /** @@ -88,9 +90,8 @@ public function buildForm(FormBuilderInterface $builder, array $options) */ public function buildView(FormView $view, FormInterface $form, array $options) { - $view->vars['loader'] = $form->getConfig()->getAttribute('loader'); + $view->vars['loader'] = $form->getConfig()->getAttribute('loader'); $view->vars['loader_object'] = $form->getConfig()->getAttribute('loader_object'); - $view->vars['loader_data'] = serialize($form->getConfig()->getAttribute('loader_data')); + $view->vars['loader_data'] = serialize($form->getConfig()->getAttribute('loader_data')); } - } diff --git a/Form/Type/IdToMediaTransformer.php b/Form/Type/IdToMediaTransformer.php index 9af1da69..f2e233d9 100644 --- a/Form/Type/IdToMediaTransformer.php +++ b/Form/Type/IdToMediaTransformer.php @@ -27,7 +27,7 @@ class IdToMediaTransformer implements DataTransformerInterface /** * @param ObjectManager $objectManager The object manager - * @param CurrentValueContainer $currentValueContainer The currentvaluecontainer + * @param CurrentValueContainer $currentValueContainer The current value container */ public function __construct(ObjectManager $objectManager, CurrentValueContainer $currentValueContainer) { @@ -45,24 +45,20 @@ public function __construct(ObjectManager $objectManager, CurrentValueContainer */ public function transform($entity) { - if (null === $entity || '' === $entity) { + if (empty($entity)) { return ''; } - if (!is_object($entity)) { throw new UnexpectedTypeException($entity, 'object'); } - if ($entity instanceof Collection) { throw new \InvalidArgumentException('Expected an object, but got a collection. Did you forget to pass "multiple=true" to an entity field?'); } - $this->currentValueContainer->setCurrentValue($entity); - return array( - "ent" => $entity, - "id" => $entity->getId() + 'ent' => $entity, + 'id' => $entity->getId() ); } @@ -76,18 +72,15 @@ public function transform($entity) */ public function reverseTransform($key) { - if ('' === $key || null === $key) { + if (empty($key)) { return null; } - if (!is_numeric($key)) { throw new UnexpectedTypeException($key, 'numeric'); } - - if (!($entity = $this->objectManager->getRepository('KunstmaanMediaBundle:Media')->findOneById($key))) { + if (!($entity = $this->objectManager->getRepository('KunstmaanMediaBundle:Media')->find($key))) { throw new TransformationFailedException(sprintf('The entity with key "%s" could not be found', $key)); } - $this->currentValueContainer->setCurrentValue($entity); return $entity; diff --git a/Form/Type/MediaType.php b/Form/Type/MediaType.php index fac1b45b..152f2dbd 100644 --- a/Form/Type/MediaType.php +++ b/Form/Type/MediaType.php @@ -50,8 +50,8 @@ public function __construct($mediaManager, $objectManager) public function buildForm(FormBuilderInterface $builder, array $options) { $builder->addViewTransformer( - new IdToMediaTransformer($this->objectManager, $options['current_value_container']), - true + new IdToMediaTransformer($this->objectManager, $options['current_value_container']), + true ); $builder->setAttribute('chooser', $options['chooser']); $builder->setAttribute('mediatype', $options['mediatype']); @@ -74,12 +74,12 @@ public function setDefaultOptions(OptionsResolverInterface $resolver) { parent::setDefaultOptions($resolver); $resolver->setDefaults( - array( - 'compound' => false, - 'chooser' => 'KunstmaanMediaBundle_chooser', - 'mediatype' => null, - 'current_value_container' => new CurrentValueContainer(), - ) + array( + 'compound' => false, + 'chooser' => 'KunstmaanMediaBundle_chooser', + 'mediatype' => null, + 'current_value_container' => new CurrentValueContainer(), + ) ); } diff --git a/Helper/File/FileHandler.php b/Helper/File/FileHandler.php index 39cd1bde..9e770eab 100644 --- a/Helper/File/FileHandler.php +++ b/Helper/File/FileHandler.php @@ -132,7 +132,7 @@ public function prepareMedia(Media $media) $media->setContent($file); } if ($content instanceof UploadedFile) { - $media->setName($content->getClientOriginalName()); + $media->setOriginalFilename($content->getClientOriginalName()); } $metadata = array(); @@ -206,10 +206,10 @@ public function createNew($data) /** @var $data File */ $media = new Media(); - if (method_exists($media, 'getClientOriginalName')) { - $media->setName($data->getClientOriginalName()); + if (method_exists($data, 'getClientOriginalName')) { + $media->setOriginalFilename($data->getClientOriginalName()); } else { - $media->setName($data->getFilename()); + $media->setOriginalFilename($data->getFilename()); } $media->setContent($data); diff --git a/Helper/File/FileHelper.php b/Helper/File/FileHelper.php index 2ce38c92..7c0f6b1a 100644 --- a/Helper/File/FileHelper.php +++ b/Helper/File/FileHelper.php @@ -2,6 +2,7 @@ namespace Kunstmaan\MediaBundle\Helper\File; +use Kunstmaan\MediaBundle\Entity\Folder; use Symfony\Component\HttpFoundation\File\File; use Symfony\Component\HttpFoundation\File\UploadedFile; @@ -14,7 +15,6 @@ */ class FileHelper { - /** * @var Media */ @@ -54,6 +54,22 @@ public function setName($name) $this->media->setName($name); } + /** + * @return Folder + */ + public function getFolder() + { + return $this->media->getFolder(); + } + + /** + * @param Folder $folder + */ + public function setFolder(Folder $folder) + { + $this->media->setFolder($folder); + } + /** * @return string */ @@ -86,6 +102,19 @@ public function setDescription($description) $this->media->setDescription($description); } + public function getOriginalFilename() + { + return $this->media->getOriginalFilename(); + } + + /** + * @param string $name + */ + public function setOriginalFilename($name) + { + $this->media->setOriginalFilename($name); + } + /** * @return UploadedFile */ @@ -102,7 +131,9 @@ public function setFile(File $file) $this->file = $file; $this->media->setContent($file); $this->media->setContentType($file->getMimeType()); - $this->media->setUrl('/uploads/media/'.$this->media->getUuid() . '.' . $this->media->getContent()->getExtension()); + $this->media->setUrl( + '/uploads/media/' . $this->media->getUuid() . '.' . $this->media->getContent()->getExtension() + ); } /** @@ -114,17 +145,17 @@ public function getMedia() } /** - * @param string $mediaurl + * @param string $mediaUrl * * @throws \Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException */ - public function getMediaFromUrl($mediaurl) + public function getMediaFromUrl($mediaUrl) { - $path = tempnam(sys_get_temp_dir(), 'kuma_'); - $saveFile = fopen($path, 'w'); + $path = tempnam(sys_get_temp_dir(), 'kuma_'); + $saveFile = fopen($path, 'w'); $this->path = $path; - $ch = curl_init($mediaurl); + $ch = curl_init($mediaUrl); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_FILE, $saveFile); curl_exec($ch); @@ -156,5 +187,4 @@ public function __destruct() unlink($this->path); } } - } \ No newline at end of file diff --git a/Helper/File/PdfHandler.php b/Helper/File/PdfHandler.php new file mode 100644 index 00000000..79fd3bd4 --- /dev/null +++ b/Helper/File/PdfHandler.php @@ -0,0 +1,102 @@ +setWebPath(realpath(str_replace('/', DIRECTORY_SEPARATOR, $kernelRootDir . '/../web/')) . DIRECTORY_SEPARATOR); + } + + /** + * @param string $webPath + */ + public function setWebPath($webPath) + { + $this->webPath = $webPath; + } + + /** + * @param PreviewTransformerInterface $pdfTransformer + */ + public function setPdfTransformer(PreviewTransformerInterface $pdfTransformer) + { + $this->pdfTransformer = $pdfTransformer; + } + + /** + * @return string + */ + public function getType() + { + return PdfHandler::TYPE; + } + + /** + * @param mixed $object + * + * @return bool + */ + public function canHandle($object) + { + if (parent::canHandle($object) && + ($object instanceof Media && $object->getContentType() == 'application/pdf')) { + return true; + } + + return false; + } + + /** + * @param Media $media + */ + public function saveMedia(Media $media) + { + parent::saveMedia($media); + + try { + // Generate preview for PDF + $this->pdfTransformer->apply($this->webPath . $media->getUrl()); + } catch (\ImagickException $e) { + // Fail silently () + } + } + + /** + * @param Media $media The media entity + * @param string $basepath The base path + * + * @return string + */ + public function getImageUrl(Media $media, $basepath) + { + $filename = $this->pdfTransformer->getPreviewFilename($basepath . $media->getUrl()); + if (!file_exists($this->webPath . $filename)) { + return null; + } + + return $filename; + } +} \ No newline at end of file diff --git a/Helper/File/SVGMimeTypeGuesser.php b/Helper/File/SVGMimeTypeGuesser.php index 40742929..00b7d91d 100644 --- a/Helper/File/SVGMimeTypeGuesser.php +++ b/Helper/File/SVGMimeTypeGuesser.php @@ -1,9 +1,4 @@ repository = $repository; + } + + /** + * @param Folder $rootFolder + * + * @return array|string + */ + public function getFolderHierarchy(Folder $rootFolder) + { + return $this->repository->childrenHierarchy($rootFolder); + } + + /** + * @param Folder $folder + * + * @return Folder + */ + public function getRootFolderFor(Folder $folder) + { + $parentIds = $this->getParentIds($folder); + + return $this->repository->getFolder($parentIds[0]); + } + + /** + * @param Folder $folder + * + * @return array + */ + public function getParentIds(Folder $folder) + { + return $this->repository->getParentIds($folder); + } + + /** + * @param Folder $folder + * + * @return array + */ + public function getParents(Folder $folder) + { + return $this->repository->getPath($folder); + } +} \ No newline at end of file diff --git a/Helper/Image/ImageHandler.php b/Helper/Image/ImageHandler.php index 20dc42c8..7f38abf5 100644 --- a/Helper/Image/ImageHandler.php +++ b/Helper/Image/ImageHandler.php @@ -99,5 +99,4 @@ public function prepareMedia(Media $media) } } - } diff --git a/Helper/Media/AbstractMediaHandler.php b/Helper/Media/AbstractMediaHandler.php index 96492e56..2b21badd 100644 --- a/Helper/Media/AbstractMediaHandler.php +++ b/Helper/Media/AbstractMediaHandler.php @@ -11,68 +11,67 @@ */ abstract class AbstractMediaHandler { - /** * @return string */ - public abstract function getName(); + abstract public function getName(); /** * @return string */ - public abstract function getType(); + abstract public function getType(); /** * @return AbstractType */ - public abstract function getFormType(); + abstract public function getFormType(); /** * @param mixed $media */ - public abstract function canHandle($media); + abstract public function canHandle($media); /** * @param Media $media * * @return mixed */ - public abstract function getFormHelper(Media $media); + abstract public function getFormHelper(Media $media); /** * @param Media $media * * @return void */ - public abstract function prepareMedia(Media $media); + abstract public function prepareMedia(Media $media); /** * @param Media $media * * @return void */ - public abstract function saveMedia(Media $media); + abstract public function saveMedia(Media $media); /** * @param Media $media * * @return void */ - public abstract function updateMedia(Media $media); + abstract public function updateMedia(Media $media); /** * @param Media $media * * @return void */ - public abstract function removeMedia(Media $media); + abstract public function removeMedia(Media $media); /** * @param mixed $data * * @return Media */ - public abstract function createNew($data); + abstract public function createNew($data); /** * {@inheritDoc} @@ -96,6 +95,5 @@ public function getImageUrl(Media $media, $basepath) /** * @return array */ - public abstract function getAddFolderActions(); - + abstract public function getAddFolderActions(); } \ No newline at end of file diff --git a/Helper/MediaManager.php b/Helper/MediaManager.php index 9c02b2ab..b9246b30 100644 --- a/Helper/MediaManager.php +++ b/Helper/MediaManager.php @@ -23,21 +23,25 @@ class MediaManager /** * @param AbstractMediaHandler $handler Media handler * - * @return void + * @return MediaManager */ public function addHandler(AbstractMediaHandler $handler) { $this->handlers[$handler->getName()] = $handler; + + return $this; } /** * @param AbstractMediaHandler $handler Media handler * - * @return void + * @return MediaManager */ public function setDefaultHandler(AbstractMediaHandler $handler) { $this->defaultHandler = $handler; + + return $this; } /** @@ -54,6 +58,7 @@ public function getHandler($media) return $handler; } } + return $this->defaultHandler; } @@ -85,11 +90,15 @@ public function getHandlers() /** * @param \Kunstmaan\MediaBundle\Entity\Media $media + * + * @return MediaManager */ public function prepareMedia(Media $media) { $handler = $this->getHandler($media); $handler->prepareMedia($media); + + return $this; } /** diff --git a/Helper/Menu/MediaMenuAdaptor.php b/Helper/Menu/MediaMenuAdaptor.php index 7e1ef5db..8f282c32 100644 --- a/Helper/Menu/MediaMenuAdaptor.php +++ b/Helper/Menu/MediaMenuAdaptor.php @@ -3,12 +3,12 @@ namespace Kunstmaan\MediaBundle\Helper\Menu; use Doctrine\ORM\EntityManager; +use Kunstmaan\MediaBundle\Repository\FolderRepository; use Symfony\Component\HttpFoundation\Request; use Kunstmaan\AdminBundle\Helper\Menu\MenuItem; use Kunstmaan\AdminBundle\Helper\Menu\MenuAdaptorInterface; use Kunstmaan\AdminBundle\Helper\Menu\MenuBuilder; use Kunstmaan\AdminBundle\Helper\Menu\TopMenuItem; -use Kunstmaan\MediaBundle\Entity\Media; use Kunstmaan\MediaBundle\Entity\Folder; /** @@ -17,16 +17,16 @@ class MediaMenuAdaptor implements MenuAdaptorInterface { /** - * @var EntityManager + * @var FolderRepository $repo */ - private $em; + private $repo; /** - * @param EntityManager $em + * @param FolderRepository $repo */ - public function __construct($em) + public function __construct($repo) { - $this->em = $em; + $this->repo = $repo; } /** @@ -39,137 +39,34 @@ public function __construct($em) */ public function adaptChildren(MenuBuilder $menu, array &$children, MenuItem $parent = null, Request $request = null) { - - $mediaRoutes = array( - 'Show media' => 'KunstmaanMediaBundle_media_show', - 'Edit metadata' => 'KunstmaanMediaBundle_metadata_edit', - 'Edit slide' => 'KunstmaanMediaBundle_slide_edit', - 'Edit video' => 'KunstmaanMediaBundle_video_edit' - ); - - $createRoutes = array( - 'Create' => 'KunstmaanMediaBundle_media_create', - 'Bulk upload' => 'KunstmaanMediaBundle_media_bulk_upload' - ); - - $allRoutes = array_merge($createRoutes, $mediaRoutes); - if (is_null($parent)) { - /* @var Folder[] $galleries */ - $galleries = $this->em->getRepository('KunstmaanMediaBundle:Folder')->getAllFolders(); - $currentId = $request->get('folderId'); - - if (isset($currentId)) { - /* @var Folder $currentFolder */ - $currentFolder = $this->em->getRepository('KunstmaanMediaBundle:Folder')->findOneById($currentId); - } else if (in_array($request->attributes->get('_route'), $mediaRoutes)) { - /* @var Media $media */ - $media = $this->em->getRepository('KunstmaanMediaBundle:Media')->getMedia($request->get('mediaId')); - $currentFolder = $media->getFolder(); - } else if (in_array($request->attributes->get('_route'), $createRoutes)) { - $currentId = $request->get('folderId'); - if (isset($currentId)) { - $currentFolder = $this->em->getRepository('KunstmaanMediaBundle:Folder')->findOneById($currentId); - } - } - - if (isset($currentFolder)) { - $parents = $currentFolder->getParents(); - } else { - $parents = array(); - } - - foreach ($galleries as $folder) { - $menuitem = new TopMenuItem($menu); - $menuitem->setRoute('KunstmaanMediaBundle_folder_show'); - $menuitem->setRouteparams(array('folderId' => $folder->getId())); - $menuitem->setInternalname($folder->getName()); - $menuitem->setParent($parent); - $menuitem->setRole($folder->getRel()); - if (isset($currentFolder) && (stripos($request->attributes->get('_route'), $menuitem->getRoute()) === 0 || in_array($request->attributes->get('_route'), $allRoutes))) { - if ($currentFolder->getId() == $folder->getId()) { - $menuitem->setActive(true); - } else { - foreach ($parents as $parentFolder) { - if ($parentFolder->getId() == $folder->getId()) { - $menuitem->setActive(true); - break; - } - } - } - } - $children[] = $menuitem; - } - } else if ('KunstmaanMediaBundle_folder_show' == $parent->getRoute()) { - $parentRouteParams = $parent->getRouteparams(); - /* @var Folder $parentFolder */ - $parentFolder = $this->em->getRepository('KunstmaanMediaBundle:Folder')->findOneById($parentRouteParams['folderId']); - /* @var Folder[] $galleries */ - $galleries = $parentFolder->getChildren(); - $currentId = $request->get('folderId'); - + // Add menu item for root gallery + $rootFolders = $this->repo->getRootNodes(); + $currentId = $request->get('folderId'); + $currentFolder = null; if (isset($currentId)) { /* @var Folder $currentFolder */ - $currentFolder = $this->em->getRepository('KunstmaanMediaBundle:Folder')->findOneById($currentId); - } else if (in_array($request->attributes->get('_route'), $mediaRoutes)) { - $media = $this->em->getRepository('KunstmaanMediaBundle:Media')->getMedia($request->get('mediaId')); - $currentFolder = $media->getFolder(); - } else if (in_array($request->attributes->get('_route'), $createRoutes)) { - $currentId = $request->get('folderId'); - if (isset($currentId)) { - $currentFolder = $this->em->getRepository('KunstmaanMediaBundle:Folder')->findOneById($currentId); - } + $currentFolder = $this->repo->find($currentId); } - /* @var Folder[] $parentGalleries */ - $parentGalleries = null; - if (isset($currentFolder)) { - $parentGalleries = $currentFolder->getParents(); - } else { - $parentGalleries = array(); - } - - foreach ($galleries as $folder) { - $menuitem = new MenuItem($menu); - $menuitem->setRoute('KunstmaanMediaBundle_folder_show'); - $menuitem->setRouteparams(array('folderId' => $folder->getId())); - $menuitem->setInternalname($folder->getName()); - $menuitem->setParent($parent); - $menuitem->setRole($folder->getRel()); - if (isset($currentFolder) && (stripos($request->attributes->get('_route'), $menuitem->getRoute()) === 0 || in_array($request->attributes->get('_route'), $allRoutes))) { - if ($currentFolder->getId() == $folder->getId()) { - $menuitem->setActive(true); - } else { - foreach ($parentGalleries as $parentFolder) { - if ($parentFolder->getId() == $folder->getId()) { - $menuitem->setActive(true); - break; - } - } + /** @var Folder $rootFolder */ + foreach ($rootFolders as $rootFolder) { + $menuItem = new TopMenuItem($menu); + $menuItem + ->setRoute('KunstmaanMediaBundle_folder_show') + ->setRouteparams(array('folderId' => $rootFolder->getId())) + ->setInternalname($rootFolder->getName()) + ->setParent(null) + ->setRole($rootFolder->getRel()); + + if (!is_null($currentFolder)) { + $parentIds = $this->repo->getParentIds($currentFolder); + if (in_array($rootFolder->getId(), $parentIds)) { + $menuItem->setActive(true); } } - $children[] = $menuitem; + $children[] = $menuItem; } - - foreach ($allRoutes as $name => $route) { - $menuitem = new MenuItem($menu); - $menuitem->setRoute($route); - $menuitem->setInternalname($name); - $menuitem->setParent($parent); - $menuitem->setAppearInNavigation(false); - if (stripos($request->attributes->get('_route'), $menuitem->getRoute()) === 0) { - if (stripos($menuitem->getRoute(), 'KunstmaanMediaBundle_media_show') === 0) { - /* @var Media $media */ - $media = $this->em->getRepository('KunstmaanMediaBundle:Media')->getMedia($request->get('mediaId')); - $menuitem->setInternalname('Show ' . $media->getClassType() . ' ' . $media->getName()); - } - $menuitem->setActive(true); - } - - $children[] = $menuitem; - } - } - } } \ No newline at end of file diff --git a/Helper/Remote/AbstractRemoteHelper.php b/Helper/Remote/AbstractRemoteHelper.php index 97016f81..a974c682 100644 --- a/Helper/Remote/AbstractRemoteHelper.php +++ b/Helper/Remote/AbstractRemoteHelper.php @@ -2,6 +2,7 @@ namespace Kunstmaan\MediaBundle\Helper\Remote; +use Kunstmaan\MediaBundle\Entity\Folder; use Kunstmaan\MediaBundle\Entity\Media; /** @@ -142,4 +143,19 @@ public function setType($type) return $this; } + /** + * @return Folder + */ + public function getFolder() + { + return $this->media->getFolder(); + } + + /** + * @param Folder $folder + */ + public function setFolder(Folder $folder) + { + $this->media->setFolder($folder); + } } diff --git a/Helper/Remote/RemoteInterface.php b/Helper/Remote/RemoteInterface.php index 1b97f181..a38770bd 100644 --- a/Helper/Remote/RemoteInterface.php +++ b/Helper/Remote/RemoteInterface.php @@ -4,16 +4,75 @@ interface RemoteInterface { + /** + * @return string + */ public function getName(); + + /** + * @param string $name + * + * @return mixed + */ public function setName($name); + + /** + * @return string + */ public function getCopyright(); + + /** + * @param string $copyright + * + * @return mixed + */ public function setCopyright($copyright); + + /** + * @return string + */ public function getDescription(); + + /** + * @param string $description + * + * @return mixed + */ public function setDescription($description); + + /** + * @return string + */ public function getCode(); + + /** + * @param string $code + * + * @return mixed + */ public function setCode($code); + + /** + * @return string + */ public function getThumbnailUrl(); + + /** + * @param string $url + * + * @return mixed + */ public function setThumbnailUrl($url); + + /** + * @return string + */ public function getType(); + + /** + * @param string $type + * + * @return mixed + */ public function setType($type); -} \ No newline at end of file +} diff --git a/Helper/RemoteAudio/RemoteAudioHandler.php b/Helper/RemoteAudio/RemoteAudioHandler.php index f8e50cbe..514cea06 100644 --- a/Helper/RemoteAudio/RemoteAudioHandler.php +++ b/Helper/RemoteAudio/RemoteAudioHandler.php @@ -20,7 +20,7 @@ class RemoteAudioHandler extends AbstractMediaHandler /** * @var string */ - const CONTENT_TYPE = "remote/audio"; + const CONTENT_TYPE = 'remote/audio'; /** * @var string @@ -37,7 +37,7 @@ public function __construct($soundcloudApiKey) */ public function getName() { - return "Remote Audio Handler"; + return 'Remote Audio Handler'; } /** @@ -71,7 +71,10 @@ public function getSoundcloudApiKey() */ public function canHandle($object) { - if ((is_string($object)) || ($object instanceof Media && $object->getContentType() == RemoteAudioHandler::CONTENT_TYPE)) { + if ( + (is_string($object)) || + ($object instanceof Media && $object->getContentType() == RemoteAudioHandler::CONTENT_TYPE) + ) { return true; } @@ -100,16 +103,20 @@ public function prepareMedia(Media $media) $media->setUuid($uuid); } $audio = new RemoteAudioHelper($media); - $code = $audio->getCode(); + $code = $audio->getCode(); //update thumbnail - switch($audio->getType()) { + switch ($audio->getType()) { case 'soundcloud': /** * todo: use resolve url so you can use the track url instead of the track code */ - $scData = json_decode(file_get_contents("http://api.soundcloud.com/tracks/" . $code . ".json?client_id=" . $this->getSoundcloudApiKey())); + $scData = json_decode( + file_get_contents( + 'http://api.soundcloud.com/tracks/' . $code . '.json?client_id=' . $this->getSoundcloudApiKey() + ) + ); $artworkUrl = $scData->artwork_url; $artworkUrl = str_replace('large.jpg', 't500x500.jpg', $artworkUrl); $audio->setThumbnailUrl($artworkUrl); @@ -173,10 +180,10 @@ public function getImageUrl(Media $media, $basepath) public function getAddFolderActions() { return array( - RemoteAudioHandler::TYPE => array( - 'type' => RemoteAudioHandler::TYPE, - 'name' => 'media.audio.add') - ); + RemoteAudioHandler::TYPE => array( + 'type' => RemoteAudioHandler::TYPE, + 'name' => 'media.audio.add' + ) + ); } - -} \ No newline at end of file +} diff --git a/Helper/RemoteSlide/RemoteSlideHandler.php b/Helper/RemoteSlide/RemoteSlideHandler.php index 544446ec..eae22222 100644 --- a/Helper/RemoteSlide/RemoteSlideHandler.php +++ b/Helper/RemoteSlide/RemoteSlideHandler.php @@ -15,7 +15,7 @@ class RemoteSlideHandler extends AbstractMediaHandler /** * @var string */ - const CONTENT_TYPE = "remote/slide"; + const CONTENT_TYPE = 'remote/slide'; const TYPE = 'slide'; @@ -24,7 +24,7 @@ class RemoteSlideHandler extends AbstractMediaHandler */ public function getName() { - return "Remote Slide Handler"; + return 'Remote Slide Handler'; } /** @@ -50,7 +50,10 @@ public function getFormType() */ public function canHandle($object) { - if ((is_string($object)) || ($object instanceof Media && $object->getContentType() == RemoteSlideHandler::CONTENT_TYPE)) { + if ( + (is_string($object)) || + ($object instanceof Media && $object->getContentType() == RemoteSlideHandler::CONTENT_TYPE) + ) { return true; } @@ -79,14 +82,19 @@ public function prepareMedia(Media $media) $media->setUuid($uuid); } $slide = new RemoteSlideHelper($media); - $code = $slide->getCode(); - //update thumbnail + $code = $slide->getCode(); + // update thumbnail switch ($slide->getType()) { case 'slideshare': try { - $json = json_decode(file_get_contents('http://www.slideshare.net/api/oembed/2?url=http://www.slideshare.net/slideshow/embed_code/'.$code.'&format=json')); - $slide->setThumbnailUrl('http:'.$json->thumbnail); + $json = json_decode( + file_get_contents( + 'http://www.slideshare.net/api/oembed/2?url=http://www.slideshare.net/slideshow/embed_code/' . $code . '&format=json' + ) + ); + $slide->setThumbnailUrl($json->thumbnail); } catch (\ErrorException $e) { + // Silent exception - should not bubble up since failure to create a thumbnail is not a fatal error } break; } @@ -122,12 +130,12 @@ public function updateMedia(Media $media) public function getAddUrlFor(array $params = array()) { return array( - 'slide' => array( - 'path' => 'KunstmaanMediaBundle_folder_slidecreate', - 'params' => array( - 'folderId' => $params['folderId'] - ) + 'slide' => array( + 'path' => 'KunstmaanMediaBundle_folder_slidecreate', + 'params' => array( + 'folderId' => $params['folderId'] ) + ) ); } @@ -138,19 +146,21 @@ public function getAddUrlFor(array $params = array()) */ public function createNew($data) { - $result = null; + $result = null; if (is_string($data)) { if (strpos($data, 'http') !== 0) { - $data = "http://" . $data; + $data = 'http://' . $data; } $parsedUrl = parse_url($data); - switch($parsedUrl['host']) { + switch ($parsedUrl['host']) { case 'www.slideshare.net': case 'slideshare.net': $result = new Media(); - $slide = new RemoteSlideHelper($result); + $slide = new RemoteSlideHelper($result); $slide->setType('slideshare'); - $json = json_decode(file_get_contents('http://www.slideshare.net/api/oembed/2?url='.$data.'&format=json')); + $json = json_decode( + file_get_contents('http://www.slideshare.net/api/oembed/2?url=' . $data . '&format=json') + ); $slide->setCode($json->{"slideshow_id"}); $result = $slide->getMedia(); $result->setName('SlideShare ' . $data); @@ -188,9 +198,10 @@ public function getImageUrl(Media $media, $basepath) public function getAddFolderActions() { return array( - RemoteSlideHandler::TYPE => array( - 'type' => RemoteSlideHandler::TYPE, - 'name' => 'media.slide.add') - ); + RemoteSlideHandler::TYPE => array( + 'type' => RemoteSlideHandler::TYPE, + 'name' => 'media.slide.add' + ) + ); } } \ No newline at end of file diff --git a/Helper/RemoteVideo/RemoteVideoHandler.php b/Helper/RemoteVideo/RemoteVideoHandler.php index 6e0e712f..57c2a1e8 100644 --- a/Helper/RemoteVideo/RemoteVideoHandler.php +++ b/Helper/RemoteVideo/RemoteVideoHandler.php @@ -15,7 +15,7 @@ class RemoteVideoHandler extends AbstractMediaHandler /** * @var string */ - const CONTENT_TYPE = "remote/video"; + const CONTENT_TYPE = 'remote/video'; /** * @var string @@ -27,7 +27,7 @@ class RemoteVideoHandler extends AbstractMediaHandler */ public function getName() { - return "Remote Video Handler"; + return 'Remote Video Handler'; } /** @@ -53,7 +53,10 @@ public function getFormType() */ public function canHandle($object) { - if ((is_string($object)) || ($object instanceof Media && $object->getContentType() == RemoteVideoHandler::CONTENT_TYPE)) { + if ( + (is_string($object)) || + ($object instanceof Media && $object->getContentType() == RemoteVideoHandler::CONTENT_TYPE) + ) { return true; } @@ -82,22 +85,24 @@ public function prepareMedia(Media $media) $media->setUuid($uuid); } $video = new RemoteVideoHelper($media); - $code = $video->getCode(); + $code = $video->getCode(); //update thumbnail - switch($video->getType()) { + switch ($video->getType()) { case 'youtube': - $video->setThumbnailUrl("http://img.youtube.com/vi/" . $code . "/0.jpg"); + $video->setThumbnailUrl('http://img.youtube.com/vi/' . $code . '/0.jpg'); break; case 'vimeo': - $xml = simplexml_load_file("http://vimeo.com/api/v2/video/".$code.".xml"); + $xml = simplexml_load_file('http://vimeo.com/api/v2/video/' . $code . '.xml'); $video->setThumbnailUrl((string) $xml->video->thumbnail_large); break; case 'dailymotion': - $json = json_decode(file_get_contents("https://api.dailymotion.com/video/".$code."?fields=thumbnail_large_url")); - $thumbnailUrl = $json->{"thumbnail_large_url"}; + $json = json_decode( + file_get_contents('https://api.dailymotion.com/video/' . $code . '?fields=thumbnail_large_url') + ); + $thumbnailUrl = $json->{'thumbnail_large_url'}; /* dirty hack to fix urls for imagine */ if (!$this->endsWith($thumbnailUrl, '.jpg') && !$this->endsWith($thumbnailUrl, '.png')) { - $thumbnailUrl = $thumbnailUrl.'&ext=.jpg'; + $thumbnailUrl = $thumbnailUrl . '&ext=.jpg'; } $video->setThumbnailUrl($thumbnailUrl); break; @@ -106,12 +111,13 @@ public function prepareMedia(Media $media) /** * String helper + * * @param string $str string * @param string $sub substring * * @return boolean */ - private function endsWith( $str, $sub ) + private function endsWith($str, $sub) { return substr($str, strlen($str) - strlen($sub)) === $sub; } @@ -147,12 +153,12 @@ public function updateMedia(Media $media) public function getAddUrlFor(array $params = array()) { return array( - 'video' => array( - 'path' => 'KunstmaanMediaBundle_folder_videocreate', - 'params' => array( - 'folderId' => $params['folderId'] - ) + 'video' => array( + 'path' => 'KunstmaanMediaBundle_folder_videocreate', + 'params' => array( + 'folderId' => $params['folderId'] ) + ) ); } @@ -166,16 +172,16 @@ public function createNew($data) $result = null; if (is_string($data)) { if (strpos($data, 'http') !== 0) { - $data = "http://" . $data; + $data = 'http://' . $data; } $parsedUrl = parse_url($data); - switch($parsedUrl['host']) { + switch ($parsedUrl['host']) { case 'www.youtube.com': case 'youtube.com': parse_str($parsedUrl['query'], $queryFields); - $code = $queryFields['v']; + $code = $queryFields['v']; $result = new Media(); - $video = new RemoteVideoHelper($result); + $video = new RemoteVideoHelper($result); $video->setType('youtube'); $video->setCode($code); $result = $video->getMedia(); @@ -183,9 +189,9 @@ public function createNew($data) break; case 'www.vimeo.com': case 'vimeo.com': - $code = substr($parsedUrl['path'], 1); + $code = substr($parsedUrl['path'], 1); $result = new Media(); - $video = new RemoteVideoHelper($result); + $video = new RemoteVideoHelper($result); $video->setType('vimeo'); $video->setCode($code); $result = $video->getMedia(); @@ -193,9 +199,9 @@ public function createNew($data) break; case 'www.dailymotion.com': case 'dailymotion.com': - $code = substr($parsedUrl['path'], 7); + $code = substr($parsedUrl['path'], 7); $result = new Media(); - $video = new RemoteVideoHelper($result); + $video = new RemoteVideoHelper($result); $video->setType('dailymotion'); $video->setCode($code); $result = $video->getMedia(); @@ -234,10 +240,10 @@ public function getImageUrl(Media $media, $basepath) public function getAddFolderActions() { return array( - RemoteVideoHandler::TYPE => array( - 'type' => RemoteVideoHandler::TYPE, - 'name' => 'media.video.add') - ); + RemoteVideoHandler::TYPE => array( + 'type' => RemoteVideoHandler::TYPE, + 'name' => 'media.video.add' + ) + ); } - } \ No newline at end of file diff --git a/Helper/Transformer/PdfTransformer.php b/Helper/Transformer/PdfTransformer.php new file mode 100644 index 00000000..24d47e2f --- /dev/null +++ b/Helper/Transformer/PdfTransformer.php @@ -0,0 +1,52 @@ +imagick = $imagick; + } + + /** + * Apply the transformer on the absolute path and return an altered version of it. + * + * @param string $absolutePath + * + * @return string|false + */ + public function apply($absolutePath) + { + $info = pathinfo($absolutePath); + + if (isset($info['extension']) && false !== strpos(strtolower($info['extension']), 'pdf')) { + // If it doesn't exist yet, extract the first page of the PDF + $previewFilename = $this->getPreviewFilename($absolutePath); + if (!file_exists($previewFilename)) { + $this->imagick->readImage($absolutePath.'[0]'); + $this->imagick->setImageFormat('jpg'); + $this->imagick->flattenimages(); + $this->imagick->writeImage($previewFilename); + $this->imagick->clear(); + } + + $absolutePath = $previewFilename; + } + + return $absolutePath; + } + + /** + * @param string $absolutePath + * + * @return string + */ + public function getPreviewFilename($absolutePath) + { + return $absolutePath . '.jpg'; + } +} \ No newline at end of file diff --git a/Helper/Transformer/PreviewTransformerInterface.php b/Helper/Transformer/PreviewTransformerInterface.php new file mode 100644 index 00000000..9b347e4d --- /dev/null +++ b/Helper/Transformer/PreviewTransformerInterface.php @@ -0,0 +1,17 @@ + array('info_text' => 'YOUR TOOLTIP TEXT'), ) ); -``` \ No newline at end of file +``` + +## Generating PDF thumbnails + +For this functionality to work, you need to install the ImageMagick extension with PDF support (using +Ghostscript). You will also have to make sure that the Ghostscript executable (gs) can be found +in the path of the user that is executing the code (apache/www or a custom user depending on your setup). + +You can determine that path by running ```which gs``` on the command line in Linux/OS X. + +To install Ghostscript on Mac OS X you can use ```brew install gs```. + +On OS X with apache you will probably have to add that path to the apache environment settings in +```/System/Library/LaunchDaemons/org.apache.httpd.plist```. Make sure it contains the following : +``` +EnvironmentVariables + + PATH + /usr/bin:/bin:/usr/sbin:/sbin:/path/to/gs + +``` + +Where ```/path/to/gs``` is just the actual path where the gs binary is stored. diff --git a/Repository/FolderRepository.php b/Repository/FolderRepository.php index e6ae3e52..a9edf5a8 100644 --- a/Repository/FolderRepository.php +++ b/Repository/FolderRepository.php @@ -2,24 +2,65 @@ namespace Kunstmaan\MediaBundle\Repository; -use Kunstmaan\MediaBundle\Entity\Folder; -use Doctrine\ORM\EntityRepository; use Doctrine\ORM\EntityNotFoundException; +use Doctrine\ORM\Query; +use Doctrine\ORM\QueryBuilder; +use Gedmo\Tree\Entity\Repository\NestedTreeRepository; +use Kunstmaan\MediaBundle\Entity\Folder; +use Kunstmaan\MediaBundle\Entity\Media; /** * FolderRepository + * + * @method FolderRepository persistAsFirstChild(object $node) + * @method FolderRepository persistAsFirstChildOf(object $node, object $parent) + * @method FolderRepository persistAsLastChild(object $node) + * @method FolderRepository persistAsLastChildOf(object $node, object $parent) + * @method FolderRepository persistAsNextSibling(object $node) + * @method FolderRepository persistAsNextSiblingOf(object $node, object $sibling) + * @method FolderRepository persistAsPrevSibling(object $node) + * @method FolderRepository persistAsPrevSiblingOf(object $node, object $sibling) */ -class FolderRepository extends EntityRepository +class FolderRepository extends NestedTreeRepository { /** * @param Folder $folder The folder + * + * @throws \Exception */ public function save(Folder $folder) { - $em = $this->getEntityManager(); + $em = $this->getEntityManager(); + $parent = $folder->getParent(); - $em->persist($folder); - $em->flush(); + $em->beginTransaction(); + try { + // Find where to insert the new item + $children = $parent->getChildren(true); + if ($children->isEmpty()) { + // No children yet - insert as first child + $this->persistAsFirstChildOf($folder, $parent); + } else { + $previousChild = null; + foreach ($children as $child) { + // Alphabetical sorting - could be nice if we implemented a sorting strategy + if (strcasecmp($folder->getName(), $child->getName()) < 0) { + break; + } + $previousChild = $child; + } + if (is_null($previousChild)) { + $this->persistAsPrevSiblingOf($folder, $children[0]); + } else { + $this->persistAsNextSiblingOf($folder, $previousChild); + } + } + $em->commit(); + $em->flush(); + } catch (\Exception $e) { + $em->rollback(); + throw $e; + } } /** @@ -29,8 +70,8 @@ public function delete(Folder $folder) { $em = $this->getEntityManager(); - $this->deleteMedia($folder, $em); - $this->deleteChildren($folder, $em); + $this->deleteMedia($folder); + $this->deleteChildren($folder); $folder->setDeleted(true); $em->persist($folder); $em->flush(); @@ -39,27 +80,28 @@ public function delete(Folder $folder) /** * @param Folder $folder */ - public function deleteMedia(Folder $folder) + private function deleteMedia(Folder $folder) { $em = $this->getEntityManager(); - foreach ($folder->getMedia() as $item) { - $item->setDeleted(true); - $em->persist($item); - $em->remove($item); + /** @var Media $media */ + foreach ($folder->getMedia() as $media) { + $media->setDeleted(true); + $em->persist($media); } } /** * @param Folder $folder */ - public function deleteChildren(Folder $folder) + private function deleteChildren(Folder $folder) { $em = $this->getEntityManager(); + /** @var Folder $child */ foreach ($folder->getChildren() as $child) { - $this->deleteMedia($child, $em); - $this->deleteChildren($child, $em); + $this->deleteMedia($child); + $this->deleteChildren($child); $child->setDeleted(true); $em->persist($child); } @@ -73,9 +115,9 @@ public function deleteChildren(Folder $folder) public function getAllFolders($limit = null) { $qb = $this->createQueryBuilder('folder') - ->select('folder') - ->where('folder.parent is null AND folder.deleted != true') - ->orderBy('folder.name'); + ->select('folder') + ->where('folder.parent is null AND folder.deleted != true') + ->orderBy('folder.name'); if (false === is_null($limit)) { $qb->setMaxResults($limit); @@ -94,7 +136,7 @@ public function getFolder($folderId) { $folder = $this->find($folderId); if (!$folder) { - throw new EntityNotFoundException('The id given for the folder is not valid.'); + throw new EntityNotFoundException(); } return $folder; @@ -104,10 +146,191 @@ public function getFirstTopFolder() { $folder = $this->findOneBy(array('parent' => null)); if (!$folder) { - throw new EntityNotFoundException('No first top folder found (where parent is NULL)'); + throw new EntityNotFoundException(); } return $folder; } + public function getParentIds(Folder $folder) + { + /** @var QueryBuilder $qb */ + $qb = $this->getPathQueryBuilder($folder) + ->select('node.id'); + + $result = $qb->getQuery()->getScalarResult(); + $ids = array_map('current', $result); + + return $ids; + } + + /** + * {@inheritdoc} + */ + public function getRootNodesQueryBuilder($sortByField = null, $direction = 'asc') + { + /** @var QueryBuilder $qb */ + $qb = parent::getRootNodesQueryBuilder($sortByField, $direction); + $qb->andWhere('node.deleted != true'); + + return $qb; + } + + /** + * {@inheritdoc} + */ + public function getPathQueryBuilder($node) + { + /** @var QueryBuilder $qb */ + $qb = parent::getPathQueryBuilder($node); + $qb->andWhere('node.deleted != true'); + + return $qb; + } + + /** + * {@inheritdoc} + */ + public function childrenQueryBuilder( + $node = null, + $direct = false, + $sortByField = null, + $direction = 'ASC', + $includeNode = false + ) { + /** @var QueryBuilder $qb */ + $qb = parent::childrenQueryBuilder($node, $direct, $sortByField, $direction, $includeNode); + $qb->andWhere('node.deleted != true'); + + return $qb; + } + + /** + * {@inheritdoc} + */ + public function getLeafsQueryBuilder($root = null, $sortByField = null, $direction = 'ASC') + { + /** @var QueryBuilder $qb */ + $qb = parent::getLeafsQueryBuilder($root, $sortByField, $direction); + $qb->andWhere('node.deleted != true'); + + return $qb; + } + + /** + * {@inheritdoc} + */ + public function getNextSiblingsQueryBuilder($node, $includeSelf = false) + { + /** @var QueryBuilder $qb */ + $qb = parent::getNextSiblingsQueryBuilder($node, $includeSelf); + $qb->andWhere('node.deleted != true'); + + return $qb; + } + + /** + * {@inheritdoc} + */ + public function getPrevSiblingsQueryBuilder($node, $includeSelf = false) + { + /** @var QueryBuilder $qb */ + $qb = parent::getPrevSiblingsQueryBuilder($node, $includeSelf); + $qb->andWhere('node.deleted != true'); + + return $qb; + } + + /** + * {@inheritdoc} + */ + public function getNodesHierarchyQueryBuilder( + $node = null, + $direct = false, + array $options = array(), + $includeNode = false + ) { + /** @var QueryBuilder $qb */ + $qb = parent::getNodesHierarchyQueryBuilder($node, $direct, $options, $includeNode); + $qb->andWhere('node.deleted != true'); + + return $qb; + } + + /** + * {@inheritdoc} + */ + public function getNodesHierarchy($node = null, $direct = false, array $options = array(), $includeNode = false) + { + $query = $this->getNodesHierarchyQuery($node, $direct, $options, $includeNode); + $query->setHint( + Query::HINT_CUSTOM_OUTPUT_WALKER, + 'Gedmo\\Translatable\\Query\\TreeWalker\\TranslationWalker' + ); + + return $query->getArrayResult(); + } + + /** + * Rebuild the nested tree + */ + public function rebuildTree() + { + $em = $this->getEntityManager(); + + // Reset tree... + $sql = 'UPDATE kuma_folders SET lvl=NULL,lft=NULL,rgt=NULL'; + $stmt = $em->getConnection()->prepare($sql); + $stmt->execute(); + + $folders = $this->findBy(array(), array('parent' => 'ASC', 'name' => 'asc')); + + $rootFolder = $folders[0]; + $first = true; + foreach ($folders as $folder) { + // Force parent load + $parent = $folder->getParent(); + if (is_null($parent)) { + $folder->setLevel(0); + if ($first) { + $this->persistAsFirstChild($folder); + $first = false; + } else { + $this->persistAsNextSiblingOf($folder, $rootFolder); + } + } else { + $folder->setLevel($parent->getLevel() + 1); + $this->persistAsLastChildOf($folder, $parent); + } + } + $em->flush(); + } + + /** + * Used as querybuilder for Folder entity selectors + * + * @param Folder $ignoreSubtree Folder (with children) that has to be filtered out (optional) + * + * @return QueryBuilder + */ + public function selectFolderQueryBuilder(Folder $ignoreSubtree = null) + { + /** @var QueryBuilder $qb */ + $qb = $this->createQueryBuilder('f'); + $qb->where('f.deleted != true') + ->orderBy('f.lft'); + + // Fetch all folders except the current one and its children + if (!is_null($ignoreSubtree) && $ignoreSubtree->getId() !== null) { + $orX = $qb->expr()->orX(); + $orX->add('f.rgt > :right') + ->add('f.lft < :left'); + + $qb->andWhere($orX) + ->setParameter('left', $ignoreSubtree->getLeft()) + ->setParameter('right', $ignoreSubtree->getRight()); + } + + return $qb; + } } \ No newline at end of file diff --git a/Repository/MediaRepository.php b/Repository/MediaRepository.php index e00120ac..78a82f0b 100644 --- a/Repository/MediaRepository.php +++ b/Repository/MediaRepository.php @@ -17,7 +17,6 @@ class MediaRepository extends EntityRepository public function save(Media $media) { $em = $this->getEntityManager(); - $em->persist($media); $em->flush(); } @@ -28,7 +27,6 @@ public function save(Media $media) public function delete(Media $media) { $em = $this->getEntityManager(); - $media->setDeleted(true); $em->persist($media); $em->flush(); @@ -44,7 +42,7 @@ public function getMedia($mediaId) { $media = $this->find($mediaId); if (!$media) { - throw new EntityNotFoundException('The id given for the media is not valid.'); + throw new EntityNotFoundException(); } return $media; @@ -62,7 +60,7 @@ public function getPicture($pictureId) $picture = $em->getRepository('KunstmaanMediaBundle:Image')->find($pictureId); if (!$picture) { - throw new EntityNotFoundException('Unable to find image.'); + throw new EntityNotFoundException(); } return $picture; diff --git a/Resources/config/handlers.yml b/Resources/config/handlers.yml index c779ba21..375ca9ba 100644 --- a/Resources/config/handlers.yml +++ b/Resources/config/handlers.yml @@ -1,34 +1,48 @@ -parameters: - kunstmaan_media.media_handler.remote_slide.class: 'Kunstmaan\MediaBundle\Helper\RemoteSlide\RemoteSlideHandler' - kunstmaan_media.media_handler.remote_video.class: 'Kunstmaan\MediaBundle\Helper\RemoteVideo\RemoteVideoHandler' - kunstmaan_media.media_handler.remote_audio.class: 'Kunstmaan\MediaBundle\Helper\RemoteAudio\RemoteAudioHandler' - kunstmaan_media.media_handler.image.class: 'Kunstmaan\MediaBundle\Helper\Image\ImageHandler' - kunstmaan_media.media_handler.file.class: 'Kunstmaan\MediaBundle\Helper\File\FileHandler' - aviary_api_key: null -services: - kunstmaan_media.media_handlers.remote_slide: - class: "%kunstmaan_media.media_handler.remote_slide.class%" - tags: - - { name: 'kunstmaan_media.media_handler' } - kunstmaan_media.media_handlers.remote_video: - class: "%kunstmaan_media.media_handler.remote_video.class%" - tags: - - { name: 'kunstmaan_media.media_handler' } - kunstmaan_media.media_handlers.remote_audio: - class: "%kunstmaan_media.media_handler.remote_audio.class%" - arguments: ["%kunstmaan_media.soundcloud_api_key%"] - tags: - - { name: 'kunstmaan_media.media_handler' } - kunstmaan_media.media_handlers.image: - class: "%kunstmaan_media.media_handler.image.class%" - arguments: ["%aviary_api_key%"] - calls: - - [ setMediaPath, [ "%kernel.root_dir%" ] ] - tags: - - { name: 'kunstmaan_media.media_handler' } - kunstmaan_media.media_handlers.file: - class: "%kunstmaan_media.media_handler.file.class%" - calls: - - [ setMediaPath, [ "%kernel.root_dir%" ] ] - tags: - - { name: 'kunstmaan_media.media_handler' } +parameters: + kunstmaan_media.media_handler.remote_slide.class: 'Kunstmaan\MediaBundle\Helper\RemoteSlide\RemoteSlideHandler' + kunstmaan_media.media_handler.remote_video.class: 'Kunstmaan\MediaBundle\Helper\RemoteVideo\RemoteVideoHandler' + kunstmaan_media.media_handler.remote_audio.class: 'Kunstmaan\MediaBundle\Helper\RemoteAudio\RemoteAudioHandler' + kunstmaan_media.media_handler.image.class: 'Kunstmaan\MediaBundle\Helper\Image\ImageHandler' + kunstmaan_media.media_handler.file.class: 'Kunstmaan\MediaBundle\Helper\File\FileHandler' + kunstmaan_media.media_handler.pdf.class: 'Kunstmaan\MediaBundle\Helper\File\PdfHandler' + aviary_api_key: null + +services: + kunstmaan_media.media_handlers.remote_slide: + class: "%kunstmaan_media.media_handler.remote_slide.class%" + tags: + - { name: 'kunstmaan_media.media_handler' } + + kunstmaan_media.media_handlers.remote_video: + class: "%kunstmaan_media.media_handler.remote_video.class%" + tags: + - { name: 'kunstmaan_media.media_handler' } + + kunstmaan_media.media_handlers.remote_audio: + class: "%kunstmaan_media.media_handler.remote_audio.class%" + arguments: ["%kunstmaan_media.soundcloud_api_key%"] + tags: + - { name: 'kunstmaan_media.media_handler' } + + kunstmaan_media.media_handlers.image: + class: "%kunstmaan_media.media_handler.image.class%" + arguments: ["%aviary_api_key%"] + calls: + - [ setMediaPath, [ "%kernel.root_dir%" ] ] + tags: + - { name: 'kunstmaan_media.media_handler' } + + kunstmaan_media.media_handlers.file: + class: "%kunstmaan_media.media_handler.file.class%" + calls: + - [ setMediaPath, [ "%kernel.root_dir%" ] ] + tags: + - { name: 'kunstmaan_media.media_handler' } + + kunstmaan_media.media_handlers.pdf: + class: "%kunstmaan_media.media_handler.pdf.class%" + calls: + - [ setMediaPath, [ "%kernel.root_dir%" ] ] + - [ setPdfTransformer, [ "@kunstmaan_media.pdf_transformer" ]] + tags: + - { name: 'kunstmaan_media.media_handler' } diff --git a/Resources/config/liip_imagine.yml b/Resources/config/liip_imagine.yml index 0609c7ca..9ab44d01 100644 --- a/Resources/config/liip_imagine.yml +++ b/Resources/config/liip_imagine.yml @@ -7,7 +7,7 @@ liip_imagine: media_list_thumbnail: quality: 75 filters: - thumbnail: { size: [210, 150], mode: outbound } + thumbnail: { size: [210, 150], mode: inset } media_detail_thumbnail: quality: 75 filters: diff --git a/Resources/config/services.yml b/Resources/config/services.yml index 168c0069..4104d31a 100644 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -1,5 +1,6 @@ parameters: kunstmaan_media.media_manager.class: 'Kunstmaan\MediaBundle\Helper\MediaManager' + kunstmaan_media.folder_manager.class: 'Kunstmaan\MediaBundle\Helper\FolderManager' kunstmaan_media.menu.adaptor.class: 'Kunstmaan\MediaBundle\Helper\Menu\MediaMenuAdaptor' kunstmaan_media.listener.doctrine.class: 'Kunstmaan\MediaBundle\EventListener\DoctrineMediaListener' kunstmaan_media.form.type.media.class: 'Kunstmaan\MediaBundle\Form\Type\MediaType' @@ -7,6 +8,7 @@ parameters: kunstmaan_media.icon_font_manager.class: 'Kunstmaan\MediaBundle\Helper\IconFont\IconFontManager' kunstmaan_media.icon_font.default_loader.class: 'Kunstmaan\MediaBundle\Helper\IconFont\DefaultIconFontLoader' kunstmaan_media.media_creator_service.class: 'Kunstmaan\MediaBundle\Helper\Services\MediaCreatorService' + kunstmaan_media.pdf_transformer.class: 'Kunstmaan\MediaBundle\Helper\Transformer\PdfTransformer' services: liip_imagine.data.loader.stream.profile_photos: @@ -22,12 +24,6 @@ services: calls: - [ setDefaultHandler, [ "@kunstmaan_media.media_handlers.file" ] ] - kunstmaan_media.menu.adaptor: - class: "%kunstmaan_media.menu.adaptor.class%" - arguments: ["@doctrine.orm.entity_manager"] - tags: - - { name: 'kunstmaan_admin.menu.adaptor' } - kunstmaan_media.listener.doctrine: class: "%kunstmaan_media.listener.doctrine.class%" arguments: ["@kunstmaan_media.media_manager"] @@ -66,3 +62,26 @@ services: kunstmaan_media.media_creator_service: class: "%kunstmaan_media.media_creator_service.class%" arguments: ["@service_container"] + + kunstmaan_media.repository.folder: + class: Kunstmaan\MediaBundle\Repository\FolderRepository + factory_service: "doctrine.orm.entity_manager" + factory_method: getRepository + arguments: ["KunstmaanMediaBundle:Folder"] + + kunstmaan_media.menu.adaptor: + class: "%kunstmaan_media.menu.adaptor.class%" + arguments: ["@kunstmaan_media.repository.folder"] + tags: + - { name: 'kunstmaan_admin.menu.adaptor' } + + kunstmaan_media.folder_manager: + class: "%kunstmaan_media.folder_manager.class%" + arguments: ["@kunstmaan_media.repository.folder"] + + kunstmaan_media.imagick: + class: "Imagick" + + kunstmaan_media.pdf_transformer: + class: "%kunstmaan_media.pdf_transformer.class%" + arguments: ["@kunstmaan_media.imagick"] diff --git a/Resources/views/Chooser/chooserShowFolder.html.twig b/Resources/views/Chooser/chooserShowFolder.html.twig index 188101df..73342fc4 100644 --- a/Resources/views/Chooser/chooserShowFolder.html.twig +++ b/Resources/views/Chooser/chooserShowFolder.html.twig @@ -49,13 +49,14 @@ {% endblock %} diff --git a/Resources/views/Chooser/chooserShowRecTreeView.html.twig b/Resources/views/Chooser/chooserShowRecTreeView.html.twig index 03e9dc6d..31ab6056 100644 --- a/Resources/views/Chooser/chooserShowRecTreeView.html.twig +++ b/Resources/views/Chooser/chooserShowRecTreeView.html.twig @@ -1,10 +1,10 @@ -
  • - {{ child.name | trans }} - {% if child.children is defined %} +
  • + {{ mediaFolder.name }} + {% if mediaFolder.__children is not empty %} {% endif %} -
  • \ No newline at end of file + diff --git a/Resources/views/Folder/breadcrumbs.html.twig b/Resources/views/Folder/breadcrumbs.html.twig new file mode 100644 index 00000000..49131e61 --- /dev/null +++ b/Resources/views/Folder/breadcrumbs.html.twig @@ -0,0 +1,12 @@ +{% set breadcrumbitems = foldermanager.getParents(folder) %} +{% if breadcrumbitems | length > 0 %} + +{% endif %} diff --git a/Resources/views/Folder/foldertreeview.html.twig b/Resources/views/Folder/foldertreeview.html.twig new file mode 100644 index 00000000..2d937aaf --- /dev/null +++ b/Resources/views/Folder/foldertreeview.html.twig @@ -0,0 +1,10 @@ +
  • + {{ mediaFolder.name }} + {% if mediaFolder.__children is not empty %} + + {% endif %} +
  • diff --git a/Resources/views/Folder/show.html.twig b/Resources/views/Folder/show.html.twig index 99bdab5d..868373ab 100644 --- a/Resources/views/Folder/show.html.twig +++ b/Resources/views/Folder/show.html.twig @@ -1,6 +1,36 @@ {% extends 'KunstmaanAdminListBundle:Default:list.html.twig' %} -{% block extra_actions_header %}{% endblock %} +{% block header %} + +{% endblock %} + +{% block breadcrumb %} + {% include 'KunstmaanMediaBundle:Folder:breadcrumbs.html.twig' %} +{% endblock %} + +{% block sidebar %} + +{% endblock %} {% block content %}
    @@ -77,10 +107,12 @@
  • {% set handler = mediamanager.getHandler(media) %} - {% set thumbnailurl = handler.getImageUrl(media, app.request.basePath, 210, 150) %} {% set imageurl = handler.getImageUrl(media, app.request.basePath) %} + {% if imageurl is not empty and media.location == 'local' %} + {% set imageurl = imageurl | imagine_filter('media_list_thumbnail') %} + {% endif %} {% if imageurl %} - {{ media.name }} + {{ media.name }} {% else %}
    diff --git a/Resources/views/Media/create.html.twig b/Resources/views/Media/create.html.twig index c7963358..f6123f62 100644 --- a/Resources/views/Media/create.html.twig +++ b/Resources/views/Media/create.html.twig @@ -11,7 +11,7 @@ {% block content %} {% form_theme form 'KunstmaanAdminBundle:Form:fields.html.twig' %} -
    +
    {{ form_widget(form) }}
    diff --git a/Resources/views/Media/show.html.twig b/Resources/views/Media/show.html.twig index a63094e4..c10320ce 100644 --- a/Resources/views/Media/show.html.twig +++ b/Resources/views/Media/show.html.twig @@ -4,6 +4,31 @@

    {{ 'media.media.title'|trans }} {{ media.name }}

    {% endblock %} +{% block breadcrumb %} + {% include 'KunstmaanMediaBundle:Folder:breadcrumbs.html.twig' %} +{% endblock %} + +{% block sidebar %} + +{% endblock %} + {% block content %} diff --git a/Tests/Entity/FolderTest.php b/Tests/Entity/FolderTest.php index b6fbfeaa..c439cd0b 100644 --- a/Tests/Entity/FolderTest.php +++ b/Tests/Entity/FolderTest.php @@ -1,7 +1,9 @@ object = new Folder; + $this->object = new Folder(); } /** * @covers Kunstmaan\MediaBundle\Entity\Folder::getName * @covers Kunstmaan\MediaBundle\Entity\Folder::setName + * @covers Kunstmaan\MediaBundle\Entity\Folder::__toString */ public function testGetSetName() { - $this->object->setName('abc'); - $this->assertEquals('abc', $this->object->getName()); + $this->object->setName('name'); + $this->assertEquals('name', $this->object->getName()); + $this->assertEquals('name', $this->object->__toString()); } /** @@ -38,8 +43,8 @@ public function testGetSetName() */ public function testGetSetRel() { - $this->object->setRel('bcd'); - $this->assertEquals('bcd', $this->object->getRel()); + $this->object->setRel('rel'); + $this->assertEquals('rel', $this->object->getRel()); } /** @@ -80,12 +85,205 @@ public function testGetParent() * @covers Kunstmaan\MediaBundle\Entity\Folder::setDeleted * @covers Kunstmaan\MediaBundle\Entity\Folder::isDeleted */ - public function testSetDeleted() + public function testGetSetDeleted() { + $this->assertFalse($this->object->isDeleted()); + $this->object->setDeleted(true); - $this->assertEquals(true, $this->object->isDeleted()); - $this->object->setDeleted(false); - $this->assertEquals(false, $this->object->isDeleted()); + $this->assertTrue($this->object->isDeleted()); + } + + /** + * @covers Kunstmaan\MediaBundle\Entity\Folder::getTranslatableLocale + * @covers Kunstmaan\MediaBundle\Entity\Folder::setTranslatableLocale + */ + public function testGetSetTranslatableLocale() + { + $this->object->setTranslatableLocale('nl'); + $this->assertEquals('nl', $this->object->getTranslatableLocale()); + } + + /** + * @covers Kunstmaan\MediaBundle\Entity\Folder::getParents + */ + public function testGetParents() + { + $root = new Folder(); + $root->setId(1); + + $subFolder = new Folder(); + $subFolder->setId(2); + $subFolder->setParent($root); + + $subSubFolder = new Folder(); + $subSubFolder->setId(3); + $subSubFolder->setParent($subFolder); + + $parents = array($root, $subFolder); + $this->assertEquals($parents, $subSubFolder->getParents()); + } + + /** + * @covers Kunstmaan\MediaBundle\Entity\Folder::addChild + */ + public function testAddChild() + { + $this->assertCount(0, $this->object->getChildren()); + + $subFolder = new Folder(); + $subFolder->setId(2); + $this->object->addChild($subFolder); + + $this->assertCount(1, $this->object->getChildren()); + $this->assertEquals($this->object, $subFolder->getParent()); + } + + /** + * @covers Kunstmaan\MediaBundle\Entity\Folder::addMedia + */ + public function testAddMedia() + { + $this->assertCount(0, $this->object->getMedia()); + + $media = new Media(); + $this->object->addMedia($media); + + $this->assertCount(1, $this->object->getMedia()); + } + + /** + * @covers Kunstmaan\MediaBundle\Entity\Folder::getMedia + */ + public function testGetMedia() + { + $media = new Media(); + $this->object->addMedia($media); + + $deletedMedia = new Media(); + $deletedMedia->setDeleted(true); + $this->object->addMedia($deletedMedia); + + $this->assertCount(1, $this->object->getMedia()); + $this->assertCount(1, $this->object->getMedia(false)); + $this->assertCount(2, $this->object->getMedia(true)); + + $folderMedia = $this->object->getMedia(false); + $this->assertContains($media, $folderMedia); + $this->assertNotContains($deletedMedia, $folderMedia); + + $folderMedia = $this->object->getMedia(true); + $this->assertContains($media, $folderMedia); + $this->assertContains($deletedMedia, $folderMedia); + } + + /** + * @covers Kunstmaan\MediaBundle\Entity\Folder::getChildren + * @covers Kunstmaan\MediaBundle\Entity\Folder::setChildren + */ + public function testGetSetChildren() + { + $child = new Folder(); + + $deletedChild = new Folder(); + $deletedChild->setDeleted(true); + + $children = new ArrayCollection(); + $children->add($child); + $children->add($deletedChild); + + $this->object->setChildren($children); + + $this->assertCount(1, $this->object->getChildren()); + $this->assertCount(1, $this->object->getChildren(false)); + $this->assertCount(2, $this->object->getChildren(true)); + + $children = $this->object->getChildren(false); + $this->assertContains($child, $children); + $this->assertNotContains($deletedChild, $children); + + $children = $this->object->getChildren(true); + $this->assertContains($child, $children); + $this->assertContains($deletedChild, $children); + } + + /** + * @covers Kunstmaan\MediaBundle\Entity\Folder::hasActive + */ + public function testHasActive() + { + $root = new Folder(); + $root->setId(1); + + $subFolder = new Folder(); + $subFolder->setId(2); + $root->addChild($subFolder); + + $subFolder2 = new Folder(); + $subFolder2->setId(4); + $root->addChild($subFolder2); + + $subSubFolder = new Folder(); + $subSubFolder->setId(3); + $subFolder->addChild($subSubFolder); + + $this->assertTrue($root->hasActive(2)); + $this->assertTrue($root->hasActive(4)); + $this->assertTrue($subFolder->hasActive(3)); + $this->assertFalse($subFolder->hasActive(4)); + } + + /** + * @covers Kunstmaan\MediaBundle\Entity\Folder::getInternalName + * @covers Kunstmaan\MediaBundle\Entity\Folder::setInternalName + */ + public function testGetSetInternalName() + { + $this->object->setInternalName('internal_name'); + $this->assertEquals('internal_name', $this->object->getInternalName()); } + /** + * @covers Kunstmaan\MediaBundle\Entity\Folder::setLeft + * @covers Kunstmaan\MediaBundle\Entity\Folder::getLeft + */ + public function testGetSetLeft() + { + $this->assertEquals(0, $this->object->getLeft()); + $this->object->setLeft(1); + $this->assertEquals(1, $this->object->getLeft()); + } + + /** + * @covers Kunstmaan\MediaBundle\Entity\Folder::setRight + * @covers Kunstmaan\MediaBundle\Entity\Folder::getRight + */ + public function testGetSetRight() + { + $this->assertEquals(0, $this->object->getRight()); + $this->object->setRight(2); + $this->assertEquals(2, $this->object->getRight()); + } + + /** + * @covers Kunstmaan\MediaBundle\Entity\Folder::setLevel + * @covers Kunstmaan\MediaBundle\Entity\Folder::getLevel + */ + public function testGetSetLevel() + { + $this->assertEquals(0, $this->object->getLevel()); + $this->object->setLevel(1); + $this->assertEquals(1, $this->object->getLevel()); + } + + /** + * @covers Kunstmaan\MediaBundle\Entity\Folder::getOptionLabel + */ + public function testGetOptionLabel() + { + $this->object + ->setName('Test') + ->setLevel(2); + + $this->assertEquals('-- Test', $this->object->getOptionLabel()); + } } diff --git a/Tests/Entity/MediaTest.php b/Tests/Entity/MediaTest.php index 3eea0fe6..90680841 100644 --- a/Tests/Entity/MediaTest.php +++ b/Tests/Entity/MediaTest.php @@ -18,10 +18,11 @@ class MediaTest extends \PHPUnit_Framework_TestCase /** * Sets up the fixture, for example, opens a network connection. * This method is called before a test is executed. + * @covers Kunstmaan\MediaBundle\Entity\Media::__construct */ protected function setUp() { - $this->object = new Media; + $this->object = new Media(); } /** @@ -40,6 +41,10 @@ public function testSetFileSize() { $this->object->setFileSize(45); $this->assertEquals('45b', $this->object->getFileSize()); + $this->object->setFileSize(64 * 1024); + $this->assertEquals('64kb', $this->object->getFileSize()); + $this->object->setFileSize(64 * 1024 * 1024); + $this->assertEquals('64mb', $this->object->getFileSize()); } /** @@ -58,8 +63,8 @@ public function testGetSetUuid() */ public function testGetSetName() { - $this->object->setName('bcd'); - $this->assertEquals('bcd', $this->object->getName()); + $this->object->setName('name.jpg'); + $this->assertEquals('name.jpg', $this->object->getName()); } /** @@ -68,18 +73,20 @@ public function testGetSetName() */ public function testGetSetLocation() { - $this->object->setLocation('cde'); - $this->assertEquals('cde', $this->object->getLocation()); + $this->object->setLocation('local'); + $this->assertEquals('local', $this->object->getLocation()); } /** * @covers Kunstmaan\MediaBundle\Entity\Media::getContentType + * @covers Kunstmaan\MediaBundle\Entity\Media::getContentTypeShort * @covers Kunstmaan\MediaBundle\Entity\Media::setContentType */ public function testGetSetContentType() { - $this->object->setContentType('def'); - $this->assertEquals('def', $this->object->getContentType()); + $this->object->setContentType('image/jpeg'); + $this->assertEquals('image/jpeg', $this->object->getContentType()); + $this->assertEquals('jpeg', $this->object->getContentTypeShort()); } /** @@ -110,8 +117,8 @@ public function testGetSetUpdatedAt() */ public function testGetContent() { - $this->object->setContent('efg'); - $this->assertEquals('efg', $this->object->getContent()); + $this->object->setContent('content'); + $this->assertEquals('content', $this->object->getContent()); } /** @@ -130,12 +137,68 @@ public function testGetSetFolder() * @covers Kunstmaan\MediaBundle\Entity\Media::setDeleted * @covers Kunstmaan\MediaBundle\Entity\Media::isDeleted */ - public function testSetDeleted() + public function testGetSetDeleted() { + $this->assertFalse($this->object->isDeleted()); + $this->object->setDeleted(true); - $this->assertEquals(true, $this->object->isDeleted()); - $this->object->setDeleted(false); - $this->assertEquals(false, $this->object->isDeleted()); + $this->assertTrue($this->object->isDeleted()); } + /** + * @covers Kunstmaan\MediaBundle\Entity\Media::getMetadata + * @covers Kunstmaan\MediaBundle\Entity\Media::getMetadataValue + * @covers Kunstmaan\MediaBundle\Entity\Media::setMetadata + * @covers Kunstmaan\MediaBundle\Entity\Media::setMetadataValue + */ + public function testGetSetMetaDataAndValues() + { + $meta = array('original_width' => 320, 'original_height' => 200); + $this->object->setMetadata($meta); + $this->assertEquals($meta, $this->object->getMetadata()); + $this->assertEquals(320, $this->object->getMetadataValue('original_width')); + $this->assertEquals(200, $this->object->getMetadataValue('original_height')); + $this->object->setMetadataValue('original_width', 640); + $this->assertEquals(640, $this->object->getMetadataValue('original_width')); + } + + /** + * @covers Kunstmaan\MediaBundle\Entity\Media::getUrl + * @covers Kunstmaan\MediaBundle\Entity\Media::setUrl + */ + public function testGetSetUrl() + { + $this->object->setUrl('http://domain.tld/path/name.ext'); + $this->assertEquals('http://domain.tld/path/name.ext', $this->object->getUrl()); + } + + /** + * @covers Kunstmaan\MediaBundle\Entity\Media::getOriginalFilename + * @covers Kunstmaan\MediaBundle\Entity\Media::setOriginalFilename + */ + public function testGetSetOriginalFilename() + { + $this->object->setOriginalFilename('name.ext'); + $this->assertEquals('name.ext', $this->object->getOriginalFilename()); + } + + /** + * @covers Kunstmaan\MediaBundle\Entity\Media::getCopyright + * @covers Kunstmaan\MediaBundle\Entity\Media::setCopyright + */ + public function testGetSetCopyright() + { + $this->object->setCopyright('(c) 2014 Kunstmaan All rights reserved'); + $this->assertEquals('(c) 2014 Kunstmaan All rights reserved', $this->object->getCopyright()); + } + + /** + * @covers Kunstmaan\MediaBundle\Entity\Media::getDescription + * @covers Kunstmaan\MediaBundle\Entity\Media::setDescription + */ + public function testGetSetDescription() + { + $this->object->setDescription('Description of this picture'); + $this->assertEquals('Description of this picture', $this->object->getDescription()); + } } diff --git a/Tests/Files/sample.pdf b/Tests/Files/sample.pdf new file mode 100644 index 0000000000000000000000000000000000000000..a66d1844b7b468bb5603305cc12cd1f7cb0a6376 GIT binary patch literal 31460 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 J%c{#^{U0~f7sdbp literal 0 HcmV?d00001 diff --git a/Tests/Form/AbstractTypeTest.php b/Tests/Form/AbstractTypeTest.php index d0f1acad..4202b7a2 100644 --- a/Tests/Form/AbstractTypeTest.php +++ b/Tests/Form/AbstractTypeTest.php @@ -1,5 +1,6 @@ markTestIncomplete('This test has not been implemented yet.'); + $this->object->setName('name'); + $this->assertEquals('name', $this->object->getName()); } /** - * @covers Kunstmaan\MediaBundle\Helper\File\FileHelper::setName - * @todo Implement testSetName(). + * @covers Kunstmaan\MediaBundle\Helper\File\FileHelper::setFile + * @covers Kunstmaan\MediaBundle\Helper\File\FileHelper::getFile */ - public function testSetName() + public function testGetSetFile() { - $this->markTestIncomplete('This test has not been implemented yet.'); + $path = tempnam('/tmp', 'kunstmaan-media'); + $file = new UploadedFile($path, 'test'); + $this->object->setFile($file); + $this->assertEquals($file, $this->object->getFile()); + unlink($path); } /** - * @covers Kunstmaan\MediaBundle\Helper\File\FileHelper::getFile - * @todo Implement testGetFile(). + * @covers Kunstmaan\MediaBundle\Helper\File\FileHelper::setFolder + * @covers Kunstmaan\MediaBundle\Helper\File\FileHelper::getFolder */ - public function testGetFile() + public function testGetSetFolder() { - $this->markTestIncomplete('This test has not been implemented yet.'); + $folder = new Folder(); + $this->object->setFolder($folder); + $this->assertEquals($folder, $this->object->getFolder()); } /** - * @covers Kunstmaan\MediaBundle\Helper\File\FileHelper::setFile - * @todo Implement testSetFile(). + * @covers Kunstmaan\MediaBundle\Helper\File\FileHelper::setCopyright + * @covers Kunstmaan\MediaBundle\Helper\File\FileHelper::getCopyright */ - public function testSetFile() + public function testGetSetCopyright() { - $this->markTestIncomplete('This test has not been implemented yet.'); + $this->object->setCopyright('copyright'); + $this->assertEquals('copyright', $this->object->getCopyright()); + } + + /** + * @covers Kunstmaan\MediaBundle\Helper\File\FileHelper::setDescription + * @covers Kunstmaan\MediaBundle\Helper\File\FileHelper::getDescription + */ + public function testGetSetDescription() + { + $this->object->setDescription('description'); + $this->assertEquals('description', $this->object->getDescription()); + } + + /** + * @covers Kunstmaan\MediaBundle\Helper\File\FileHelper::setOriginalFilename + * @covers Kunstmaan\MediaBundle\Helper\File\FileHelper::getOriginalFilename + */ + public function testGetSetOriginalFilename() + { + $this->object->setOriginalFilename('image.jpg'); + $this->assertEquals('image.jpg', $this->object->getOriginalFilename()); } /** * @covers Kunstmaan\MediaBundle\Helper\File\FileHelper::getMedia - * @todo Implement testGetMedia(). */ public function testGetMedia() { - $this->markTestIncomplete('This test has not been implemented yet.'); + $this->media->setId(1); + $media = $this->object->getMedia(); + $this->assertEquals($this->media, $media); } /** @@ -95,9 +127,9 @@ public function testGetMediaFromUrl() /** * @covers Kunstmaan\MediaBundle\Helper\File\FileHelper::__destruct - * @todo Implement test__destruct(). + * @todo Implement testDestruct(). */ - public function test__destruct() + public function testDestruct() { $this->markTestIncomplete('This test has not been implemented yet.'); } diff --git a/Tests/Helper/File/PdfHandlerTest.php b/Tests/Helper/File/PdfHandlerTest.php new file mode 100644 index 00000000..f113c7ff --- /dev/null +++ b/Tests/Helper/File/PdfHandlerTest.php @@ -0,0 +1,112 @@ +pdfTransformer = $this->getMock('Kunstmaan\MediaBundle\Helper\Transformer\PreviewTransformerInterface'); + + $this->filesDir = realpath(__DIR__ . '/../../Files'); + + $this->object = new PdfHandler(); + $this->object->setPdfTransformer($this->pdfTransformer); + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + */ + protected function tearDown() + { + } + + /** + * @covers Kunstmaan\MediaBundle\Helper\File\PdfHandler::getType + */ + public function testGetType() + { + $this->assertEquals(PdfHandler::TYPE, $this->object->getType()); + } + + /** + * @covers Kunstmaan\MediaBundle\Helper\File\PdfHandler::canHandle + */ + public function testCanHandlePdfFiles() + { + $media = new Media(); + $media->setContent(new File($this->filesDir . '/sample.pdf')); + $media->setContentType('application/pdf'); + + $this->assertTrue($this->object->canHandle($media)); + } + + /** + * @covers Kunstmaan\MediaBundle\Helper\File\PdfHandler::canHandle + */ + public function testCannotHandleNonPdfFiles() + { + $media = new Media(); + $media->setContentType('image/jpg'); + + $this->assertFalse($this->object->canHandle($media)); + $this->assertFalse($this->object->canHandle(new \stdClass())); + } + + /** + * @covers Kunstmaan\MediaBundle\Helper\File\PdfHandler::saveMedia + */ + public function testSaveMedia() + { + $media = new Media(); + $this->object->saveMedia($media); + } + + /** + * @covers Kunstmaan\MediaBundle\Helper\File\PdfHandler::getImageUrl + * @covers Kunstmaan\MediaBundle\Helper\File\PdfHandler::setWebPath + */ + public function testGetImageUrl() + { + $this->pdfTransformer + ->expects($this->any()) + ->method('getPreviewFilename') + ->will($this->returnValue('/media.pdf.jpg')); + + $media = new Media(); + $media->setUrl('/path/to/media.pdf'); + $this->assertNull($this->object->getImageUrl($media, '/basepath')); + + $previewFilename = sys_get_temp_dir() . '/media.pdf.jpg'; + $fileSystem = new Filesystem(); + $fileSystem->touch($previewFilename); + $media->setUrl('/media.pdf'); + $this->object->setWebPath(sys_get_temp_dir()); + $this->assertEquals('/media.pdf.jpg', $this->object->getImageUrl($media, '')); + $fileSystem->remove($previewFilename); + } +} diff --git a/Tests/Helper/FolderManagerTest.php b/Tests/Helper/FolderManagerTest.php new file mode 100644 index 00000000..9c37b535 --- /dev/null +++ b/Tests/Helper/FolderManagerTest.php @@ -0,0 +1,138 @@ +repository = $this->getMockBuilder('Kunstmaan\MediaBundle\Repository\FolderRepository') + ->disableOriginalConstructor() + ->getMock(); + + $this->repository + ->expects($this->any()) + ->method('getParentIds') + ->will($this->returnValue(array(1, 2))); + + $folder1 = new Folder(); + $folder1->setId(1); + + $folder2 = new Folder(); + $folder2->setId(2); + + $this->parents = array($folder1, $folder2); + + $this->repository + ->expects($this->any()) + ->method('getPath') + ->will($this->returnValue(array($folder1, $folder2))); + + $rootFolder = new Folder(); + $rootFolder->setId(1); + + $this->repository + ->expects($this->any()) + ->method('getFolder') + ->with($this->equalTo(1)) + ->will($this->returnValue($rootFolder)); + + $this->folder = new Folder(); + $this->folder->setId(3); + + $this->object = new FolderManager($this->repository); + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + */ + protected function tearDown() + { + } + + /** + * @covers Kunstmaan\MediaBundle\Helper\FolderManager::getFolderHierarchy + */ + public function testGetFolderHierarchy() + { + $this->repository + ->expects($this->once()) + ->method('childrenHierarchy') + ->with($this->equalTo($this->folder)) + ->will($this->returnValue(array())); + + $this->object->getFolderHierarchy($this->folder); + } + + /** + * @covers Kunstmaan\MediaBundle\Helper\FolderManager::getRootFolderFor + */ + public function testGetRootFolderFor() + { + $this->repository + ->expects($this->once()) + ->method('getFolder') + ->with($this->equalTo(1)); + + $rootFolder = $this->object->getRootFolderFor($this->folder); + $this->assertEquals(1, $rootFolder->getId()); + } + + /** + * @covers Kunstmaan\MediaBundle\Helper\FolderManager::getParentIds + */ + public function testGetParentIds() + { + $this->repository + ->expects($this->once()) + ->method('getParentIds') + ->with($this->equalTo($this->folder)); + + $this->assertEquals(array(1, 2), $this->object->getParentIds($this->folder)); + } + + /** + * @covers Kunstmaan\MediaBundle\Helper\FolderManager::getParents + */ + public function testGetParents() + { + $this->repository + ->expects($this->once()) + ->method('getPath') + ->with($this->equalTo($this->folder)); + + $this->assertEquals($this->parents, $this->object->getParents($this->folder)); + } +} diff --git a/Tests/Helper/MediaManagerTest.php b/Tests/Helper/MediaManagerTest.php index c609d06e..80fa64de 100644 --- a/Tests/Helper/MediaManagerTest.php +++ b/Tests/Helper/MediaManagerTest.php @@ -1,6 +1,7 @@ setMediaPath(realpath(dirname(__DIR__).'/../../../../../../app/')); + $this->defaultHandler = $this->getMockForAbstractClass('Kunstmaan\MediaBundle\Helper\Media\AbstractMediaHandler'); + $this->defaultHandler + ->expects($this->any()) + ->method('canHandle') + ->will($this->returnValue(true)); + $this->defaultHandler + ->expects($this->any()) + ->method('getName') + ->will($this->returnValue('DefaultHandler')); + $this->defaultHandler + ->expects($this->any()) + ->method('getType') + ->will($this->returnValue('any/type')); $this->object = new MediaManager(); - $this->object->setDefaultHandler($fileHandler); + $this->object->setDefaultHandler($this->defaultHandler); } /** @@ -36,91 +51,238 @@ protected function tearDown() /** * @covers Kunstmaan\MediaBundle\Helper\MediaManager::addHandler - * @todo Implement testAddHandler(). + * @covers Kunstmaan\MediaBundle\Helper\MediaManager::getHandler */ public function testAddHandler() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete('This test has not been implemented yet.'); + $media = new Media(); + $handler = $this->getCustomHandler($media); + $this->object->addHandler($handler); + $this->assertEquals($handler, $this->object->getHandler($media)); } /** - * @covers Kunstmaan\MediaBundle\Helper\MediaManager::getHandler - * @todo Implement testGetHandler(). + * @covers Kunstmaan\MediaBundle\Helper\MediaManager::getHandlerForType */ - public function testGetHandler() + public function testGetHandlerForType() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete('This test has not been implemented yet.'); + $handler = $this->getCustomHandler(); + $this->object->addHandler($handler); + $this->assertEquals($handler, $this->object->getHandlerForType('custom/type')); + $this->assertEquals($this->defaultHandler, $this->object->getHandlerForType('unknown/type')); } /** - * @covers Kunstmaan\MediaBundle\Helper\MediaManager::getHandlerForType - * @todo Implement testGetHandlerForType(). + * @covers Kunstmaan\MediaBundle\Helper\MediaManager::getHandlers */ - public function testGetHandlerForType() + public function testGetHandlers() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete('This test has not been implemented yet.'); + $handler = $this->getCustomHandler(); + $this->object->addHandler($handler); + $handlers = $this->object->getHandlers(); + $this->assertCount(1, $handlers); + $this->assertEquals($handler, current($handlers)); } /** - * @covers Kunstmaan\MediaBundle\Helper\MediaManager::getHandlers - * @todo Implement testGetHandlers(). + * @covers Kunstmaan\MediaBundle\Helper\MediaManager::prepareMedia */ - public function testGetHandlers() + public function testPrepareMediaWithDefaultHandler() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete('This test has not been implemented yet.'); + $media = new Media(); + $this->defaultHandler + ->expects($this->any()) + ->method('prepareMedia') + ->with($this->equalTo($media)); + $this->object->prepareMedia($media); } /** * @covers Kunstmaan\MediaBundle\Helper\MediaManager::prepareMedia - * @todo Implement testPrepareMedia(). */ - public function testPrepareMedia() + public function testPrepareMediaWithCustomHandler() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete('This test has not been implemented yet.'); + $media = new Media(); + $handler = $this->getCustomHandler($media); + $handler + ->expects($this->once()) + ->method('prepareMedia') + ->with($this->equalTo($media)); + $this->object->addHandler($handler); + $this->object->prepareMedia($media); } /** * @covers Kunstmaan\MediaBundle\Helper\MediaManager::saveMedia - * @todo Implement testSaveMedia(). */ - public function testSaveMedia() + public function testSaveMediaWithDefaultHandler() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete('This test has not been implemented yet.'); + $media = new Media(); + $this->defaultHandler + ->expects($this->once()) + ->method('saveMedia') + ->with($this->equalTo($media)); + $this->object->saveMedia($media, true); + + $this->defaultHandler + ->expects($this->once()) + ->method('updateMedia') + ->with($this->equalTo($media)); + $this->object->saveMedia($media); + } + + /** + * @covers Kunstmaan\MediaBundle\Helper\MediaManager::saveMedia + */ + public function testCreateMediaWithCustomHandler() + { + $media = new Media(); + $handler = $this->getCustomHandler($media); + $handler + ->expects($this->once()) + ->method('saveMedia') + ->with($this->equalTo($media)); + $this->object->addHandler($handler); + $this->object->saveMedia($media, true); + } + + /** + * @covers Kunstmaan\MediaBundle\Helper\MediaManager::saveMedia + */ + public function testUpdateMediaWithCustomHandler() + { + $media = new Media(); + $handler = $this->getCustomHandler($media); + $handler + ->expects($this->once()) + ->method('updateMedia') + ->with($this->equalTo($media)); + $this->object->addHandler($handler); + $this->object->saveMedia($media); } /** * @covers Kunstmaan\MediaBundle\Helper\MediaManager::removeMedia - * @todo Implement testRemoveMedia(). */ - public function testRemoveMedia() + public function testRemoveMediaWithDefaultHandler() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete('This test has not been implemented yet.'); + $media = new Media(); + $this->defaultHandler + ->expects($this->once()) + ->method('removeMedia') + ->with($this->equalTo($media)); + $this->object->removeMedia($media); + } + + /** + * @covers Kunstmaan\MediaBundle\Helper\MediaManager::removeMedia + */ + public function testRemoveMediaWithCustomHandler() + { + $media = new Media(); + $handler = $this->getCustomHandler($media); + $handler + ->expects($this->once()) + ->method('removeMedia') + ->with($this->equalTo($media)); + $this->object->addHandler($handler); + $this->object->removeMedia($media); + } + + /** + * @covers Kunstmaan\MediaBundle\Helper\MediaManager::getHandler + */ + public function testGetHandlerWithDefaultHandler() + { + $media = new Media(); + $this->assertEquals($this->defaultHandler, $this->object->getHandler($media)); + } + + /** + * @covers Kunstmaan\MediaBundle\Helper\MediaManager::getHandler + */ + public function testGetHandlerWithCustomHandler() + { + $media = new Media(); + $handler = $this->getCustomHandler($media); + $this->object->addHandler($handler); + $this->assertEquals($handler, $this->object->getHandler($media)); } /** * @covers Kunstmaan\MediaBundle\Helper\MediaManager::createNew - * @todo Implement testCreateNew(). */ public function testCreateNew() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete('This test has not been implemented yet.'); + $media = new Media(); + $data = new \StdClass(); + $this->assertNull($this->object->createNew($data)); + + $handler1 = $this->getCustomHandler(null, 'CustomHandler1'); + $handler1 + ->expects($this->once()) + ->method('createNew') + ->with($this->equalTo($data)) + ->will($this->returnValue(false)); + $this->object->addHandler($handler1); + + $handler2 = $this->getCustomHandler(null, 'CustomHandler2'); + $handler2 + ->expects($this->once()) + ->method('createNew') + ->with($this->equalTo($data)) + ->will($this->returnValue($media)); + $this->object->addHandler($handler2); + + $this->assertEquals($media, $this->object->createNew($data)); } /** * @covers Kunstmaan\MediaBundle\Helper\MediaManager::getFolderAddActions - * @todo Implement testGetFolderAddActions(). */ public function testGetFolderAddActions() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete('This test has not been implemented yet.'); + $actions = array(); + $this->assertEquals($actions, $this->object->getFolderAddActions()); + + $actions = array('action1','action2'); + $handler = $this->getCustomHandler(); + $handler + ->expects($this->once()) + ->method('getAddFolderActions') + ->will($this->returnValue($actions)); + $this->object->addHandler($handler); + $this->assertEquals($actions, $this->object->getFolderAddActions()); + } + + /** + * @param object $media + * @param string $name + * + * @return \PHPUnit_Framework_MockObject_MockObject + */ + protected function getCustomHandler($media = null, $name = null) + { + $handler = $this->getMockForAbstractClass('Kunstmaan\MediaBundle\Helper\Media\AbstractMediaHandler'); + if (empty($name)) { + $name = 'CustomHandler'; + } + $handler + ->expects($this->any()) + ->method('getName') + ->will($this->returnValue($name)); + $handler + ->expects($this->any()) + ->method('getType') + ->will($this->returnValue('custom/type')); + if (!is_null($media)) { + $handler + ->expects($this->any()) + ->method('canHandle') + ->with($this->equalTo($media)) + ->will($this->returnValue(true)); + } + + return $handler; } } diff --git a/Tests/Helper/RemoteAudio/RemoteAudioHelperTest.php b/Tests/Helper/RemoteAudio/RemoteAudioHelperTest.php new file mode 100644 index 00000000..050f4fbe --- /dev/null +++ b/Tests/Helper/RemoteAudio/RemoteAudioHelperTest.php @@ -0,0 +1,47 @@ +media = new Media(); + $this->object = new RemoteAudioHelper($this->media); + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + */ + protected function tearDown() + { + } + + /** + * @covers Kunstmaan\MediaBundle\Helper\RemoteAudio\RemoteAudioHelper::getMedia + */ + public function testGetMedia() + { + $this->assertEquals(RemoteAudioHandler::CONTENT_TYPE, $this->object->getMedia()->getContentType()); + } +} diff --git a/Tests/Helper/RemoteSlide/RemoteSlideHelperTest.php b/Tests/Helper/RemoteSlide/RemoteSlideHelperTest.php index 3c1eb564..0efa4ce3 100644 --- a/Tests/Helper/RemoteSlide/RemoteSlideHelperTest.php +++ b/Tests/Helper/RemoteSlide/RemoteSlideHelperTest.php @@ -1,8 +1,9 @@ markTestIncomplete('This test has not been implemented yet.'); - } - - /** - * @covers Kunstmaan\MediaBundle\Helper\RemoteSlide\RemoteSlideHelper::setName - * @todo Implement testSetName(). - */ - public function testSetName() - { - $this->markTestIncomplete('This test has not been implemented yet.'); - } - /** * @covers Kunstmaan\MediaBundle\Helper\RemoteSlide\RemoteSlideHelper::getMedia - * @todo Implement testGetMedia(). */ public function testGetMedia() { - $this->markTestIncomplete('This test has not been implemented yet.'); - } - - /** - * @covers Kunstmaan\MediaBundle\Helper\RemoteSlide\RemoteSlideHelper::getCode - * @todo Implement testGetCode(). - */ - public function testGetCode() - { - $this->markTestIncomplete('This test has not been implemented yet.'); - } - - /** - * @covers Kunstmaan\MediaBundle\Helper\RemoteSlide\RemoteSlideHelper::setCode - * @todo Implement testSetCode(). - */ - public function testSetCode() - { - $this->markTestIncomplete('This test has not been implemented yet.'); - } - - /** - * @covers Kunstmaan\MediaBundle\Helper\RemoteSlide\RemoteSlideHelper::getThumbnailUrl - * @todo Implement testGetThumbnailUrl(). - */ - public function testGetThumbnailUrl() - { - $this->markTestIncomplete('This test has not been implemented yet.'); - } - - /** - * @covers Kunstmaan\MediaBundle\Helper\RemoteSlide\RemoteSlideHelper::setThumbnailUrl - * @todo Implement testSetThumbnailUrl(). - */ - public function testSetThumbnailUrl() - { - $this->markTestIncomplete('This test has not been implemented yet.'); - } - - /** - * @covers Kunstmaan\MediaBundle\Helper\RemoteSlide\RemoteSlideHelper::getType - * @todo Implement testGetType(). - */ - public function testGetType() - { - $this->markTestIncomplete('This test has not been implemented yet.'); - } - - /** - * @covers Kunstmaan\MediaBundle\Helper\RemoteSlide\RemoteSlideHelper::setType - * @todo Implement testSetType(). - */ - public function testSetType() - { - $this->markTestIncomplete('This test has not been implemented yet.'); - } - - /** - * @covers Kunstmaan\MediaBundle\Helper\RemoteSlide\RemoteSlideHelper::updateMedia - * @todo Implement testUpdateMedia(). - */ - public function testUpdateMedia() - { - $this->markTestIncomplete('This test has not been implemented yet.'); + $this->assertEquals(RemoteSlideHandler::CONTENT_TYPE, $this->object->getMedia()->getContentType()); } } diff --git a/Tests/Helper/RemoteVideo/RemoteVideoHelperTest.php b/Tests/Helper/RemoteVideo/RemoteVideoHelperTest.php index 8357d786..39ba84c8 100644 --- a/Tests/Helper/RemoteVideo/RemoteVideoHelperTest.php +++ b/Tests/Helper/RemoteVideo/RemoteVideoHelperTest.php @@ -3,6 +3,7 @@ use Kunstmaan\MediaBundle\Entity\Media; +use Kunstmaan\MediaBundle\Helper\RemoteVideo\RemoteVideoHandler; use Kunstmaan\MediaBundle\Helper\RemoteVideo\RemoteVideoHelper; /** @@ -24,9 +25,12 @@ class RemoteVideoHelperTest extends \PHPUnit_Framework_TestCase /** * Sets up the fixture, for example, opens a network connection. * This method is called before a test is executed. + * @covers Kunstmaan\MediaBundle\Helper\RemoteVideo\RemoteVideoHelper::__construct */ protected function setUp() - {$this->media = new Media();$this->object = new RemoteVideoHelper($this->media); + { + $this->media = new Media(); + $this->object = new RemoteVideoHelper($this->media); } /** @@ -37,84 +41,11 @@ protected function tearDown() { } - /** - * @covers Kunstmaan\MediaBundle\Helper\RemoteVideo\RemoteVideoHelper::getName - * @todo Implement testGetName(). - */ - public function testGetName() - { - $this->markTestIncomplete('This test has not been implemented yet.'); - } - - /** - * @covers Kunstmaan\MediaBundle\Helper\RemoteVideo\RemoteVideoHelper::setName - * @todo Implement testSetName(). - */ - public function testSetName() - { - $this->markTestIncomplete('This test has not been implemented yet.'); - } - /** * @covers Kunstmaan\MediaBundle\Helper\RemoteVideo\RemoteVideoHelper::getMedia - * @todo Implement testGetMedia(). */ public function testGetMedia() { - $this->markTestIncomplete('This test has not been implemented yet.'); - } - - /** - * @covers Kunstmaan\MediaBundle\Helper\RemoteVideo\RemoteVideoHelper::getCode - * @todo Implement testGetCode(). - */ - public function testGetCode() - { - $this->markTestIncomplete('This test has not been implemented yet.'); - } - - /** - * @covers Kunstmaan\MediaBundle\Helper\RemoteVideo\RemoteVideoHelper::setCode - * @todo Implement testSetCode(). - */ - public function testSetCode() - { - $this->markTestIncomplete('This test has not been implemented yet.'); - } - - /** - * @covers Kunstmaan\MediaBundle\Helper\RemoteVideo\RemoteVideoHelper::getThumbnailUrl - * @todo Implement testGetThumbnailUrl(). - */ - public function testGetThumbnailUrl() - { - $this->markTestIncomplete('This test has not been implemented yet.'); - } - - /** - * @covers Kunstmaan\MediaBundle\Helper\RemoteVideo\RemoteVideoHelper::setThumbnailUrl - * @todo Implement testSetThumbnailUrl(). - */ - public function testSetThumbnailUrl() - { - $this->markTestIncomplete('This test has not been implemented yet.'); - } - - /** - * @covers Kunstmaan\MediaBundle\Helper\RemoteVideo\RemoteVideoHelper::getType - * @todo Implement testGetType(). - */ - public function testGetType() - { - $this->markTestIncomplete('This test has not been implemented yet.'); - } - - /** - * @covers Kunstmaan\MediaBundle\Helper\RemoteVideo\RemoteVideoHelper::setType - * @todo Implement testSetType(). - */ - public function testSetType() - { - $this->markTestIncomplete('This test has not been implemented yet.'); + $this->assertEquals(RemoteVideoHandler::CONTENT_TYPE, $this->object->getMedia()->getContentType()); } } diff --git a/Tests/Helper/Transformer/PdfTransformerTest.php b/Tests/Helper/Transformer/PdfTransformerTest.php new file mode 100644 index 00000000..c9d72828 --- /dev/null +++ b/Tests/Helper/Transformer/PdfTransformerTest.php @@ -0,0 +1,109 @@ +markTestSkipped('Imagick is not available.'); + } + $this->filesDir = realpath(__DIR__ . '/../../Files'); + $this->tempDir = str_replace('/', DIRECTORY_SEPARATOR, sys_get_temp_dir().'/kunstmaan_media_test'); + + $this->filesystem = new Filesystem(); + $this->removeTempDir(); + $this->filesystem->mkdir($this->tempDir); + $this->object = new PdfTransformer(new \Imagick()); + } + + protected function tearDown() + { + if (!$this->filesystem) { + return; + } + + $this->removeTempDir(); + } + + /** + * @covers Kunstmaan\MediaBundle\Helper\Transformer\PdfTransformer::apply + */ + public function testApplyWritesJpg() + { + $pdfFilename = $this->tempDir . '/sample.pdf'; + $jpgFilename = $pdfFilename.'.jpg'; + + $pdf = $this->filesDir . '/sample.pdf'; + $this->filesystem->copy($pdf, $pdfFilename); + $this->assertTrue(file_exists($pdfFilename)); + + $transformer = new PdfTransformer(new \Imagick()); + $absolutePath = $transformer->apply($pdfFilename); + + $this->assertEquals($jpgFilename, $absolutePath); + $this->assertTrue(file_exists($jpgFilename)); + $this->assertNotEmpty(file_get_contents($jpgFilename)); + } + + /** + * @covers Kunstmaan\MediaBundle\Helper\Transformer\PdfTransformer::apply + */ + public function testApplyDoesNotOverwriteExisting() + { + $pdfFilename = $this->tempDir . '/sample.pdf'; + $jpgFilename = $pdfFilename . '.jpg'; + + $pdf = $this->filesDir . '/sample.pdf'; + $this->filesystem->copy($pdf, $pdfFilename); + $this->assertTrue(file_exists($pdfFilename)); + + $this->filesystem->touch($jpgFilename); + + $transformer = new PdfTransformer(new \Imagick()); + $absolutePath = $transformer->apply($pdfFilename); + + $this->assertEquals($jpgFilename, $absolutePath); + $this->assertEmpty(file_get_contents($jpgFilename)); + } + + /** + * @covers Kunstmaan\MediaBundle\Helper\Transformer\PdfTransformer::getPreviewFilename + */ + public function testGetPreviewFilename() + { + $pdfFilename = $this->tempDir . '/sample.pdf'; + $jpgFilename = $pdfFilename . '.jpg'; + + $this->assertEquals($jpgFilename, $this->object->getPreviewFilename($pdfFilename)); + } + + private function removeTempDir() + { + if ($this->filesystem->exists($this->tempDir)) { + $this->filesystem->remove($this->tempDir); + } + } +} diff --git a/UPGRADE.md b/UPGRADE.md new file mode 100644 index 00000000..6521cf41 --- /dev/null +++ b/UPGRADE.md @@ -0,0 +1,24 @@ +Upgrade Instructions +==================== + +## To v2.3.X with extra fields, indexes and folder tree + +When upgrading from a previous version, make sure you update the table structure ( +```app/console doctrine:schema:update --force``` +or ```app/console doctrine:migrations:diff && app/console doctrine:migrations:migrate```). + +A new field to store the original filename was added to the Media table, so you will have to update the table structure +when upgrading from a version prior to 2.3.X. + +You can use ```app/console kuma:media:migrate-name``` to initialize the original filename field for already +uploaded media (it will just copy the contents of name field into the original_filename field, so you could also just +update this using a simple SQL query if you want). + +The Folder entity has been refactored to be a nested tree, which should speed up the media section (this will +especially be noticeable if you have lots of media folders). + +To migrate your current media tree to the new format, you have to execute ```app/console kuma:media:rebuild-folder-tree``` +to initialize the folder tree. If you decide to undelete folders you should run this command as well. + +If you want to create PDF preview images for PDF files that have already been uploaded (provided that you have the +necessary PDF support enabled), you can run the ```app/console kuma:media:create-pdf-previews``` command. diff --git a/composer.json b/composer.json index f8d710e6..41e26838 100644 --- a/composer.json +++ b/composer.json @@ -15,6 +15,7 @@ "require": { "php": ">=5.3.3", "symfony/symfony": "~2.3", + "gedmo/doctrine-extensions": "2.3.*", "doctrine/doctrine-fixtures-bundle": "~2.2.0", "stof/doctrine-extensions-bundle": "1.1.*@dev", "liip/imagine-bundle": "v0.20.2", @@ -27,11 +28,13 @@ "knplabs/knp-menu-bundle": "2.0.*@dev", "friendsofsymfony/user-bundle": "2.0.*@dev" }, + "suggest": { + "ext-imagick": "to support PDF preview images" + }, "minimum-stability": "stable", "autoload": { - "psr-0": { "Kunstmaan\\MediaBundle": "" } + "psr-4": { "Kunstmaan\\MediaBundle\\": "" } }, - "target-dir": "Kunstmaan/MediaBundle", "extra": { "branch-alias": { "dev-master": "2.3.x-dev" From 7f1d93b1a1255c001f6cc82b5c5c300abcfaf2e5 Mon Sep 17 00:00:00 2001 From: Wim Vandersmissen Date: Thu, 14 Aug 2014 14:07:55 +0200 Subject: [PATCH 2/2] version in doc --- UPGRADE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UPGRADE.md b/UPGRADE.md index 6521cf41..f5aabafe 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,14 +1,14 @@ Upgrade Instructions ==================== -## To v2.3.X with extra fields, indexes and folder tree +## To v2.3.18 with extra fields, indexes and folder tree When upgrading from a previous version, make sure you update the table structure ( ```app/console doctrine:schema:update --force``` or ```app/console doctrine:migrations:diff && app/console doctrine:migrations:migrate```). A new field to store the original filename was added to the Media table, so you will have to update the table structure -when upgrading from a version prior to 2.3.X. +when upgrading from a version prior to 2.3.18. You can use ```app/console kuma:media:migrate-name``` to initialize the original filename field for already uploaded media (it will just copy the contents of name field into the original_filename field, so you could also just