X-Git-Url: https://git.rapsys.eu/treebundle/blobdiff_plain/6081ccbb83091e8ce6045f73b911bebfd2963ebd..ad8124dcef6a76e6b458c44622b6285fa522ac01:/Controller/TreeController.php?ds=sidebyside diff --git a/Controller/TreeController.php b/Controller/TreeController.php index a6e2ac1..7c2a38f 100644 --- a/Controller/TreeController.php +++ b/Controller/TreeController.php @@ -11,24 +11,38 @@ namespace Rapsys\TreeBundle\Controller; +use Doctrine\Persistence\ManagerRegistry; + use Psr\Container\ContainerInterface; +use Rapsys\PackBundle\Util\FacebookUtil; +use Rapsys\TreeBundle\Entity\Album; +use Rapsys\TreeBundle\Entity\Element; +use Rapsys\TreeBundle\RapsysTreeBundle; +use Rapsys\UserBundle\RapsysUserBundle; + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Routing\RouterInterface; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; + use Symfony\Contracts\Translation\TranslatorInterface; use Twig\Environment; -use Rapsys\TreeBundle\RapsysTreeBundle; - /** * {@inheritdoc} */ class TreeController extends AbstractController { + /** + * Alias string + */ + protected string $alias; + /** * Config array */ @@ -39,11 +53,21 @@ class TreeController extends AbstractController { */ protected array $context = []; + /** + * Count integer + */ + protected int $count; + /** * Locale string */ protected string $locale; + /** + * Page integer + */ + protected int $page; + /** * Request instance */ @@ -62,15 +86,20 @@ class TreeController extends AbstractController { /** * Creates a new tree controller * + * @param AuthorizationCheckerInterface $checker The container instance * @param ContainerInterface $container The ContainerInterface instance + * @param ManagerRegistry $doctrine The doctrine instance + * @param FacebookUtil $facebook The facebook instance * @param RouterInterface $router The router instance + * @param Security $security The security instance * @param RequestStack $stack The stack instance * @param TranslatorInterface $translator The translator instance * @param Environment $twig The twig environment instance + * @param integer $limit The page limit */ - function __construct(protected ContainerInterface $container, protected RouterInterface $router, protected RequestStack $stack, protected TranslatorInterface $translator, protected Environment $twig) { + function __construct(protected AuthorizationCheckerInterface $checker, protected ContainerInterface $container, protected ManagerRegistry $doctrine, protected FacebookUtil $facebook, protected RouterInterface $router, protected Security $security, protected RequestStack $stack, protected TranslatorInterface $translator, protected Environment $twig, protected int $limit = 5) { //Retrieve config - $this->config = $container->getParameter(RapsysTreeBundle::getAlias()); + $this->config = $container->getParameter($this->alias = RapsysTreeBundle::getAlias()); //Get main request $this->request = $this->stack->getMainRequest(); @@ -84,6 +113,14 @@ class TreeController extends AbstractController { //Set alternates $alternates = []; + //Get current page + $this->page = (int) $this->request->query->get('page'); + + //With negative page + if ($this->page < 0) { + $this->page = 0; + } + //Set route //TODO: default to not found route ??? //TODO: pour une url not found, cet attribut n'est pas défini, comment on fait ??? @@ -153,6 +190,273 @@ class TreeController extends AbstractController { ]; } + /** + * The album page + * + * Display album + * + * @param Request $request The request instance + * @param int $id The album id + * @param string $path The album path + * @param string $slug The album slug + * @return Response The rendered view + */ + public function album(Request $request, int $id, string $path, string $slug): Response { + //Get user + $user = $this->security->getUser(); + + //Check admin role + if (!$this->checker->isGranted('ROLE_'.strtoupper($this->container->getParameter(RapsysUserBundle::getAlias().'.default.admin')))) { + //Throw access denied + //XXX: prevent slugger reverse engineering by not displaying decoded mail + throw $this->createAccessDeniedException($this->translator->trans('Unable to access album', [], $this->alias)); + } + + //Without album + if (!($this->context['album'] = $this->doctrine->getRepository(Album::class)->findOneByIdPathAsArray($id, $path))) { + //Throw 404 + throw $this->createNotFoundException($this->translator->trans('Unable to find album')); + } + + //With slug not matching + if ($this->context['album']['slug'] !== $slug) { + //Redirect on clean slug + return $this->redirectToRoute($this->route, [ 'slug' => $this->context['album']['slug'] ]+$this->routeParams); + } + + //Set modified + $this->modified = $this->context['album']['modified']; + + //Create response + $response = new Response(); + + //With logged user + if ($this->checker->isGranted('IS_AUTHENTICATED_REMEMBERED')) { + //Set last modified + $response->setLastModified(new \DateTime('-1 year')); + + //Set as private + $response->setPrivate(); + //Without logged user + } else { + //Set etag + //XXX: only for public to force revalidation by last modified + $response->setEtag(md5(serialize($this->context['album']))); + + //Set last modified + $response->setLastModified($this->modified); + + //Set as public + $response->setPublic(); + + //Without role and modification + if ($response->isNotModified($request)) { + //Return 304 response + return $response; + } + } + + //Set keywords + /*$this->context['head']['keywords'] = implode( + ', ', + //Use closure to extract each unique article keywords sorted + (function ($t) { + //Return array + $r = []; + + //Iterate on articles + foreach($t as $a) { + //Non empty keywords + if (!empty($a['keywords'])) { + //Iterate on keywords + foreach($a['keywords'] as $k) { + //Set keyword + $r[$k['title']] = $k['title']; + } + } + } + + //Sort array + sort($r); + + //Return array + return $r; + })($this->context['articles']) + ); + //Get albums + $this->context['albums'] = $this->config['albums'];*/ + + //Return rendered response + return $this->render('@RapsysTree/album.html.twig', $this->context, $response); + } + + + /** + * The element page + * + * Display element + * + * @param Request $request The request instance + * @param int $id The element id + * @param ?string $path The element path + * @return Response The rendered view + */ + public function element(Request $request, int $id, string $path): Response { + //Get user + $user = $this->security->getUser(); + + //Without element + if (!($this->context['element'] = $this->doctrine->getRepository(Element::class)->findOneByUidIdPathAsArray($user?->getId(), $id, $path))) { + //Throw 404 + throw $this->createNotFoundException($this->translator->trans('Unable to find element')); + } + + //With realpath not matching path + //XXX: extra slashes removed by rewrite rule + if (($this->context['path'] = substr($this->context['element']['realpath'], strlen($this->context['element']['album']['path'])+1)) !== $path) { + //Redirect on clean path + return $this->redirectToRoute($this->route, [ 'path' => $this->context['path'] ]+$this->routeParams); + } + + //Set modified + $this->modified = $this->context['element']['modified']; + + //Create response + $response = new Response(); + + //With logged user + if ($this->checker->isGranted('IS_AUTHENTICATED_REMEMBERED')) { + //Set last modified + $response->setLastModified(new \DateTime('-1 year')); + + //Set as private + $response->setPrivate(); + //Without logged user + } else { + //Set etag + //XXX: only for public to force revalidation by last modified + $response->setEtag(md5(serialize($this->context['element']))); + + //Set last modified + $response->setLastModified($this->modified); + + //Set as public + $response->setPublic(); + + //Without role and modification + if ($response->isNotModified($request)) { + //Return 304 response + return $response; + } + } + + //TODO: move that in a shared function to use in album member function too + + /* + //With directory + if (is_dir($realpath)) { + //Set element directories + $this->context['element'] += [ + //Directories + 'directories' => [], + //Files + 'files' => [] + ]; + + //Iterate on directory + foreach(array_diff(scandir($realpath), ['.', '..']) as $item) { + //Check item + if ( + //Without item realpath + !($itempath = realpath($realpath.'/'.$item)) || + //With item realpath not matching element path + ( + $this->context['element']['album']['path'].$this->context['element']['path'] !== + substr($itempath, 0, strlen($this->context['element']['album']['path'].$this->context['element']['path'])) + ) + ) { + //Skip + continue; + } + + //With directory + if (is_dir($itempath)) { + //Append directory + $this->context['element']['directories'][$item] = $this->router->generate($this->route, [ 'path' => $this->context['path'].'/'.$item ]+$this->routeParams); + //With file + } elseif (is_file($itempath)) { + //Append file + $this->context['element']['files'][$item] = [ + //Set mime + 'mime' => mime_content_type($itempath), + //Set size + 'size' => filesize($itempath), + //Set link + 'link' => $this->router->generate($this->route, [ 'path' => $this->context['path'].'/'.$item ]+$this->routeParams) + ]; + //With unknown type + } else { + //Throw 404 + throw $this->createNotFoundException($this->translator->trans('Unable to process element')); + } + } + //With file + } elseif (is_file($realpath)) { + //Append file + $this->context['element']['file'] = [ + //Set mime + 'mime' => mime_content_type($realpath), + //Set size + 'size' => filesize($realpath), + //TODO: extra fields ? (preview, miniature, etc ?) + ]; + //TODO: mimetype decided extra fields ? (.pdf for doc, webm preview, img preview, etc ?) + //TODO: XXX: finish this !!! + //With unknown type + } else { + //Throw 404 + throw $this->createNotFoundException($this->translator->trans('Unable to process element')); + } + */ + + //Set keywords + /*$this->context['head']['keywords'] = implode( + ', ', + //Use closure to extract each unique article keywords sorted + (function ($t) { + //Return array + $r = []; + + //Iterate on articles + foreach($t as $a) { + //Non empty keywords + if (!empty($a['keywords'])) { + //Iterate on keywords + foreach($a['keywords'] as $k) { + //Set keyword + $r[$k['title']] = $k['title']; + } + } + } + + //Sort array + sort($r); + + //Return array + return $r; + })($this->context['articles']) + ); + //Get albums + $this->context['albums'] = $this->config['albums'];*/ + + #header('Content-Type: text/plain'); + #var_dump($this->context['element']); + #exit; + + //Return rendered response + return $this->render('@RapsysTree/element.html.twig', $this->context, $response); + } + /** * The index page * @@ -162,18 +466,86 @@ class TreeController extends AbstractController { * @return Response The rendered view */ public function index(Request $request): Response { - //Get roots - $this->context['roots'] = $this->config['roots']; + //Get user + $user = $this->security->getUser(); - //Render template - $response = $this->render('@RapsysTree/index.html.twig', $this->context); + //With not enough albums + if (($this->count = $this->doctrine->getRepository(Album::class)->countByUidAsInt($user?->getId())) < $this->page * $this->limit) { + //Throw 404 + throw $this->createNotFoundException($this->translator->trans('Unable to find albums')); + } - $response->setEtag(md5($response->getContent())); - $response->setPublic(); - $response->isNotModified($request); + //Get albums + if ($this->context['albums'] = $this->doctrine->getRepository(Album::class)->findByUidAsArray($user?->getId(), $this->page, $this->limit)) { + //Set modified + $this->modified = max(array_map(function ($v) { return $v['modified']; }, $this->context['albums'])); + //Without albums + } else { + //Set empty modified + $this->modified = new \DateTime('-1 year'); + } - //Return response - return $response; + //Create response + $response = new Response(); + + //With logged user + if ($this->checker->isGranted('IS_AUTHENTICATED_REMEMBERED')) { + //Set last modified + $response->setLastModified(new \DateTime('-1 year')); + + //Set as private + $response->setPrivate(); + //Without logged user + } else { + //Set etag + //XXX: only for public to force revalidation by last modified + $response->setEtag(md5(serialize($this->context['albums']))); + + //Set last modified + $response->setLastModified($this->modified); + + //Set as public + $response->setPublic(); + + //Without role and modification + if ($response->isNotModified($request)) { + //Return 304 response + return $response; + } + } + + //Set keywords + /*$this->context['head']['keywords'] = implode( + ', ', + //Use closure to extract each unique article keywords sorted + (function ($t) { + //Return array + $r = []; + + //Iterate on articles + foreach($t as $a) { + //Non empty keywords + if (!empty($a['keywords'])) { + //Iterate on keywords + foreach($a['keywords'] as $k) { + //Set keyword + $r[$k['title']] = $k['title']; + } + } + } + + //Sort array + sort($r); + + //Return array + return $r; + })($this->context['articles']) + ); + //Get albums + $this->context['albums'] = $this->config['albums'];*/ + + //Return rendered response + return $this->render('@RapsysTree/index.html.twig', $this->context, $response); } /**