ElementController::generateBreadcrumb()   B
last analyzed

Complexity

Conditions 5
Paths 4

Size

Total Lines 20
Code Lines 13

Duplication

Lines 20
Ratio 100 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 20
loc 20
rs 8.8571
cc 5
eloc 13
nc 4
nop 2
1
<?php
2
/*
3
  ÁTICA - Aplicación web para la gestión documental de centros educativos
4
5
  Copyright (C) 2015-2017: Luis Ramón López López
6
7
  This program is free software: you can redistribute it and/or modify
8
  it under the terms of the GNU Affero General Public License as published by
9
  the Free Software Foundation, either version 3 of the License, or
10
  (at your option) any later version.
11
12
  This program is distributed in the hope that it will be useful,
13
  but WITHOUT ANY WARRANTY; without even the implied warranty of
14
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
  GNU Affero General Public License for more details.
16
17
  You should have received a copy of the GNU Affero General Public License
18
  along with this program.  If not, see [http://www.gnu.org/licenses/].
19
*/
20
21
namespace AppBundle\Controller\Organization;
22
23
use AppBundle\Entity\Actor;
24
use AppBundle\Entity\Element;
25
use AppBundle\Entity\ElementRepository;
26
use AppBundle\Entity\Organization;
27
use AppBundle\Entity\Reference;
28
use AppBundle\Entity\Role;
29
use AppBundle\Form\Type\ElementType;
30
use AppBundle\Security\OrganizationVoter;
31
use Doctrine\Common\Collections\ArrayCollection;
32
use Doctrine\Common\Persistence\ObjectManager;
33
use Doctrine\ORM\QueryBuilder;
34
use Pagerfanta\Adapter\DoctrineORMAdapter;
35
use Pagerfanta\Pagerfanta;
36
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
37
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
38
use Symfony\Component\Form\Form;
39
use Symfony\Component\HttpFoundation\Request;
40
41
/**
42
 * @Route("/centro/elementos")
43
 */
44
class ElementController extends Controller
45
{
46
    /**
47
     * @Route("/listar/{page}/{path}", name="organization_element_list", requirements={"page" = "\d+", "path" = ".+"}, defaults={"page" = "1", "path" = null}, methods={"GET"})
48
     */
49
    public function listAction($page, $path = null, Request $request)
50
    {
51
        $organization = $this->get('AppBundle\Service\UserExtensionService')->getCurrentOrganization();
52
        $this->denyAccessUnlessGranted(OrganizationVoter::MANAGE, $organization);
53
54
        $q = $request->get('q', null);
55
56
        $element = $this->getSelectedElement($path, $organization);
57
        $pager = $this->getElementListPager($page, $element, $q);
58
59
        $breadcrumb = $this->generateBreadcrumb($element);
60
61
        return $this->render('organization/element/list.html.twig', [
62
            'breadcrumb' => $breadcrumb,
63
            'title' => $element->getName(),
64
            'pager' => $pager,
65
            'current' => $element,
66
            'q' => $q,
67
            'domain' => 'element'
68
        ]);
69
    }
70
71
    /**
72
     * @Route("/operar/{path}", name="organization_element_operation", requirements={"path" = ".+"}, defaults={"path" = null}, methods={"POST"})
73
     */
74
    public function operationAction($path = null, Request $request)
75
    {
76
        $organization = $this->get('AppBundle\Service\UserExtensionService')->getCurrentOrganization();
77
        $this->denyAccessUnlessGranted(OrganizationVoter::MANAGE, $organization);
78
79
        $element = $this->getSelectedElement($path, $organization);
80
        $ok = $this->processElementMovementOperation($request, $element);
81
82
        $items = $request->request->get('elements', []);
83
        if ($ok || count($items) === 0) {
84
            return $this->redirectToRoute('organization_element_list', ['path' => $path]);
85
        }
86
87
        $elements = $this->filterElementsFromItems($items, $element);
88
89
        $result = $this->processElementRemoveOperation($path, $request, $items, $element);
90
        if (false !== $result) {
91
            return $result;
92
        }
93
94
        $title = $this->get('translator')->trans('title.delete', [], 'element');
95
        $breadcrumb = $this->generateBreadcrumb($element, false);
96
        $breadcrumb[] = ['fixed' => $this->get('translator')->trans('title.delete', [], 'element')];
97
98
        return $this->render('organization/element/delete.html.twig', [
99
            'menu_path' => 'organization_element_list',
100
            'breadcrumb' => $breadcrumb,
101
            'title' => $title,
102
            'elements' => $elements
103
        ]);
104
    }
105
106
    /**
107
     * @Route("/carpeta/{path}", name="organization_element_folder_new", requirements={"path" = ".+"}, methods={"GET", "POST"})
108
     * @Route("/nuevo/{path}", name="organization_element_new", requirements={"path" = ".+"}, methods={"GET", "POST"})
109
     * @Route("/modificar/{path}", name="organization_element_form", requirements={"path" = ".+"}, methods={"GET", "POST"})
110
     */
111
    public function formAction($path, Request $request)
112
    {
113
        $organization = $this->get('AppBundle\Service\UserExtensionService')->getCurrentOrganization();
114
        $this->denyAccessUnlessGranted(OrganizationVoter::MANAGE, $organization);
115
116
        /** @var ObjectManager $em */
117
        $em = $this->getDoctrine()->getManager();
118
119
        /** @var Element|null $element */
120
        if (null === $element = $em->getRepository('AppBundle:Element')->findOneByOrganizationAndPath($organization, $path)) {
121
            throw $this->createNotFoundException();
122
        }
123
124
        list($breadcrumb, $element, $title) = $this->getElementBreadcrumbAndTitle($request, $element, $organization);
125
126
        $form = $this->createForm(ElementType::class, $element);
127
128
        $this->setElementReferencesInForm($element, $form);
0 ignored issues
show
Compatibility introduced by
$form of type object<Symfony\Component\Form\FormInterface> is not a sub-type of object<Symfony\Component\Form\Form>. It seems like you assume a concrete implementation of the interface Symfony\Component\Form\FormInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
129
        $this->setElementRolesInForm($element, $form, $organization);
0 ignored issues
show
Compatibility introduced by
$form of type object<Symfony\Component\Form\FormInterface> is not a sub-type of object<Symfony\Component\Form\Form>. It seems like you assume a concrete implementation of the interface Symfony\Component\Form\FormInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
130
131
        $form->handleRequest($request);
132
133
        if ($form->isSubmitted() && $form->isValid()) {
134
            try {
135
                $em->persist($element);
136
                $em->flush();
137
                $this->updateElementReferences($element, $em, $form);
0 ignored issues
show
Compatibility introduced by
$form of type object<Symfony\Component\Form\FormInterface> is not a sub-type of object<Symfony\Component\Form\Form>. It seems like you assume a concrete implementation of the interface Symfony\Component\Form\FormInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
138
                $this->updateElementRoles($element, $em, $form);
0 ignored issues
show
Compatibility introduced by
$form of type object<Symfony\Component\Form\FormInterface> is not a sub-type of object<Symfony\Component\Form\Form>. It seems like you assume a concrete implementation of the interface Symfony\Component\Form\FormInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
139
                $this->addFlash('success', $this->get('translator')->trans('message.saved', [], 'element'));
140
                return $this->redirectToRoute('organization_element_list', ['page' => 1, 'path' => $element->getParent()->getPath()]);
141
            } catch (\Exception $e) {
142
                $this->addFlash('error', $this->get('translator')->trans('message.save_error', [], 'element'));
143
            }
144
        }
145
146
        return $this->render('organization/element/form.html.twig', [
147
            'menu_path' => 'organization_element_list',
148
            'breadcrumb' => $breadcrumb,
149
            'title' => $title,
150
            'element' => $element,
151
            'form' => $form->createView()
152
        ]);
153
    }
154
155
    /**
156
     * Returns breadcrumb that matches the element (ignores root element)
157
     * @param Element $element
158
     * @param bool $ignoreLast
159
     * @return array
160
     */
161 View Code Duplication
    private function generateBreadcrumb(Element $element = null, $ignoreLast = true)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
162
    {
163
        $breadcrumb = [];
164
165
        if (null === $element) {
166
            return null;
167
        }
168
169
        $item = $element;
170
        while ($item->getParent()) {
171
            $entry = ['fixed' => $item->getName()];
172
            if ($item !== $element || !$ignoreLast) {
173
                $entry['routeName'] = 'organization_element_list';
174
                $entry['routeParams'] = ['path' => $item->getPath()];
175
            }
176
            array_unshift($breadcrumb, $entry);
177
            $item = $item->getParent();
178
        }
179
        return $breadcrumb;
180
    }
181
182
    /**
183
     * @param $path
184
     * @param $organization
185
     * @return Element
186
     */
187
    private function getSelectedElement($path, $organization)
188
    {
189
        /** @var ElementRepository $elementRepository */
190
        $elementRepository = $this->getDoctrine()->getManager()->getRepository('AppBundle:Element');
191
192
        /** @var Element|null $element */
193
        if (null !== $path) {
194
            $element = $elementRepository->findOneByOrganizationAndPath($organization, $path);
195
            if (null === $element) {
196
                throw $this->createNotFoundException();
197
            }
198
        } else {
199
            $element = $elementRepository->findCurrentOneByOrganization($organization);
200
        }
201
        return $element;
202
    }
203
204
    /**
205
     * @param Element $element
206
     * @param ObjectManager $em
207
     * @param Form $form
208
     */
209
    private function updateElementReferences($element, $em, $form)
210
    {
211
        /** @var Reference $reference */
212
        foreach ($element->getPathReferences() as $reference) {
213
            $items = $em->getRepository('AppBundle:Element')->getChildrenQueryBuilder($reference->getTarget())
214
                ->andWhere('node.folder = false')
215
                ->getQuery()
216
                ->getResult();
217
218
            $data = $form
219
                ->get('reference'.$reference->getTarget()->getId())->getData();
220
221
            if (!is_array($data)) {
222
                $data = [$data];
223
            }
224
225
            foreach ($items as $item) {
226
                $childItems = $em->getRepository('AppBundle:Element')->getChildrenQueryBuilder($element)
227
                    ->getQuery()
228
                    ->getResult();
229
230
                if (in_array($item, $data)) {
231
                    $element->addLabel($item);
232
                    /** @var Element $child */
233
                    foreach ($childItems as $child) {
234
                        $child->addLabel($item);
235
                    }
236
                } else {
237
                    $element->removeLabel($item);
238
                    /** @var Element $child */
239
                    foreach ($childItems as $child) {
240
                        $child->removeLabel($item);
241
                    }
242
                }
243
            }
244
        }
245
        $em->flush();
246
    }
247
248
    /**
249
     * @param Element $element
250
     * @param ObjectManager $em
251
     * @param Form $form
252
     */
253
    private function updateElementRoles($element, $em, $form)
254
    {
255
        /** @var Actor $actor */
256
        foreach ($element->getPathActors() as $actor) {
257
            $formData = $form
258
                ->get('role'.$actor->getProfile()->getId())->getData();
259
260
            if (!is_array($formData)) {
261
                $formData = [$formData];
262
            }
263
264
            $data = new ArrayCollection($formData);
265
266
            $oldRoles = $element->getRoles();
267
268
            foreach ($oldRoles as $role) {
269
                if ($role->getProfile() === $actor->getProfile()) {
270
                    if (!$data->contains($role->getUser())) {
271
                        $em->remove($role);
272
                    } else {
273
                        $data->removeElement($role->getUser());
274
                    }
275
                }
276
            }
277
278
            foreach ($data as $datum) {
279
                $role = new Role();
280
                $role
281
                    ->setUser($datum)
282
                    ->setProfile($actor->getProfile())
283
                    ->setElement($element);
284
                $em->persist($role);
285
            }
286
        }
287
        $em->flush();
288
    }
289
290
    /**
291
     * @param Element $element
292
     * @param Form $form
293
     */
294
    private function setElementReferencesInForm($element, $form)
295
    {
296
        /** @var ObjectManager $em */
297
        $em = $this->getDoctrine()->getManager();
298
299
        $labels = $element->getLabels();
300
301
        /** @var Reference $reference */
302
        foreach ($element->getPathReferences() as $reference) {
303
            $data = [];
304
            $items = $em->getRepository('AppBundle:Element')->getChildrenQueryBuilder($reference->getTarget())
305
                ->andWhere('node.folder = false')
306
                ->getQuery()
307
                ->getResult();
308
309
            foreach ($labels as $label) {
310
                if (in_array($label, $items)) {
311
                    $data[] = $label;
312
                }
313
            }
314
315
            if (!empty($data)) {
316
                if ($reference->isMultiple()) {
317
                    $form->get('reference'.$reference->getTarget()->getId())->setData($data);
318
                } else {
319
                    $form->get('reference'.$reference->getTarget()->getId())->setData($data[0]);
320
                }
321
            }
322
        }
323
    }
324
325
    /**
326
     * @param Element $element
327
     * @param Form $form
328
     * @param Organization $organization
329
     */
330
    private function setElementRolesInForm($element, $form, $organization)
331
    {
332
        /** @var ObjectManager $em */
333
        $em = $this->getDoctrine()->getManager();
334
335
        $roles = $element->getRoles();
336
337
        /** @var Actor $actor */
338
        foreach ($element->getPathActors() as $actor) {
339
            $data = [];
340
            $items = $em->getRepository('AppBundle:User')->findByOrganizationAndDate($organization);
341
342
            foreach ($roles as $role) {
343
                if ($role->getProfile() === $actor->getProfile() && in_array($role->getUser(), $items)) {
344
                    $data[] = $role->getUser();
345
                }
346
            }
347
348
            if (!empty($data)) {
349
                $form->get('role'.$actor->getProfile()->getId())->setData($data);
350
            }
351
        }
352
    }
353
354
    /**
355
     * @param $page
356
     * @param Element|null $element
357
     * @param $q
358
     * @return Pagerfanta
359
     */
360
    private function getElementListPager($page, $element, $q)
361
    {
362
        /** @var ElementRepository $elementRepository */
363
        $elementRepository = $this->getDoctrine()->getManager()->getRepository('AppBundle:Element');
364
365
        /** @var QueryBuilder $queryBuilder */
366
        $queryBuilder = $elementRepository->getChildrenQueryBuilder($element, true)
367
            ->addSelect('ref')
368
            ->addSelect('r')
369
            ->addSelect('u')
370
            ->addSelect('l')
371
            ->leftJoin('node.references', 'ref')
372
            ->leftJoin('node.roles', 'r')
373
            ->leftJoin('r.profile', 'p')
374
            ->leftJoin('node.labels', 'l')
375
            ->leftJoin('r.user', 'u');
376
377
        if ($q) {
378
            $queryBuilder
379
                ->andWhere('node.name LIKE :tq OR p.nameNeutral LIKE :tq OR l.name LIKE :tq OR u.firstName LIKE :tq OR u.lastName LIKE :tq')
380
                ->setParameter('tq', '%'.$q.'%');
381
        }
382
383
        $adapter = new DoctrineORMAdapter($queryBuilder, false);
384
        $pager = new Pagerfanta($adapter);
385
        $pager
386
            ->setMaxPerPage($this->getParameter('page.size'))
387
            ->setCurrentPage($q ? 1 : $page);
388
        return $pager;
389
    }
390
391
    /**
392
     * @param Request $request
393
     * @param Element|null $element
394
     * @return bool
395
     */
396
    private function processElementMovementOperation(Request $request, $element)
397
    {
398
        $ok = false;
399
        $em = $this->getDoctrine()->getManager();
400
401
        foreach (['up', 'down'] as $op) {
402
            if ($request->get($op)) {
403
                $item = $em->getRepository('AppBundle:Element')->find($request->get($op));
404
                if (null === $item || $item->getParent() !== $element) {
405
                    throw $this->createNotFoundException();
406
                }
407
                $method = 'move'.ucfirst($op);
408
                $em->getRepository('AppBundle:Element')->$method($item);
409
                $ok = true;
410
            }
411
        }
412
        return $ok;
413
    }
414
415
    /**
416
     * @param $items
417
     * @param Element|null $element
418
     * @return array
419
     */
420 View Code Duplication
    private function filterElementsFromItems($items, $element)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
421
    {
422
        $em = $this->getDoctrine()->getManager();
423
424
        $elements = $em->createQueryBuilder()
425
            ->select('e')
426
            ->from('AppBundle:Element', 'e')
427
            ->where('e.id IN (:items)')
428
            ->andWhere('e.parent = :current')
429
            ->andWhere('e.code IS NULL')
430
            ->setParameter('items', $items)
431
            ->setParameter('current', $element)
432
            ->orderBy('e.left')
433
            ->getQuery()
434
            ->getResult();
435
436
        return $elements;
437
    }
438
439
    /**
440
     * @param $items
441
     * @param Element|null $element
442
     */
443 View Code Duplication
    private function deleteElements($items, $element)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
444
    {
445
        $em = $this->getDoctrine()->getManager();
446
447
        $em->createQueryBuilder()
448
            ->delete('AppBundle:Element', 'e')
449
            ->where('e.id IN (:items)')
450
            ->andWhere('e.parent = :current')
451
            ->andWhere('e.code IS NULL')
452
            ->setParameter('items', $items)
453
            ->setParameter('current', $element)
454
            ->getQuery()
455
            ->execute();
456
457
        $em->flush();
458
    }
459
460
    /**
461
     * @param $path
462
     * @param Request $request
463
     * @param $items
464
     * @param Element|null $element
465
     * @return bool|\Symfony\Component\HttpFoundation\RedirectResponse
466
     */
467
    private function processElementRemoveOperation($path, Request $request, $items, $element)
468
    {
469
        if ($request->get('confirm', '') === 'ok') {
470
            try {
471
                $this->deleteElements($items, $element);
472
                $this->addFlash('success', $this->get('translator')->trans('message.deleted', [], 'element'));
473
            } catch (\Exception $e) {
474
                $this->addFlash('error', $this->get('translator')->trans('message.delete_error', [], 'element'));
475
            }
476
            return $this->redirectToRoute('organization_element_list', ['path' => $path]);
477
        }
478
        return false;
479
    }
480
481
    /**
482
     * @param Request $request
483
     * @param $element
484
     * @param $organization
485
     * @return array
486
     */
487
    private function getElementBreadcrumbAndTitle(Request $request, $element, $organization)
488
    {
489
        $new = in_array($request->get('_route'), ['organization_element_new', 'organization_element_folder_new'], true);
490
        $breadcrumb = $this->generateBreadcrumb($element, !$new);
491
492
        if ($new) {
493
            $newElement = new Element();
494
            $newElement
495
                ->setParent($element)
496
                ->setOrganization($organization)
497
                ->setFolder($request->get('_route') === 'organization_element_folder_new');
498
499
            $element = $newElement;
500
501
            $title = $this->get('translator')->trans($newElement->isFolder() ? 'title.new_folder' : 'title.new', [], 'element');
502
            $breadcrumb[] = ['fixed' => $title];
503
        } else {
504
            $title = $this->get('translator')->trans('title.edit', [], 'element');
505
        }
506
        return array($breadcrumb, $element, $title);
507
    }
508
}
509