Completed
Push — master ( 812d98...4d5eff )
by Luis Ramón
02:16
created

FolderController::uploadFormAction()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 35
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 35
rs 8.439
c 0
b 0
f 0
cc 6
eloc 23
nc 6
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\Documentation;
22
23
use AppBundle\Entity\Documentation\Entry;
24
use AppBundle\Entity\Documentation\Folder;
25
use AppBundle\Entity\Documentation\FolderPermission;
26
use AppBundle\Entity\Documentation\FolderRepository;
27
use AppBundle\Entity\Documentation\History;
28
use AppBundle\Entity\Documentation\Version;
29
use AppBundle\Entity\ElementRepository;
30
use AppBundle\Entity\Organization;
31
use AppBundle\Form\Model\DocumentUpload;
32
use AppBundle\Form\Type\Documentation\FolderType;
33
use AppBundle\Form\Type\Documentation\UploadType;
34
use AppBundle\Security\FolderVoter;
35
use AppBundle\Security\OrganizationVoter;
36
use Doctrine\Common\Collections\ArrayCollection;
37
use Doctrine\ORM\EntityManager;
38
use Doctrine\ORM\QueryBuilder;
39
use Pagerfanta\Adapter\DoctrineORMAdapter;
40
use Pagerfanta\Pagerfanta;
41
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
42
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
43
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
44
use Symfony\Component\Form\Form;
45
use Symfony\Component\HttpFoundation\File\UploadedFile;
46
use Symfony\Component\HttpFoundation\Request;
47
48
/**
49
 * @Route("/documentos")
50
 */
51
class FolderController extends Controller
52
{
53
    /**
54
     * @Route("/carpeta/{id}/nueva", name="documentation_folder_new", methods={"GET", "POST"})
55
     * @Route("/carpeta/{id}", name="documentation_folder_form", requirements={"id" = "\d+"}, methods={"GET", "POST"})
56
     * @Security("is_granted('FOLDER_MANAGE', folder)")
57
     */
58
    public function folderFormAction(Folder $folder = null, Request $request)
59
    {
60
        $organization = $this->get('AppBundle\Service\UserExtensionService')->getCurrentOrganization();
61
        $this->denyAccessUnlessGranted(OrganizationVoter::MANAGE, $organization);
62
63
        $em = $this->getDoctrine()->getManager();
64
        $new = $request->get('_route') === 'documentation_folder_new';
65
66
        $sourceFolder = $folder;
67
68
        if ($new) {
69
            $newFolder = new Folder();
70
            $newFolder
71
                ->setOrganization($organization)
72
                ->setParent($folder);
73
            $folder = $newFolder;
74
            $em->persist($folder);
75
        } else {
76
            if (null === $sourceFolder->getParent()) {
77
                throw $this->createAccessDeniedException();
78
            }
79
        }
80
        $breadcrumb = $sourceFolder->getParent() ? $this->generateBreadcrumb($sourceFolder, false) : [];
81
82
        if ($request->request->get('folder')) {
83
            $folder->setType($request->request->get('folder')['type']);
1 ignored issue
show
Bug introduced by
It seems like $folder is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
84
        }
85
        $form = $this->createForm(FolderType::class, $folder, [
86
            'new' => $new,
87
            'allow_extra_fields' => !$request->request->has('submit')
88
        ]);
89
90
        $this->setFolderRolesInForm($folder, $form);
91
        $form->handleRequest($request);
92
        $breadcrumb[] = ['fixed' => $this->get('translator')->trans($new ? 'title.folder.new' : 'title.folder.edit', [], 'documentation')];
93
94
        if ($form->isSubmitted() && $form->isValid() && $request->request->has('submit')) {
95
            try {
96
                $this->updateFolderRolesFromForm($folder, $em, $form);
1 ignored issue
show
Bug introduced by
It seems like $folder defined by parameter $folder on line 58 can be null; however, AppBundle\Controller\Doc...teFolderRolesFromForm() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
97
                $em->flush();
98
                $this->addFlash('success', $this->get('translator')->trans('message.folder.saved', [], 'documentation'));
99
                return $this->redirectToRoute('documentation', ['id' => $sourceFolder->getId()]);
100
            } catch (\Exception $e) {
101
                $this->addFlash('error', $this->get('translator')->trans('message.folder.save_error', [], 'documentation'));
102
            }
103
        }
104
105
        return $this->render('documentation/folder_form.html.twig', [
106
            'menu_path' => 'documentation',
107
            'breadcrumb' => $breadcrumb,
108
            'title' => $this->get('translator')->trans($new ? 'title.folder.new' : 'title.folder.edit', [], 'documentation'),
109
            'form' => $form->createView(),
110
            'folder' => $folder
111
        ]);
112
    }
113
114
    /**
115
     * @param Folder $folder
116
     * @param Form $form
117
     */
118
    private function setFolderRolesInForm(Folder $folder = null, Form $form)
119
    {
120
        if (null === $folder) {
121
            return;
122
        }
123
124
        $permissions = $folder->getPermissions();
125
126
        $permissionTypes = [
127
            'access' => FolderPermission::PERMISSION_VISIBLE,
128
            'manager' => FolderPermission::PERMISSION_MANAGE,
129
            'upload' => FolderPermission::PERMISSION_UPLOAD,
130
            'review' => FolderPermission::PERMISSION_REVIEW,
131
            'approve' => FolderPermission::PERMISSION_APPROVE
132
        ];
133
134
        foreach ($permissionTypes as $name => $type) {
135
            if ($form->has('profiles_'.$name)) {
136
                $data = [];
137
138
                /** @var FolderPermission $permission */
139
                foreach ($permissions as $permission) {
140
                    if ($permission->getPermission() === $type) {
141
                        $data[] = $permission->getElement();
142
                    }
143
                }
144
145
                if (!empty($data)) {
146
                    $form->get('profiles_' . $name)->setData($data);
147
                }
148
            }
149
        }
150
    }
151
152
    /**
153
     * @param Folder $folder
154
     * @param EntityManager $em
155
     * @param Form $form
156
     */
157
    private function updateFolderRolesFromForm($folder, EntityManager $em, $form)
158
    {
159
        $oldPermissions = $folder->getPermissions();
160
161
        $permissionTypes = [
162
            'access' => FolderPermission::PERMISSION_VISIBLE,
163
            'manager' => FolderPermission::PERMISSION_MANAGE,
164
            'upload' => FolderPermission::PERMISSION_UPLOAD,
165
            'review' => FolderPermission::PERMISSION_REVIEW,
166
            'approve' => FolderPermission::PERMISSION_APPROVE
167
        ];
168
169
        foreach ($permissionTypes as $name => $type) {
170
171
            $data = $form->has('profiles_' . $name) ? $form->get('profiles_' . $name)->getData() : [];
172
            if (!$data instanceof ArrayCollection) {
173
                $data = new ArrayCollection($data);
174
            }
175
176
            /** @var FolderPermission $permission */
177
            foreach ($oldPermissions as $permission) {
178
                if ($permission->getPermission() === $type) {
179
                    if (!$data->contains($permission->getElement())) {
180
                        $em->remove($permission);
181
                    } else {
182
                        $data->removeElement($permission->getElement());
183
                    }
184
                }
185
            }
186
187
            foreach ($data as $datum) {
188
                $permission = new FolderPermission();
189
                $permission
190
                    ->setFolder($folder)
191
                    ->setPermission($type)
192
                    ->setElement($datum);
193
                $em->persist($permission);
194
            }
195
        }
196
        $em->flush();
197
    }
198
199
    /**
200
     * @Route("/operacion/{id}", name="documentation_operation", requirements={"id" = "\d+"}, methods={"POST"})
201
     * @Security("is_granted('FOLDER_MANAGE', folder)")
202
     */
203
    public function operationAction($id, Request $request)
204
    {
205
        $organization = $this->get('AppBundle\Service\UserExtensionService')->getCurrentOrganization();
206
207
        $folder = $this->getFolder($organization, $id);
208
209
        if (null === $folder || $folder->getOrganization() !== $organization) {
210
            throw $this->createNotFoundException();
211
        }
212
        $ok = false;
213
        $em = $this->getDoctrine()->getManager();
214
        foreach (['up', 'down'] as $op) {
215
            if ($request->get($op)) {
216
                $method = 'move'.ucfirst($op);
217
                $em->getRepository('AppBundle:Documentation\Folder')->$method($folder);
218
                $ok = true;
219
            }
220
        }
221
        if ($ok) {
222
            $em->flush();
223
        }
224
        return $this->redirectToRoute('documentation', ['id' => $folder->getId()]);
225
    }
226
227
    /**
228
     * @Route("/{id}/{page}", name="documentation", requirements={"page" = "\d+", "id" = "\d+"}, defaults={"page" = "1", "folder" = null}, methods={"GET"})
229
     */
230
    public function browseAction($page, $id = null, Request $request)
231
    {
232
        $organization = $this->get('AppBundle\Service\UserExtensionService')->getCurrentOrganization();
233
234
        $q = $request->get('q', null);
235
236
        $folder = (null === $id) ? $this->getRootFolder($organization) : $this->getFolder($organization, $id);
237
238
        if (null === $folder) {
239
            throw $this->createNotFoundException();
240
        }
241
        $this->denyAccessUnlessGranted('FOLDER_ACCESS', $folder);
242
243
        $pager = $this->getFolderEntriesPager($page, $folder, $q);
244
245
        $breadcrumb = $this->generateBreadcrumb($folder);
246
247
        return $this->render('documentation/list.html.twig', [
248
            'breadcrumb' => $breadcrumb,
249
            'pager' => $pager,
250
            'current' => $folder,
251
            'permissions' => ['is_folder_manager' => $this->isGranted('FOLDER_MANAGE', $folder), 'is_organization_manager' => $this->isGranted('ORGANIZATION_MANAGE', $organization)],
252
            'tree' => $this->getOrganizationTree($this->getRootFolder($organization), $folder),
253
            'q' => $q,
254
            'domain' => 'element'
255
        ]);
256
    }
257
258
    /**
259
     * @param Organization $organization
260
     * @return Folder
261
     */
262 View Code Duplication
    private function getRootFolder($organization)
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...
263
    {
264
        /** @var FolderRepository $folderRepository */
265
        $folderRepository = $this->getDoctrine()->getManager()->getRepository('AppBundle:Documentation\Folder');
266
267
        /** @var Folder|null $folder */
268
        $folder = $folderRepository->findOneBy(['organization' => $organization, 'parent' => null]);
269
270
        return $folder;
271
    }
272
273
    /**
274
     * @param Organization $organization
275
     * @param int $id
276
     * @return Folder
277
     */
278 View Code Duplication
    private function getFolder($organization, $id)
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...
279
    {
280
        /** @var FolderRepository $folderRepository */
281
        $folderRepository = $this->getDoctrine()->getManager()->getRepository('AppBundle:Documentation\Folder');
282
283
        /** @var Folder|null $folder */
284
        $folder = $folderRepository->findOneBy(['organization' => $organization, 'id' => $id]);
285
286
        return $folder;
287
    }
288
289
    /**
290
     * @param $page
291
     * @param Folder|null $folder
292
     * @param $q
293
     * @return Pagerfanta
294
     */
295
    private function getFolderEntriesPager($page, Folder $folder = null, $q)
296
    {
297
        /** @var ElementRepository $entriesRepository */
298
        $entriesRepository = $this->getDoctrine()->getManager()->getRepository('AppBundle:Documentation\Entry');
299
300
        // obtener las carpetas
301
        $folders = $this->getDoctrine()->getManager()->getRepository('AppBundle:Documentation\Folder')->getChildren($folder, false, false, 'ASC', true);
302
303
        /** @var QueryBuilder $queryBuilder */
304
        $queryBuilder = $entriesRepository->createQueryBuilder('e')
305
            ->andWhere('e.folder IN (:folders)')
306
            ->setParameter('folders', $folders)
307
            ->join('e.folder', 'f')
308
            ->addOrderBy('f.left')
309
            ->addOrderBy('e.position')
310
            ->addSelect('v')
311
            ->leftJoin('e.currentVersion', 'v');
312
313
        if ($q) {
314
            $queryBuilder
315
                ->andWhere('e.name LIKE :tq')
316
                ->setParameter('tq', '%'.$q.'%');
317
        }
318
319
        $adapter = new DoctrineORMAdapter($queryBuilder, false);
320
        $pager = new Pagerfanta($adapter);
321
        $pager
322
            ->setMaxPerPage($this->getParameter('page.size'))
323
            ->setCurrentPage($q ? 1 : $page);
324
325
        return $pager;
326
    }
327
328
    /**
329
     * Returns breadcrumb that matches the folder (ignores root element)
330
     * @param Folder $folder
331
     * @param bool $ignoreLast
332
     * @return array
333
     */
334 View Code Duplication
    private function generateBreadcrumb(Folder $folder = 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...
335
    {
336
        $breadcrumb = [];
337
338
        if (null === $folder) {
339
            return null;
340
        }
341
342
        $item = $folder;
343
        while ($item->getParent()) {
344
            $entry = ['fixed' => $item->getName()];
345
            if ($item !== $folder || !$ignoreLast) {
346
                $entry['routeName'] = 'documentation';
347
                $entry['routeParams'] = ['id' => $item->getId()];
348
            }
349
            array_unshift($breadcrumb, $entry);
350
            $item = $item->getParent();
351
        }
352
        return $breadcrumb;
353
    }
354
355
    /**
356
     * Returns folder tree
357
     *
358
     * @param Folder $folder
359
     * @param Folder $current
360
     *
361
     * @return array
362
     */
363
    private function getOrganizationTree(Folder $folder, Folder $current = null)
364
    {
365
        /** @var FolderRepository $folderRepository */
366
        $folderRepository = $this->getDoctrine()->getManager()->getRepository('AppBundle:Documentation\Folder');
367
368
        $children = $folderRepository->childrenHierarchy($folder);
369
370
        $organization = $folder->getOrganization();
371
        $disabled = $this->isGranted('ORGANIZATION_MANAGE', $organization) ? [] : array_map(function (Folder $f) {
372
            return $f->getId();
373
        }, $folderRepository->getAccessDeniedFoldersForUserAndOrganizationArray($this->getUser(), $organization));
374
375
        list($tree) = $this->processChildren($children, $current ? $current->getId() : null, $disabled);
1 ignored issue
show
Bug introduced by
It seems like $children can also be of type string; however, AppBundle\Controller\Doc...ller::processChildren() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
376
377
        return $tree;
378
    }
379
380
    /**
381
     * Convert children array into a treeview array
382
     *
383
     * @param array $children
384
     * @param integer $currentId
385
     * @param array $disabledId
386
     * @return mixed
387
     */
388
    private function processChildren(array $children, $currentId = null, $disabledId = [])
389
    {
390
        $result = [];
391
        $selected = false;
392
        foreach ($children as $child) {
393
            $item = [];
394
            $item['text'] = $child['name'];
395
396
            $disabled = in_array($child['id'], $disabledId,false);
397
            if ($disabled) {
398
                $item['state'] = ['disabled' => true];
399
            }
400
            if ($currentId === $child['id']) {
401
                $item['state'] = ['selected' => true, 'expanded' => true];
402
                $selected = true;
403
            }
404
            if (!$disabled && count($child['__children']) > 0) {
405
                list($item['nodes'], $selected) = $this->processChildren($child['__children'], $currentId, $disabledId);
406
            } else {
407
                $item['icon'] = 'fa fa-folder';
408
            }
409
            if ($selected) {
410
                if (!isset($item['state'])) {
411
                    $item['state'] = [];
412
                }
413
                $item['state']['expanded'] = true;
414
            }
415
            $item['href'] = $this->generateUrl('documentation', ['id' => $child['id']]);
416
            $result[] = $item;
417
        }
418
419
        return [$result, $selected];
420
    }
421
422
    /**
423
     * @Route("/carpeta/{id}/subir", name="documentation_folder_upload", methods={"GET", "POST"})
424
     * @Security("is_granted('FOLDER_UPLOAD', folder) and folder.getType() != constant('AppBundle\\Entity\\Documentation\\Folder::TYPE_TASKS')")
425
     */
426
    public function uploadFormAction(Request $request, Folder $folder)
427
    {
428
        $breadcrumb = $this->generateBreadcrumb($folder, false);
429
430
        $title = $this->get('translator')->trans('title.entry.new', [], 'documentation');
431
        $breadcrumb[] = ['fixed' => $title];
432
433
        $upload = new DocumentUpload();
434
435
        if ($this->isGranted(FolderVoter::MANAGE, $folder)) {
436
            $profiles = $this->getDoctrine()->getManager()->getRepository('AppBundle:Element')->findAllProfilesByFolderPermission($folder, FolderPermission::PERMISSION_UPLOAD, true);
437
        } else {
438
            $profiles = $this->getDoctrine()->getManager()->getRepository('AppBundle:Element')->findAllProfilesByFolderPermissionAndUser($folder, FolderPermission::PERMISSION_UPLOAD, $this->getUser(), true);
439
        }
440
        $form = $this->createForm(UploadType::class, $upload, ['upload_profiles' => $profiles]);
441
442
        $form->handleRequest($request);
443
444
        if ($form->isSubmitted() && $form->isValid()) {
445
            $state = $this->getUploadStatus($request, $folder);
446
            if (null !== $state && $this->processFileUpload($folder, $upload, $state)) {
447
                $this->addFlash('success', $this->get('translator')->trans('message.upload.save_ok', [], 'upload'));
448
                return $this->redirectToRoute('documentation', ['id' => $folder->getId()]);
449
            }
450
            $this->addFlash('error', $this->get('translator')->trans('message.upload.save_error', [], 'upload'));
451
        }
452
453
        return $this->render('documentation/folder_upload.html.twig', [
454
            'menu_path' => 'documentation',
455
            'title' => $title,
456
            'breadcrumb' => $breadcrumb,
457
            'folder' => $folder,
458
            'form' => $form->createView()
459
        ]);
460
    }
461
462
    private function processFileUpload(Folder $folder, DocumentUpload $upload, $state = Version::STATUS_APPROVED)
463
    {
464
        $em = $this->getDoctrine()->getManager();
465
        $processedFileName = null;
466
        $filesystem = $this->get('entries_filesystem');
467
        try {
468
            /** @var UploadedFile $file */
469
            $file = $upload->getFile();
470
            $fileName = hash_file('sha256', $file->getRealPath());
471
            $fileName = substr($fileName, 0, 2).'/'.substr($fileName, 2, 2).'/'.$fileName;
472
            if (!$filesystem->has($fileName)) {
473
                $filesystem->write($fileName, file_get_contents($file->getRealPath()));
474
            }
475
            $processedFileName = $fileName;
476
477
            $entry = new Entry();
478
            $entry
479
                ->setName($upload->getTitle() ?: $file->getClientOriginalName())
480
                ->setFolder($folder)
481
                ->setElement($upload->getUploadProfile())
482
                ->setDescription($upload->getDescription());
483
484
            if ($upload->getCreateDate()) {
485
                $entry->setCreatedAt($upload->getCreateDate());
486
            }
487
488
            $em->persist($entry);
489
490
            $version = new Version();
491
            $version
492
                ->setEntry($entry)
493
                ->setFile($fileName)
494
                ->setState($state)
495
                ->setVersionNr($upload->getVersion());
496
497
            $entry->setCurrentVersion($version);
498
499
            $em->persist($version);
500
501
            $history = new History();
502
            $history
503
                ->setEntry($entry)
504
                ->setVersion($upload->getVersion())
505
                ->setCreatedBy($this->getUser())
506
                ->setEvent(History::LOG_CREATE);
507
508
            $em->persist($history);
509
510
            $em->flush();
511
512
            return true;
513
        } catch (\Exception $e) {
514
            // seguir la ejecución si ha ocurrido una excepción por el camino
515
        }
516
517
        if (null !== $processedFileName) {
518
            // ha ocurrido un error pero el fichero se había almacenado, borrarlo si no se estaba usando
519
            if (0 == (int) $em->getRepository('AppBundle:Documentation\Version')->countByFile($processedFileName)) {
520
                $filesystem->delete($processedFileName);
521
            }
522
        }
523
524
        return false;
525
    }
526
527
    /**
528
     * @param Request $request
529
     * @param Folder $folder
530
     * @return int|null
531
     */
532
    private function getUploadStatus(Request $request, Folder $folder)
533
    {
534
        $state = null;
535
        switch ($folder->getType()) {
536
            case Folder::TYPE_NORMAL:
537
                $state = Version::STATUS_APPROVED;
538
                break;
539
            case Folder::TYPE_WORKFLOW:
540
                $state = ($request->request->has('approve') && $this->isGranted(FolderVoter::APPROVE, $folder)) ? Version::STATUS_APPROVED : Version::STATUS_DRAFT;
541
        }
542
        return $state;
543
    }
544
}
545