Passed
Push — 1.11.x ( bce6cd...c146d9 )
by Angel Fernando Quiroz
12:25
created

main/inc/lib/PortfolioController.php (12 issues)

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Component\Utils\ChamiloApi;
6
use Chamilo\CoreBundle\Entity\Course as CourseEntity;
7
use Chamilo\CoreBundle\Entity\ExtraField as ExtraFieldEntity;
8
use Chamilo\CoreBundle\Entity\ExtraFieldRelTag;
9
use Chamilo\CoreBundle\Entity\Portfolio;
10
use Chamilo\CoreBundle\Entity\PortfolioAttachment;
11
use Chamilo\CoreBundle\Entity\PortfolioCategory;
12
use Chamilo\CoreBundle\Entity\PortfolioComment;
13
use Chamilo\UserBundle\Entity\User;
14
use Doctrine\ORM\Query\Expr\Join;
15
use Symfony\Component\Filesystem\Filesystem;
16
use Symfony\Component\HttpFoundation\Request as HttpRequest;
0 ignored issues
show
This use statement conflicts with another class in this namespace, HttpRequest. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
17
18
/**
19
 * Class PortfolioController.
20
 */
21
class PortfolioController
22
{
23
    /**
24
     * @var string
25
     */
26
    public $baseUrl;
27
    /**
28
     * @var CourseEntity|null
29
     */
30
    private $course;
31
    /**
32
     * @var \Chamilo\CoreBundle\Entity\Session|null
33
     */
34
    private $session;
35
    /**
36
     * @var \Chamilo\UserBundle\Entity\User
37
     */
38
    private $owner;
39
    /**
40
     * @var \Doctrine\ORM\EntityManager
41
     */
42
    private $em;
43
44
    /**
45
     * PortfolioController constructor.
46
     */
47
    public function __construct()
48
    {
49
        $this->em = Database::getManager();
50
51
        $this->owner = api_get_user_entity(api_get_user_id());
52
        $this->course = api_get_course_entity(api_get_course_int_id());
53
        $this->session = api_get_session_entity(api_get_session_id());
54
55
        $cidreq = api_get_cidreq();
56
        $this->baseUrl = api_get_self().'?'.($cidreq ? $cidreq.'&' : '');
57
    }
58
59
    /**
60
     * @throws \Doctrine\ORM\ORMException
61
     * @throws \Doctrine\ORM\OptimisticLockException
62
     */
63
    public function addCategory()
64
    {
65
        global $interbreadcrumb;
66
67
        Display::addFlash(
68
            Display::return_message(get_lang('PortfolioCategoryFieldHelp'), 'info')
69
        );
70
71
        $form = new FormValidator('add_category', 'post', "{$this->baseUrl}&action=add_category");
72
73
        if (api_get_configuration_value('save_titles_as_html')) {
74
            $form->addHtmlEditor('title', get_lang('Title'), true, false, ['ToolbarSet' => 'TitleAsHtml']);
75
        } else {
76
            $form->addText('title', get_lang('Title'));
77
            $form->applyFilter('title', 'trim');
78
        }
79
80
        $form->addHtmlEditor('description', get_lang('Description'), false, false, ['ToolbarSet' => 'Minimal']);
81
        $form->addButtonCreate(get_lang('Create'));
82
83
        if ($form->validate()) {
84
            $values = $form->exportValues();
85
86
            $category = new PortfolioCategory();
87
            $category
88
                ->setTitle($values['title'])
89
                ->setDescription($values['description'])
90
                ->setUser($this->owner);
91
92
            $this->em->persist($category);
93
            $this->em->flush();
94
95
            Display::addFlash(
96
                Display::return_message(get_lang('CategoryAdded'), 'success')
97
            );
98
99
            header("Location: {$this->baseUrl}");
100
            exit;
101
        }
102
103
        $interbreadcrumb[] = [
104
            'name' => get_lang('Portfolio'),
105
            'url' => $this->baseUrl,
106
        ];
107
108
        $actions = [];
109
        $actions[] = Display::url(
110
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
111
            $this->baseUrl
112
        );
113
114
        $content = $form->returnForm();
115
116
        $this->renderView($content, get_lang('AddCategory'), $actions);
117
    }
118
119
    /**
120
     * @throws \Doctrine\ORM\ORMException
121
     * @throws \Doctrine\ORM\OptimisticLockException
122
     * @throws \Exception
123
     */
124
    public function editCategory(PortfolioCategory $category)
125
    {
126
        global $interbreadcrumb;
127
128
        if (!$this->categoryBelongToOwner($category)) {
129
            api_not_allowed(true);
130
        }
131
132
        Display::addFlash(
133
            Display::return_message(get_lang('PortfolioCategoryFieldHelp'), 'info')
134
        );
135
136
        $form = new FormValidator(
137
            'edit_category',
138
            'post',
139
            $this->baseUrl."action=edit_category&id={$category->getId()}"
140
        );
141
142
        if (api_get_configuration_value('save_titles_as_html')) {
143
            $form->addHtmlEditor('title', get_lang('Title'), true, false, ['ToolbarSet' => 'TitleAsHtml']);
144
        } else {
145
            $form->addText('title', get_lang('Title'));
146
            $form->applyFilter('title', 'trim');
147
        }
148
149
        $form->addHtmlEditor('description', get_lang('Description'), false, false, ['ToolbarSet' => 'Minimal']);
150
        $form->addButtonUpdate(get_lang('Update'));
151
        $form->setDefaults(
152
            [
153
                'title' => $category->getTitle(),
154
                'description' => $category->getDescription(),
155
            ]
156
        );
157
158
        if ($form->validate()) {
159
            $values = $form->exportValues();
160
161
            $category
162
                ->setTitle($values['title'])
163
                ->setDescription($values['description']);
164
165
            $this->em->persist($category);
166
            $this->em->flush();
167
168
            Display::addFlash(
169
                Display::return_message(get_lang('Updated'), 'success')
170
            );
171
172
            header("Location: $this->baseUrl");
173
            exit;
174
        }
175
176
        $interbreadcrumb[] = [
177
            'name' => get_lang('Portfolio'),
178
            'url' => $this->baseUrl,
179
        ];
180
181
        $actions = [];
182
        $actions[] = Display::url(
183
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
184
            $this->baseUrl
185
        );
186
187
        $content = $form->returnForm();
188
189
        $this->renderView($content, get_lang('EditCategory'), $actions);
190
    }
191
192
    /**
193
     * @throws \Doctrine\ORM\ORMException
194
     * @throws \Doctrine\ORM\OptimisticLockException
195
     */
196
    public function showHideCategory(PortfolioCategory $category)
197
    {
198
        if (!$this->categoryBelongToOwner($category)) {
199
            api_not_allowed(true);
200
        }
201
202
        $category->setIsVisible(!$category->isVisible());
203
204
        $this->em->persist($category);
205
        $this->em->flush();
206
207
        Display::addFlash(
208
            Display::return_message(get_lang('VisibilityChanged'), 'success')
209
        );
210
211
        header("Location: $this->baseUrl");
212
        exit;
213
    }
214
215
    /**
216
     * @throws \Doctrine\ORM\ORMException
217
     * @throws \Doctrine\ORM\OptimisticLockException
218
     */
219
    public function deleteCategory(PortfolioCategory $category)
220
    {
221
        if (!$this->categoryBelongToOwner($category)) {
222
            api_not_allowed(true);
223
        }
224
225
        $this->em->remove($category);
226
        $this->em->flush();
227
228
        Display::addFlash(
229
            Display::return_message(get_lang('CategoryDeleted'), 'success')
230
        );
231
232
        header("Location: $this->baseUrl");
233
        exit;
234
    }
235
236
    /**
237
     * @throws \Doctrine\ORM\ORMException
238
     * @throws \Doctrine\ORM\OptimisticLockException
239
     * @throws \Doctrine\ORM\TransactionRequiredException
240
     * @throws \Exception
241
     */
242
    public function addItem()
243
    {
244
        global $interbreadcrumb;
245
246
        $categories = $this->em
247
            ->getRepository('ChamiloCoreBundle:PortfolioCategory')
248
            ->findBy(['user' => $this->owner]);
249
250
        $form = new FormValidator('add_portfolio', 'post', $this->baseUrl.'action=add_item');
251
252
        if (api_get_configuration_value('save_titles_as_html')) {
253
            $form->addHtmlEditor('title', get_lang('Title'), true, false, ['ToolbarSet' => 'TitleAsHtml']);
254
        } else {
255
            $form->addText('title', get_lang('Title'));
256
            $form->applyFilter('title', 'trim');
257
        }
258
259
        $form->addHtmlEditor('content', get_lang('Content'), true, false, ['ToolbarSet' => 'NotebookStudent']);
260
        $form->addSelectFromCollection(
261
            'category',
262
            [get_lang('Category'), get_lang('PortfolioCategoryFieldHelp')],
263
            $categories,
264
            [],
265
            true
266
        );
267
268
        $extraField = new ExtraField('portfolio');
269
        $extra = $extraField->addElements($form);
270
271
        $this->addAttachmentsFieldToForm($form);
272
273
        $form->addButtonCreate(get_lang('Create'));
274
275
        if ($form->validate()) {
276
            $values = $form->exportValues();
277
            $currentTime = new DateTime(
278
                api_get_utc_datetime(),
279
                new DateTimeZone('UTC')
280
            );
281
282
            $portfolio = new Portfolio();
283
            $portfolio
284
                ->setTitle($values['title'])
285
                ->setContent($values['content'])
286
                ->setUser($this->owner)
287
                ->setCourse($this->course)
288
                ->setSession($this->session)
289
                ->setCategory(
290
                    $this->em->find('ChamiloCoreBundle:PortfolioCategory', $values['category'])
291
                )
292
                ->setCreationDate($currentTime)
293
                ->setUpdateDate($currentTime);
294
295
            $this->em->persist($portfolio);
296
            $this->em->flush();
297
298
            $values['item_id'] = $portfolio->getId();
299
300
            $extraFieldValue = new ExtraFieldValue('portfolio');
301
            $extraFieldValue->saveFieldValues($values);
302
303
            $this->processAttachments(
304
                $form,
305
                $portfolio->getUser(),
306
                $portfolio->getId(),
307
                PortfolioAttachment::TYPE_ITEM
308
            );
309
310
            $hook = HookPortfolioItemAdded::create();
311
            $hook->setEventData(['portfolio' => $portfolio]);
312
            $hook->notifyItemAdded();
313
314
            if (1 == api_get_course_setting('email_alert_teachers_new_post')) {
315
                if ($this->session) {
316
                    $messageCourseTitle = "{$this->course->getTitle()} ({$this->session->getName()})";
0 ignored issues
show
The method getTitle() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

316
                    $messageCourseTitle = "{$this->course->/** @scrutinizer ignore-call */ getTitle()} ({$this->session->getName()})";

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
317
318
                    $teachers = SessionManager::getCoachesByCourseSession(
319
                        $this->session->getId(),
320
                        $this->course->getId()
321
                    );
322
                    $userIdListToSend = array_values($teachers);
323
                } else {
324
                    $messageCourseTitle = $this->course->getTitle();
325
326
                    $teachers = CourseManager::get_teacher_list_from_course_code($this->course->getCode());
327
328
                    $userIdListToSend = array_keys($teachers);
329
                }
330
331
                $messageSubject = sprintf(get_lang('PortfolioAlertNewPostSubject'), $messageCourseTitle);
332
333
                foreach ($userIdListToSend as $userIdToSend) {
334
                    $messageContent = sprintf(
335
                        get_lang('PortfolioAlertNewPostContent'),
336
                        $this->owner->getCompleteName(),
337
                        $messageCourseTitle,
338
                        $this->baseUrl.http_build_query(['action' => 'view', 'id' => $portfolio->getId()])
339
                    );
340
341
                    MessageManager::send_message_simple($userIdToSend, $messageSubject, $messageContent, 0, false, false, [], false);
342
                }
343
            }
344
345
            Display::addFlash(
346
                Display::return_message(get_lang('PortfolioItemAdded'), 'success')
347
            );
348
349
            header("Location: $this->baseUrl");
350
            exit;
351
        }
352
353
        $interbreadcrumb[] = [
354
            'name' => get_lang('Portfolio'),
355
            'url' => $this->baseUrl,
356
        ];
357
358
        $actions = [];
359
        $actions[] = Display::url(
360
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
361
            $this->baseUrl
362
        );
363
364
        $content = $form->returnForm();
365
366
        $this->renderView(
367
            $content."<script> $(function() { {$extra['jquery_ready_content']} }); </script>",
368
            get_lang('AddPortfolioItem'),
369
            $actions
370
        );
371
    }
372
373
    /**
374
     * @throws \Doctrine\ORM\ORMException
375
     * @throws \Doctrine\ORM\OptimisticLockException
376
     * @throws \Doctrine\ORM\TransactionRequiredException
377
     * @throws \Exception
378
     */
379
    public function editItem(Portfolio $item)
380
    {
381
        global $interbreadcrumb;
382
383
        if (!$this->itemBelongToOwner($item)) {
384
            api_not_allowed(true);
385
        }
386
387
        $categories = $this->em
388
            ->getRepository('ChamiloCoreBundle:PortfolioCategory')
389
            ->findBy(['user' => $this->owner]);
390
391
        $form = new FormValidator('edit_portfolio', 'post', $this->baseUrl."action=edit_item&id={$item->getId()}");
392
393
        if (api_get_configuration_value('save_titles_as_html')) {
394
            $form->addHtmlEditor('title', get_lang('Title'), true, false, ['ToolbarSet' => 'TitleAsHtml']);
395
        } else {
396
            $form->addText('title', get_lang('Title'));
397
            $form->applyFilter('title', 'trim');
398
        }
399
400
        if ($item->getOrigin()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $item->getOrigin() of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
401
            if (Portfolio::TYPE_ITEM === $item->getOriginType()) {
402
                $origin = $this->em->find(Portfolio::class, $item->getOrigin());
403
404
                $form->addLabel(
405
                    sprintf(get_lang('PortfolioItemFromXUser'), $origin->getUser()->getCompleteName()),
406
                    Display::panel(
407
                        Security::remove_XSS($origin->getContent())
408
                    )
409
                );
410
            } elseif (Portfolio::TYPE_COMMENT === $item->getOriginType()) {
411
                $origin = $this->em->find(PortfolioComment::class, $item->getOrigin());
412
413
                $form->addLabel(
414
                    sprintf(get_lang('PortfolioCommentFromXUser'), $origin->getAuthor()->getCompleteName()),
415
                    Display::panel(
416
                        Security::remove_XSS($origin->getContent())
417
                    )
418
                );
419
            }
420
        }
421
422
        $form->addHtmlEditor('content', get_lang('Content'), true, false, ['ToolbarSet' => 'NotebookStudent']);
423
        $form->addSelectFromCollection(
424
            'category',
425
            [get_lang('Category'), get_lang('PortfolioCategoryFieldHelp')],
426
            $categories,
427
            [],
428
            true
429
        );
430
431
        $extraField = new ExtraField('portfolio');
432
        $extra = $extraField->addElements($form, $item->getId());
433
434
        $attachmentList = $this->generateAttachmentList($item, false);
435
436
        if (!empty($attachmentList)) {
437
            $form->addLabel(get_lang('AttachmentFiles'), $attachmentList);
438
        }
439
440
        $this->addAttachmentsFieldToForm($form);
441
442
        $form->addButtonUpdate(get_lang('Update'));
443
        $form->setDefaults(
444
            [
445
                'title' => $item->getTitle(),
446
                'content' => $item->getContent(),
447
                'category' => $item->getCategory() ? $item->getCategory()->getId() : '',
448
            ]
449
        );
450
451
        if ($form->validate()) {
452
            $values = $form->exportValues();
453
            $currentTime = new DateTime(api_get_utc_datetime(), new DateTimeZone('UTC'));
454
455
            $item
456
                ->setTitle($values['title'])
457
                ->setContent($values['content'])
458
                ->setUpdateDate($currentTime)
459
                ->setCategory(
460
                    $this->em->find('ChamiloCoreBundle:PortfolioCategory', $values['category'])
461
                );
462
463
            $values['item_id'] = $item->getId();
464
465
            $extraFieldValue = new ExtraFieldValue('portfolio');
466
            $extraFieldValue->saveFieldValues($values);
467
468
            $this->em->persist($item);
469
            $this->em->flush();
470
471
            $this->processAttachments(
472
                $form,
473
                $item->getUser(),
474
                $item->getId(),
475
                PortfolioAttachment::TYPE_ITEM
476
            );
477
478
            Display::addFlash(
479
                Display::return_message(get_lang('ItemUpdated'), 'success')
480
            );
481
482
            header("Location: $this->baseUrl");
483
            exit;
484
        }
485
486
        $interbreadcrumb[] = [
487
            'name' => get_lang('Portfolio'),
488
            'url' => $this->baseUrl,
489
        ];
490
        $actions = [];
491
        $actions[] = Display::url(
492
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
493
            $this->baseUrl
494
        );
495
        $content = $form->returnForm();
496
497
        $this->renderView(
498
            $content."<script> $(function() { {$extra['jquery_ready_content']} }); </script>",
499
            get_lang('EditPortfolioItem'),
500
            $actions
501
        );
502
    }
503
504
    /**
505
     * @throws \Doctrine\ORM\ORMException
506
     * @throws \Doctrine\ORM\OptimisticLockException
507
     */
508
    public function showHideItem(Portfolio $item)
509
    {
510
        if (!$this->itemBelongToOwner($item)) {
511
            api_not_allowed(true);
512
        }
513
514
        $item->setIsVisible(
515
            !$item->isVisible()
516
        );
517
518
        $this->em->persist($item);
519
        $this->em->flush();
520
521
        Display::addFlash(
522
            Display::return_message(get_lang('VisibilityChanged'), 'success')
523
        );
524
525
        header("Location: $this->baseUrl");
526
        exit;
527
    }
528
529
    /**
530
     * @throws \Doctrine\ORM\ORMException
531
     * @throws \Doctrine\ORM\OptimisticLockException
532
     */
533
    public function deleteItem(Portfolio $item)
534
    {
535
        if (!$this->itemBelongToOwner($item)) {
536
            api_not_allowed(true);
537
        }
538
539
        $this->em->remove($item);
540
        $this->em->flush();
541
542
        Display::addFlash(
543
            Display::return_message(get_lang('ItemDeleted'), 'success')
544
        );
545
546
        header("Location: $this->baseUrl");
547
        exit;
548
    }
549
550
    /**
551
     * @throws \Exception
552
     */
553
    public function index(HttpRequest $httpRequest)
554
    {
555
        $listByUser = false;
556
557
        if ($httpRequest->query->has('user')) {
558
            $this->owner = api_get_user_entity($httpRequest->query->getInt('user'));
559
560
            if (empty($this->owner)) {
561
                api_not_allowed(true);
562
            }
563
564
            $listByUser = true;
565
        }
566
567
        $currentUserId = api_get_user_id();
568
569
        $actions = [];
570
571
        if ($currentUserId == $this->owner->getId()) {
572
            $actions[] = Display::url(
573
                Display::return_icon('add.png', get_lang('Add'), [], ICON_SIZE_MEDIUM),
574
                $this->baseUrl.'action=add_item'
575
            );
576
            $actions[] = Display::url(
577
                Display::return_icon('folder.png', get_lang('AddCategory'), [], ICON_SIZE_MEDIUM),
578
                $this->baseUrl.'action=add_category'
579
            );
580
            $actions[] = Display::url(
581
                Display::return_icon('waiting_list.png', get_lang('PortfolioDetails'), [], ICON_SIZE_MEDIUM),
582
                $this->baseUrl.'action=details'
583
            );
584
        } else {
585
            $actions[] = Display::url(
586
                Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
587
                $this->baseUrl
588
            );
589
        }
590
591
        $frmStudentList = null;
592
        $frmTagList = null;
593
594
        $categories = [];
595
596
        if ($this->course) {
597
            $frmTagList = $this->createFormTagFilter($listByUser);
598
            $frmStudentList = $this->createFormStudentFilter($listByUser);
599
            $frmStudentList->setDefaults(['user' => $this->owner->getId()]);
600
        } else {
601
            $categories = $this->getCategoriesForIndex($currentUserId);
602
        }
603
604
        $items = $this->getItemsForIndex($listByUser, $frmTagList);
605
606
        $template = new Template(null, false, false, false, false, false, false);
607
        $template->assign('user', $this->owner);
608
        $template->assign('course', $this->course);
609
        $template->assign('session', $this->session);
610
        $template->assign('portfolio', $categories);
611
        $template->assign('uncategorized_items', $items);
612
        $template->assign('frm_student_list', $this->course ? $frmStudentList->returnForm() : '');
613
        $template->assign('frm_tag_list', $this->course ? $frmTagList->returnForm() : '');
614
615
        $layout = $template->get_template('portfolio/list.html.twig');
616
        $content = $template->fetch($layout);
617
618
        $this->renderView($content, get_lang('Portfolio'), $actions);
619
    }
620
621
    /**
622
     * @throws \Doctrine\ORM\ORMException
623
     * @throws \Doctrine\ORM\OptimisticLockException
624
     * @throws \Doctrine\ORM\TransactionRequiredException
625
     */
626
    public function view(Portfolio $item)
627
    {
628
        global $interbreadcrumb;
629
630
        $form = $this->createCommentForm($item);
631
632
        $commentsRepo = $this->em->getRepository(PortfolioComment::class);
633
634
        $query = $commentsRepo->createQueryBuilder('comment')
635
            ->where('comment.item = :item')
636
            ->orderBy('comment.root, comment.lft', 'ASC')
637
            ->setParameter('item', $item)
638
            ->getQuery();
639
640
        $clockIcon = Display::returnFontAwesomeIcon('clock-o', '', true);
641
642
        $commentsHtml = $commentsRepo->buildTree(
643
            $query->getArrayResult(),
644
            [
645
                'decorate' => true,
646
                'rootOpen' => '<ul class="media-list">',
647
                'rootClose' => '</ul>',
648
                'childOpen' => function ($node) use ($commentsRepo) {
649
                    /** @var PortfolioComment $comment */
650
                    $comment = $commentsRepo->find($node['id']);
651
                    $author = $comment->getAuthor();
652
653
                    $userPicture = UserManager::getUserPicture(
654
                        $comment->getAuthor()->getId(),
655
                        USER_IMAGE_SIZE_SMALL,
656
                        null,
657
                        [
658
                            'picture_uri' => $author->getPictureUri(),
659
                            'email' => $author->getEmail(),
660
                        ]
661
                    );
662
663
                    return '<li class="media" id="comment-'.$node['id'].'">
664
                        <div class="media-left"><img class="media-object thumbnail" src="'.$userPicture.'" alt="'
665
                        .$author->getCompleteName().'"></div>
666
                        <div class="media-body">';
667
                },
668
                'childClose' => '</div></li>',
669
                'nodeDecorator' => function ($node) use ($commentsRepo, $clockIcon, $item) {
670
                    /** @var PortfolioComment $comment */
671
                    $comment = $commentsRepo->find($node['id']);
672
673
                    $commentActions[] = Display::url(
0 ignored issues
show
Comprehensibility Best Practice introduced by
$commentActions was never initialized. Although not strictly required by PHP, it is generally a good practice to add $commentActions = array(); before regardless.
Loading history...
674
                        Display::return_icon('discuss.png', get_lang('ReplyToThisComment')),
675
                        '#',
676
                        [
677
                            'data-comment' => htmlspecialchars(
678
                                json_encode(['id' => $comment->getId()])
679
                            ),
680
                            'role' => 'button',
681
                            'class' => 'btn-reply-to',
682
                        ]
683
                    );
684
                    $commentActions[] = Display::url(
685
                        Display::return_icon('copy.png', get_lang('CopyToMyPortfolio')),
686
                        $this->baseUrl.http_build_query(
687
                            [
688
                                'action' => 'copy',
689
                                'copy' => 'comment',
690
                                'id' => $comment->getId(),
691
                            ]
692
                        )
693
                    );
694
695
                    $isAllowedToEdit = api_is_allowed_to_edit();
696
697
                    if ($isAllowedToEdit) {
698
                        $commentActions[] = Display::url(
699
                            Display::return_icon('copy.png', get_lang('CopyToStudentPortfolio')),
700
                            $this->baseUrl.http_build_query(
701
                                [
702
                                    'action' => 'teacher_copy',
703
                                    'copy' => 'comment',
704
                                    'id' => $comment->getId(),
705
                                ]
706
                            )
707
                        );
708
709
                        if ($comment->isImportant()) {
710
                            $commentActions[] = Display::url(
711
                                Display::return_icon('drawing-pin.png', get_lang('UnmarkCommentAsImportant')),
712
                                $this->baseUrl.http_build_query(
713
                                    [
714
                                        'action' => 'mark_important',
715
                                        'item' => $item->getId(),
716
                                        'id' => $comment->getId(),
717
                                    ]
718
                                )
719
                            );
720
                        } else {
721
                            $commentActions[] = Display::url(
722
                                Display::return_icon('drawing-pin.png', get_lang('MarkCommentAsImportant')),
723
                                $this->baseUrl.http_build_query(
724
                                    [
725
                                        'action' => 'mark_important',
726
                                        'item' => $item->getId(),
727
                                        'id' => $comment->getId(),
728
                                    ]
729
                                )
730
                            );
731
                        }
732
733
                        if ($this->course && '1' === api_get_course_setting('qualify_portfolio_comment')) {
734
                            $commentActions[] = Display::url(
735
                                Display::return_icon('quiz.png', get_lang('QualifyThisPortfolioComment')),
736
                                $this->baseUrl.http_build_query(
737
                                    [
738
                                        'action' => 'qualify',
739
                                        'comment' => $comment->getId(),
740
                                    ]
741
                                )
742
                            );
743
                        }
744
                    }
745
746
                    $nodeHtml = '<p class="media-heading h4">'.PHP_EOL
747
                        .$comment->getAuthor()->getCompleteName().PHP_EOL.'<small>'.$clockIcon.PHP_EOL
748
                        .Display::dateToStringAgoAndLongDate($comment->getDate()).'</small>'.PHP_EOL;
749
750
                    if ($comment->isImportant()
751
                        && ($this->itemBelongToOwner($comment->getItem()) || $isAllowedToEdit)
752
                    ) {
753
                        $nodeHtml .= '<span class="label label-warning origin-style">'
754
                            .get_lang('CommentMarkedAsImportant')
755
                            .'</span>'.PHP_EOL;
756
                    }
757
758
                    $nodeHtml .= '</p>'.PHP_EOL
759
                        .'<div class="pull-right">'.implode(PHP_EOL, $commentActions).'</div>'
760
                        .Security::remove_XSS($comment->getContent())
761
                        .PHP_EOL;
762
763
                    $nodeHtml .= $this->generateAttachmentList($comment);
764
765
                    return $nodeHtml;
766
                },
767
            ]
768
        );
769
770
        $template = new Template(null, false, false, false, false, false, false);
771
        $template->assign('baseurl', $this->baseUrl);
772
        $template->assign('item', $item);
773
        $template->assign('item_content', $this->generateItemContent($item));
774
        $template->assign('comments', $commentsHtml);
775
        $template->assign('form', $form);
776
        $template->assign('attachment_list', $this->generateAttachmentList($item));
777
778
        $layout = $template->get_template('portfolio/view.html.twig');
779
        $content = $template->fetch($layout);
780
781
        $interbreadcrumb[] = ['name' => get_lang('Portfolio'), 'url' => $this->baseUrl];
782
783
        $actions = [];
784
        $actions[] = Display::url(
785
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
786
            $this->baseUrl
787
        );
788
789
        $this->renderView($content, Security::remove_XSS($item->getTitle()), $actions, false);
790
    }
791
792
    /**
793
     * @throws \Doctrine\ORM\ORMException
794
     * @throws \Doctrine\ORM\OptimisticLockException
795
     */
796
    public function copyItem(Portfolio $originItem)
797
    {
798
        $currentTime = api_get_utc_datetime(null, false, true);
799
800
        $portfolio = new Portfolio();
801
        $portfolio
802
            ->setIsVisible(false)
803
            ->setTitle(
804
                sprintf(get_lang('PortfolioItemFromXUser'), $originItem->getUser()->getCompleteName())
805
            )
806
            ->setContent('')
807
            ->setUser($this->owner)
808
            ->setOrigin($originItem->getId())
809
            ->setOriginType(Portfolio::TYPE_ITEM)
810
            ->setCourse($this->course)
811
            ->setSession($this->session)
812
            ->setCreationDate($currentTime)
813
            ->setUpdateDate($currentTime);
814
815
        $this->em->persist($portfolio);
816
        $this->em->flush();
817
818
        Display::addFlash(
819
            Display::return_message(get_lang('PortfolioItemAdded'), 'success')
820
        );
821
822
        header("Location: $this->baseUrl".http_build_query(['action' => 'edit_item', 'id' => $portfolio->getId()]));
823
        exit;
824
    }
825
826
    /**
827
     * @throws \Doctrine\ORM\ORMException
828
     * @throws \Doctrine\ORM\OptimisticLockException
829
     */
830
    public function copyComment(PortfolioComment $originComment)
831
    {
832
        $currentTime = api_get_utc_datetime(null, false, true);
833
834
        $portfolio = new Portfolio();
835
        $portfolio
836
            ->setIsVisible(false)
837
            ->setTitle(
838
                sprintf(get_lang('PortfolioCommentFromXUser'), $originComment->getAuthor()->getCompleteName())
839
            )
840
            ->setContent('')
841
            ->setUser($this->owner)
842
            ->setOrigin($originComment->getId())
843
            ->setOriginType(Portfolio::TYPE_COMMENT)
844
            ->setCourse($this->course)
845
            ->setSession($this->session)
846
            ->setCreationDate($currentTime)
847
            ->setUpdateDate($currentTime);
848
849
        $this->em->persist($portfolio);
850
        $this->em->flush();
851
852
        Display::addFlash(
853
            Display::return_message(get_lang('PortfolioItemAdded'), 'success')
854
        );
855
856
        header("Location: $this->baseUrl".http_build_query(['action' => 'edit_item', 'id' => $portfolio->getId()]));
857
        exit;
858
    }
859
860
    /**
861
     * @throws \Doctrine\ORM\ORMException
862
     * @throws \Doctrine\ORM\OptimisticLockException
863
     * @throws \Exception
864
     */
865
    public function teacherCopyItem(Portfolio $originItem)
866
    {
867
        $actionParams = http_build_query(['action' => 'teacher_copy', 'copy' => 'item', 'id' => $originItem->getId()]);
868
869
        $form = new FormValidator('teacher_copy_portfolio', 'post', $this->baseUrl.$actionParams);
870
871
        if (api_get_configuration_value('save_titles_as_html')) {
872
            $form->addHtmlEditor('title', get_lang('Title'), true, false, ['ToolbarSet' => 'TitleAsHtml']);
873
        } else {
874
            $form->addText('title', get_lang('Title'));
875
            $form->applyFilter('title', 'trim');
876
        }
877
878
        $form->addLabel(
879
            sprintf(get_lang('PortfolioItemFromXUser'), $originItem->getUser()->getCompleteName()),
880
            Display::panel(
881
                Security::remove_XSS($originItem->getContent())
882
            )
883
        );
884
        $form->addHtmlEditor('content', get_lang('Content'), true, false, ['ToolbarSet' => 'NotebookStudent']);
885
886
        $urlParams = http_build_query(
887
            [
888
                'a' => 'search_user_by_course',
889
                'course_id' => $this->course->getId(),
890
                'session_id' => $this->session ? $this->session->getId() : 0,
891
            ]
892
        );
893
        $form->addSelectAjax(
894
            'students',
895
            get_lang('Students'),
896
            [],
897
            [
898
                'url' => api_get_path(WEB_AJAX_PATH)."course.ajax.php?$urlParams",
899
                'multiple' => true,
900
            ]
901
        );
902
        $form->addRule('students', get_lang('ThisFieldIsRequired'), 'required');
903
        $form->addButtonCreate(get_lang('Save'));
904
905
        $toolName = get_lang('CopyToStudentPortfolio');
906
        $content = $form->returnForm();
907
908
        if ($form->validate()) {
909
            $values = $form->exportValues();
910
911
            $currentTime = api_get_utc_datetime(null, false, true);
912
913
            foreach ($values['students'] as $studentId) {
914
                $owner = api_get_user_entity($studentId);
915
916
                $portfolio = new Portfolio();
917
                $portfolio
918
                    ->setIsVisible(false)
919
                    ->setTitle($values['title'])
920
                    ->setContent($values['content'])
921
                    ->setUser($owner)
922
                    ->setOrigin($originItem->getId())
923
                    ->setOriginType(Portfolio::TYPE_ITEM)
924
                    ->setCourse($this->course)
925
                    ->setSession($this->session)
926
                    ->setCreationDate($currentTime)
927
                    ->setUpdateDate($currentTime);
928
929
                $this->em->persist($portfolio);
930
            }
931
932
            $this->em->flush();
933
934
            Display::addFlash(
935
                Display::return_message(get_lang('PortfolioItemAddedToStudents'), 'success')
936
            );
937
938
            header("Location: $this->baseUrl");
939
            exit;
940
        }
941
942
        $this->renderView($content, $toolName);
943
    }
944
945
    /**
946
     * @throws \Doctrine\ORM\ORMException
947
     * @throws \Doctrine\ORM\OptimisticLockException
948
     * @throws \Exception
949
     */
950
    public function teacherCopyComment(PortfolioComment $originComment)
951
    {
952
        $actionParams = http_build_query(
953
            [
954
                'action' => 'teacher_copy',
955
                'copy' => 'comment',
956
                'id' => $originComment->getId(),
957
            ]
958
        );
959
960
        $form = new FormValidator('teacher_copy_portfolio', 'post', $this->baseUrl.$actionParams);
961
962
        if (api_get_configuration_value('save_titles_as_html')) {
963
            $form->addHtmlEditor('title', get_lang('Title'), true, false, ['ToolbarSet' => 'TitleAsHtml']);
964
        } else {
965
            $form->addText('title', get_lang('Title'));
966
            $form->applyFilter('title', 'trim');
967
        }
968
969
        $form->addLabel(
970
            sprintf(get_lang('PortfolioCommentFromXUser'), $originComment->getAuthor()->getCompleteName()),
971
            Display::panel(
972
                Security::remove_XSS($originComment->getContent())
973
            )
974
        );
975
        $form->addHtmlEditor('content', get_lang('Content'), true, false, ['ToolbarSet' => 'NotebookStudent']);
976
977
        $urlParams = http_build_query(
978
            [
979
                'a' => 'search_user_by_course',
980
                'course_id' => $this->course->getId(),
981
                'session_id' => $this->session ? $this->session->getId() : 0,
982
            ]
983
        );
984
        $form->addSelectAjax(
985
            'students',
986
            get_lang('Students'),
987
            [],
988
            [
989
                'url' => api_get_path(WEB_AJAX_PATH)."course.ajax.php?$urlParams",
990
                'multiple' => true,
991
            ]
992
        );
993
        $form->addRule('students', get_lang('ThisFieldIsRequired'), 'required');
994
        $form->addButtonCreate(get_lang('Save'));
995
996
        $toolName = get_lang('CopyToStudentPortfolio');
997
        $content = $form->returnForm();
998
999
        if ($form->validate()) {
1000
            $values = $form->exportValues();
1001
1002
            $currentTime = api_get_utc_datetime(null, false, true);
1003
1004
            foreach ($values['students'] as $studentId) {
1005
                $owner = api_get_user_entity($studentId);
1006
1007
                $portfolio = new Portfolio();
1008
                $portfolio
1009
                    ->setIsVisible(false)
1010
                    ->setTitle($values['title'])
1011
                    ->setContent($values['content'])
1012
                    ->setUser($owner)
1013
                    ->setOrigin($originComment->getId())
1014
                    ->setOriginType(Portfolio::TYPE_COMMENT)
1015
                    ->setCourse($this->course)
1016
                    ->setSession($this->session)
1017
                    ->setCreationDate($currentTime)
1018
                    ->setUpdateDate($currentTime);
1019
1020
                $this->em->persist($portfolio);
1021
            }
1022
1023
            $this->em->flush();
1024
1025
            Display::addFlash(
1026
                Display::return_message(get_lang('PortfolioItemAddedToStudents'), 'success')
1027
            );
1028
1029
            header("Location: $this->baseUrl");
1030
            exit;
1031
        }
1032
1033
        $this->renderView($content, $toolName);
1034
    }
1035
1036
    /**
1037
     * @throws \Doctrine\ORM\ORMException
1038
     * @throws \Doctrine\ORM\OptimisticLockException
1039
     */
1040
    public function markImportantCommentInItem(Portfolio $item, PortfolioComment $comment)
1041
    {
1042
        if ($comment->getItem()->getId() !== $item->getId()) {
1043
            api_not_allowed(true);
1044
        }
1045
1046
        $comment->setIsImportant(
1047
            !$comment->isImportant()
1048
        );
1049
1050
        $this->em->persist($comment);
1051
        $this->em->flush();
1052
1053
        Display::addFlash(
1054
            Display::return_message(get_lang('CommentMarkedAsImportant'), 'success')
1055
        );
1056
1057
        header("Location: $this->baseUrl".http_build_query(['action' => 'view', 'id' => $item->getId()]));
1058
        exit;
1059
    }
1060
1061
    /**
1062
     * @throws \Exception
1063
     */
1064
    public function details(HttpRequest $httpRequest)
1065
    {
1066
        $isAllowedToFilterStudent = $this->course && api_is_allowed_to_edit();
1067
1068
        $actions = [];
1069
        $actions[] = Display::url(
1070
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
1071
            $this->baseUrl
1072
        );
1073
        $actions[] = Display::url(
1074
            Display::return_icon('pdf.png', get_lang('ExportMyPortfolioDataPdf'), [], ICON_SIZE_MEDIUM),
1075
            $this->baseUrl.http_build_query(['action' => 'export_pdf'])
1076
        );
1077
        $actions[] = Display::url(
1078
            Display::return_icon('save_pack.png', get_lang('ExportMyPortfolioDataZip'), [], ICON_SIZE_MEDIUM),
1079
            $this->baseUrl.http_build_query(['action' => 'export_zip'])
1080
        );
1081
1082
        $frmStudent = null;
1083
1084
        if ($isAllowedToFilterStudent) {
1085
            if ($httpRequest->query->has('user')) {
1086
                $this->owner = api_get_user_entity($httpRequest->query->getInt('user'));
1087
1088
                if (empty($this->owner)) {
1089
                    api_not_allowed(true);
1090
                }
1091
1092
                $actions[1] = Display::url(
1093
                    Display::return_icon('pdf.png', get_lang('ExportMyPortfolioDataPdf'), [], ICON_SIZE_MEDIUM),
1094
                    $this->baseUrl.http_build_query(['action' => 'export_pdf', 'user' => $this->owner->getId()])
1095
                );
1096
                $actions[2] = Display::url(
1097
                    Display::return_icon('save_pack.png', get_lang('ExportMyPortfolioDataZip'), [], ICON_SIZE_MEDIUM),
1098
                    $this->baseUrl.http_build_query(['action' => 'export_zip', 'user' => $this->owner->getId()])
1099
                );
1100
            }
1101
1102
            $frmStudent = new FormValidator('frm_student_list', 'get');
1103
            $slctStudentOptions = [];
1104
            $slctStudentOptions[$this->owner->getId()] = $this->owner->getCompleteName();
1105
1106
            $urlParams = http_build_query(
1107
                [
1108
                    'a' => 'search_user_by_course',
1109
                    'course_id' => $this->course->getId(),
1110
                    'session_id' => $this->session ? $this->session->getId() : 0,
1111
                ]
1112
            );
1113
1114
            $frmStudent->addSelectAjax(
1115
                'user',
1116
                get_lang('SelectLearnerPortfolio'),
1117
                $slctStudentOptions,
1118
                [
1119
                    'url' => api_get_path(WEB_AJAX_PATH)."course.ajax.php?$urlParams",
1120
                    'placeholder' => get_lang('SearchStudent'),
1121
                ]
1122
            );
1123
            $frmStudent->setDefaults(['user' => $this->owner->getId()]);
1124
            $frmStudent->addHidden('action', 'details');
1125
            $frmStudent->addHidden('cidReq', $this->course->getCode());
1126
            $frmStudent->addHidden('id_session', $this->session ? $this->session->getId() : 0);
1127
            $frmStudent->addButtonFilter(get_lang('Filter'));
1128
        }
1129
1130
        $itemsRepo = $this->em->getRepository(Portfolio::class);
1131
        $commentsRepo = $this->em->getRepository(PortfolioComment::class);
1132
1133
        $getItemsTotalNumber = function () use ($itemsRepo) {
1134
            $qb = $itemsRepo->createQueryBuilder('i');
1135
            $qb
1136
                ->select('COUNT(i)')
1137
                ->where('i.user = :user')
1138
                ->setParameter('user', $this->owner);
1139
1140
            if ($this->course) {
1141
                $qb
1142
                    ->andWhere('i.course = :course')
1143
                    ->setParameter('course', $this->course);
1144
1145
                if ($this->session) {
1146
                    $qb
1147
                        ->andWhere('i.session = :session')
1148
                        ->setParameter('session', $this->session);
1149
                } else {
1150
                    $qb->andWhere('i.session IS NULL');
1151
                }
1152
            }
1153
1154
            return $qb->getQuery()->getSingleScalarResult();
1155
        };
1156
        $getItemsData = function ($from, $limit, $columnNo, $orderDirection) use ($itemsRepo) {
1157
            $qb = $itemsRepo->createQueryBuilder('item')
1158
                ->where('item.user = :user')
1159
                ->leftJoin('item.category', 'category')
1160
                ->leftJoin('item.course', 'course')
1161
                ->leftJoin('item.session', 'session')
1162
                ->setParameter('user', $this->owner);
1163
1164
            if ($this->course) {
1165
                $qb
1166
                    ->andWhere('item.course = :course_id')
1167
                    ->setParameter('course_id', $this->course);
1168
1169
                if ($this->session) {
1170
                    $qb
1171
                        ->andWhere('item.session = :session')
1172
                        ->setParameter('session', $this->session);
1173
                } else {
1174
                    $qb->andWhere('item.session IS NULL');
1175
                }
1176
            }
1177
1178
            if (0 == $columnNo) {
1179
                $qb->orderBy('item.title', $orderDirection);
1180
            } elseif (1 == $columnNo) {
1181
                $qb->orderBy('item.creationDate', $orderDirection);
1182
            } elseif (2 == $columnNo) {
1183
                $qb->orderBy('item.updateDate', $orderDirection);
1184
            } elseif (3 == $columnNo) {
1185
                $qb->orderBy('category.title', $orderDirection);
1186
            } elseif (5 == $columnNo) {
1187
                $qb->orderBy('item.score', $orderDirection);
1188
            } elseif (6 == $columnNo) {
1189
                $qb->orderBy('course.title', $orderDirection);
1190
            } elseif (7 == $columnNo) {
1191
                $qb->orderBy('session.name', $orderDirection);
1192
            }
1193
1194
            $qb->setFirstResult($from)->setMaxResults($limit);
1195
1196
            return array_map(
1197
                function (Portfolio $item) {
1198
                    $category = $item->getCategory();
1199
1200
                    $row = [];
1201
                    $row[] = $item;
1202
                    $row[] = $item->getCreationDate();
1203
                    $row[] = $item->getUpdateDate();
1204
                    $row[] = $category ? $item->getCategory()->getTitle() : null;
0 ignored issues
show
$category is of type Chamilo\CoreBundle\Entity\PortfolioCategory, thus it always evaluated to true.
Loading history...
1205
                    $row[] = $item->getComments()->count();
1206
                    $row[] = $item->getScore();
1207
1208
                    if (!$this->course) {
1209
                        $row[] = $item->getCourse();
1210
                        $row[] = $item->getSession();
1211
                    }
1212
1213
                    return $row;
1214
                },
1215
                $qb->getQuery()->getResult()
1216
            );
1217
        };
1218
1219
        $portfolioItemColumnFilter = function (Portfolio $item) {
1220
            return Display::url(
1221
                Security::remove_XSS($item->getTitle()),
1222
                $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()])
1223
            );
1224
        };
1225
        $convertFormatDateColumnFilter = function (DateTime $date) {
1226
            return api_convert_and_format_date($date);
1227
        };
1228
1229
        $tblItems = new SortableTable('tbl_items', $getItemsTotalNumber, $getItemsData, 1, 20, 'DESC');
1230
        $tblItems->set_additional_parameters(['action' => 'details', 'user' => $this->owner->getId()]);
1231
        $tblItems->set_header(0, get_lang('Title'));
1232
        $tblItems->set_column_filter(0, $portfolioItemColumnFilter);
1233
        $tblItems->set_header(1, get_lang('CreationDate'), true, [], ['class' => 'text-center']);
1234
        $tblItems->set_column_filter(1, $convertFormatDateColumnFilter);
1235
        $tblItems->set_header(2, get_lang('LastUpdate'), true, [], ['class' => 'text-center']);
1236
        $tblItems->set_column_filter(2, $convertFormatDateColumnFilter);
1237
        $tblItems->set_header(3, get_lang('Category'));
1238
        $tblItems->set_header(4, get_lang('Comments'), false, [], ['class' => 'text-right']);
1239
        $tblItems->set_header(5, get_lang('Score'), true, [], ['class' => 'text-right']);
1240
1241
        if (!$this->course) {
1242
            $tblItems->set_header(6, get_lang('Course'));
1243
            $tblItems->set_header(7, get_lang('Session'));
1244
        }
1245
1246
        $getCommentsTotalNumber = function () use ($commentsRepo) {
1247
            $qb = $commentsRepo->createQueryBuilder('c');
1248
            $qb
1249
                ->select('COUNT(c)')
1250
                ->where('c.author = :author')
1251
                ->setParameter('author', $this->owner);
1252
1253
            if ($this->course) {
1254
                $qb
1255
                    ->innerJoin('c.item', 'i')
1256
                    ->andWhere('i.course = :course')
1257
                    ->setParameter('course', $this->course);
1258
1259
                if ($this->session) {
1260
                    $qb
1261
                        ->andWhere('i.session = :session')
1262
                        ->setParameter('session', $this->session);
1263
                } else {
1264
                    $qb->andWhere('i.session IS NULL');
1265
                }
1266
            }
1267
1268
            return $qb->getQuery()->getSingleScalarResult();
1269
        };
1270
        $getCommentsData = function ($from, $limit, $columnNo, $orderDirection) use ($commentsRepo) {
1271
            $qb = $commentsRepo->createQueryBuilder('comment');
1272
            $qb
1273
                ->where('comment.author = :user')
1274
                ->innerJoin('comment.item', 'item')
1275
                ->setParameter('user', $this->owner);
1276
1277
            if ($this->course) {
1278
                $qb
1279
                    ->innerJoin('comment.item', 'i')
1280
                    ->andWhere('item.course = :course')
1281
                    ->setParameter('course', $this->course);
1282
1283
                if ($this->session) {
1284
                    $qb
1285
                        ->andWhere('item.session = :session')
1286
                        ->setParameter('session', $this->session);
1287
                } else {
1288
                    $qb->andWhere('item.session IS NULL');
1289
                }
1290
            }
1291
1292
            if (0 == $columnNo) {
1293
                $qb->orderBy('comment.content', $orderDirection);
1294
            } elseif (1 == $columnNo) {
1295
                $qb->orderBy('comment.date', $orderDirection);
1296
            } elseif (2 == $columnNo) {
1297
                $qb->orderBy('item.title', $orderDirection);
1298
            } elseif (3 == $columnNo) {
1299
                $qb->orderBy('comment.score', $orderDirection);
1300
            }
1301
1302
            $qb->setFirstResult($from)->setMaxResults($limit);
1303
1304
            return array_map(
1305
                function (PortfolioComment $comment) {
1306
                    return [
1307
                        $comment,
1308
                        $comment->getDate(),
1309
                        $comment->getItem(),
1310
                        $comment->getScore(),
1311
                    ];
1312
                },
1313
                $qb->getQuery()->getResult()
1314
            );
1315
        };
1316
1317
        $tblComments = new SortableTable('tbl_comments', $getCommentsTotalNumber, $getCommentsData, 1, 20, 'DESC');
1318
        $tblComments->set_additional_parameters(['action' => 'details', 'user' => $this->owner->getId()]);
1319
        $tblComments->set_header(0, get_lang('Resume'));
1320
        $tblComments->set_column_filter(
1321
            0,
1322
            function (PortfolioComment $comment) {
1323
                return Display::url(
1324
                    $comment->getExcerpt(),
1325
                    $this->baseUrl.http_build_query(['action' => 'view', 'id' => $comment->getItem()->getId()])
1326
                    .'#comment-'.$comment->getId()
1327
                );
1328
            }
1329
        );
1330
        $tblComments->set_header(1, get_lang('Date'), true, [], ['class' => 'text-center']);
1331
        $tblComments->set_column_filter(1, $convertFormatDateColumnFilter);
1332
        $tblComments->set_header(2, get_lang('PortfolioItemTitle'));
1333
        $tblComments->set_column_filter(2, $portfolioItemColumnFilter);
1334
        $tblComments->set_header(3, get_lang('Score'), true, [], ['class' => 'text-right']);
1335
1336
        $content = '';
1337
1338
        if ($frmStudent) {
1339
            $content .= $frmStudent->returnForm();
1340
        }
1341
1342
        $content .= Display::page_subheader2(get_lang('PortfolioItems')).PHP_EOL;
1343
1344
        if ($tblItems->get_total_number_of_items() > 0) {
1345
            $content .= $tblItems->return_table().PHP_EOL;
1346
        } else {
1347
            $content .= Display::return_message(get_lang('NoItemsInYourPortfolio'), 'warning');
1348
        }
1349
1350
        $content .= Display::page_subheader2(get_lang('PortfolioCommentsMade')).PHP_EOL;
1351
1352
        if ($tblComments->get_total_number_of_items() > 0) {
1353
            $content .= $tblComments->return_table().PHP_EOL;
1354
        } else {
1355
            $content .= Display::return_message(get_lang('YouHaveNotCommented'), 'warning');
1356
        }
1357
1358
        $this->renderView($content, get_lang('PortfolioDetails'), $actions);
1359
    }
1360
1361
    /**
1362
     * @throws \MpdfException
1363
     */
1364
    public function exportPdf(HttpRequest $httpRequest)
1365
    {
1366
        $isAllowedToFilterStudent = $this->course && api_is_allowed_to_edit();
1367
1368
        if ($isAllowedToFilterStudent) {
1369
            if ($httpRequest->query->has('user')) {
1370
                $this->owner = api_get_user_entity($httpRequest->query->getInt('user'));
1371
1372
                if (empty($this->owner)) {
1373
                    api_not_allowed(true);
1374
                }
1375
            }
1376
        }
1377
1378
        $pdfContent = Display::page_header($this->owner->getCompleteName());
1379
1380
        if ($this->course) {
1381
            $pdfContent .= '<p>'.get_lang('Course').': ';
1382
1383
            if ($this->session) {
1384
                $pdfContent .= $this->session->getName().' ('.$this->course->getTitle().')';
1385
            } else {
1386
                $pdfContent .= $this->course->getTitle();
1387
            }
1388
1389
            $pdfContent .= '</p>';
1390
        }
1391
1392
        $items = $this->em
1393
            ->getRepository(Portfolio::class)
1394
            ->findItemsByUser($this->owner, $this->course, $this->session);
1395
        $comments = $this->em
1396
            ->getRepository(PortfolioComment::class)
1397
            ->findCommentsByUser($this->owner, $this->course, $this->session);
1398
1399
        $itemsHtml = $this->getItemsInHtmlFormatted($items);
1400
        $commentsHtml = $this->getCommentsInHtmlFormatted($comments);
1401
1402
        $pdfContent .= Display::page_subheader2(get_lang('PortfolioItems'));
1403
1404
        if (count($itemsHtml) > 0) {
1405
            $pdfContent .= implode(PHP_EOL, $itemsHtml);
1406
        } else {
1407
            $pdfContent .= Display::return_message(get_lang('NoItemsInYourPortfolio'), 'warning');
1408
        }
1409
1410
        $pdfContent .= Display::page_subheader2(get_lang('PortfolioCommentsMade'));
1411
1412
        if (count($commentsHtml) > 0) {
1413
            $pdfContent .= implode(PHP_EOL, $commentsHtml);
1414
        } else {
1415
            $pdfContent .= Display::return_message(get_lang('YouHaveNotCommented'), 'warning');
1416
        }
1417
1418
        $pdfName = $this->owner->getCompleteName()
1419
            .($this->course ? '_'.$this->course->getCode() : '')
1420
            .'_'.get_lang('Portfolio');
1421
1422
        $pdf = new PDF();
1423
        $pdf->content_to_pdf(
1424
            $pdfContent,
1425
            null,
1426
            $pdfName,
1427
            $this->course ? $this->course->getCode() : null,
1428
            'D',
1429
            false,
1430
            null,
1431
            false,
1432
            true
1433
        );
1434
    }
1435
1436
    public function exportZip(HttpRequest $httpRequest)
1437
    {
1438
        $isAllowedToFilterStudent = $this->course && api_is_allowed_to_edit();
1439
1440
        if ($isAllowedToFilterStudent) {
1441
            if ($httpRequest->query->has('user')) {
1442
                $this->owner = api_get_user_entity($httpRequest->query->getInt('user'));
1443
1444
                if (empty($this->owner)) {
1445
                    api_not_allowed(true);
1446
                }
1447
            }
1448
        }
1449
1450
        $itemsRepo = $this->em->getRepository(Portfolio::class);
1451
        $commentsRepo = $this->em->getRepository(PortfolioComment::class);
1452
        $attachmentsRepo = $this->em->getRepository(PortfolioAttachment::class);
1453
1454
        $items = $itemsRepo->findItemsByUser($this->owner, $this->course, $this->session);
1455
        $comments = $commentsRepo->findCommentsByUser($this->owner, $this->course, $this->session);
1456
1457
        $itemsHtml = $this->getItemsInHtmlFormatted($items);
1458
        $commentsHtml = $this->getCommentsInHtmlFormatted($comments);
1459
1460
        $sysArchivePath = api_get_path(SYS_ARCHIVE_PATH);
1461
        $tempPortfolioDirectory = $sysArchivePath."portfolio/{$this->owner->getId()}";
1462
1463
        $userDirectory = UserManager::getUserPathById($this->owner->getId(), 'system');
1464
        $attachmentsDirectory = $userDirectory.'portfolio_attachments/';
1465
1466
        $tblItemsHeaders = [];
1467
        $tblItemsHeaders[] = get_lang('Title');
1468
        $tblItemsHeaders[] = get_lang('CreationDate');
1469
        $tblItemsHeaders[] = get_lang('LastUpdate');
1470
        $tblItemsHeaders[] = get_lang('Category');
1471
        $tblItemsHeaders[] = get_lang('Category');
1472
        $tblItemsHeaders[] = get_lang('Score');
1473
        $tblItemsHeaders[] = get_lang('Course');
1474
        $tblItemsHeaders[] = get_lang('Session');
1475
        $tblItemsData = [];
1476
1477
        $tblCommentsHeaders = [];
1478
        $tblCommentsHeaders[] = get_lang('Resume');
1479
        $tblCommentsHeaders[] = get_lang('Date');
1480
        $tblCommentsHeaders[] = get_lang('PortfolioItemTitle');
1481
        $tblCommentsHeaders[] = get_lang('Score');
1482
        $tblCommentsData = [];
1483
1484
        $filenames = [];
1485
1486
        $fs = new Filesystem();
1487
1488
        /**
1489
         * @var int       $i
1490
         * @var Portfolio $item
1491
         */
1492
        foreach ($items as $i => $item) {
1493
            $itemCategory = $item->getCategory();
1494
            $itemCourse = $item->getCourse();
1495
            $itemSession = $item->getSession();
1496
1497
            $itemDirectory = $item->getCreationDate()->format('Y-m-d-H-i-s');
1498
1499
            $itemFilename = sprintf('%s/items/%s/item.html', $tempPortfolioDirectory, $itemDirectory);
1500
            $itemFileContent = $this->fixImagesSourcesToHtml($itemsHtml[$i]);
1501
1502
            $fs->dumpFile($itemFilename, $itemFileContent);
1503
1504
            $filenames[] = $itemFilename;
1505
1506
            $attachments = $attachmentsRepo->findFromItem($item);
1507
1508
            /** @var PortfolioAttachment $attachment */
1509
            foreach ($attachments as $attachment) {
1510
                $attachmentFilename = sprintf(
1511
                    '%s/items/%s/attachments/%s',
1512
                    $tempPortfolioDirectory,
1513
                    $itemDirectory,
1514
                    $attachment->getFilename()
1515
                );
1516
1517
                $fs->copy(
1518
                    $attachmentsDirectory.$attachment->getPath(),
1519
                    $attachmentFilename
1520
                );
1521
1522
                $filenames[] = $attachmentFilename;
1523
            }
1524
1525
            $tblItemsData[] = [
1526
                Display::url(
1527
                    Security::remove_XSS($item->getTitle()),
1528
                    sprintf('items/%s/item.html', $itemDirectory)
1529
                ),
1530
                api_convert_and_format_date($item->getCreationDate()),
1531
                api_convert_and_format_date($item->getUpdateDate()),
1532
                $itemCategory ? $itemCategory->getTitle() : null,
1533
                $item->getComments()->count(),
1534
                $item->getScore(),
1535
                $itemCourse->getTitle(),
1536
                $itemSession ? $itemSession->getName() : null,
1537
            ];
1538
        }
1539
1540
        /**
1541
         * @var int              $i
1542
         * @var PortfolioComment $comment
1543
         */
1544
        foreach ($comments as $i => $comment) {
1545
            $commentDirectory = $comment->getDate()->format('Y-m-d-H-i-s');
1546
1547
            $commentFileContent = $this->fixImagesSourcesToHtml($commentsHtml[$i]);
1548
            $commentFilename = sprintf('%s/comments/%s/comment.html', $tempPortfolioDirectory, $commentDirectory);
1549
1550
            $fs->dumpFile($commentFilename, $commentFileContent);
1551
1552
            $filenames[] = $commentFilename;
1553
1554
            $attachments = $attachmentsRepo->findFromComment($comment);
1555
1556
            /** @var PortfolioAttachment $attachment */
1557
            foreach ($attachments as $attachment) {
1558
                $attachmentFilename = sprintf(
1559
                    '%s/comments/%s/attachments/%s',
1560
                    $tempPortfolioDirectory,
1561
                    $commentDirectory,
1562
                    $attachment->getFilename()
1563
                );
1564
1565
                $fs->copy(
1566
                    $attachmentsDirectory.$attachment->getPath(),
1567
                    $attachmentFilename
1568
                );
1569
1570
                $filenames[] = $attachmentFilename;
1571
            }
1572
1573
            $tblCommentsData[] = [
1574
                Display::url(
1575
                    $comment->getExcerpt(),
1576
                    sprintf('comments/%s/comment.html', $commentDirectory)
1577
                ),
1578
                api_convert_and_format_date($comment->getDate()),
1579
                Security::remove_XSS($comment->getItem()->getTitle()),
1580
                $comment->getScore(),
1581
            ];
1582
        }
1583
1584
        $tblItems = new HTML_Table(['class' => 'table table-hover table-striped table-bordered data_table']);
1585
        $tblItems->setHeaders($tblItemsHeaders);
1586
        $tblItems->setData($tblItemsData);
1587
1588
        $tblComments = new HTML_Table(['class' => 'table table-hover table-striped table-bordered data_table']);
1589
        $tblComments->setHeaders($tblCommentsHeaders);
1590
        $tblComments->setData($tblCommentsData);
1591
1592
        $itemFilename = sprintf('%s/index.html', $tempPortfolioDirectory);
1593
1594
        $filenames[] = $itemFilename;
1595
1596
        $fs->dumpFile(
1597
            $itemFilename,
1598
            $this->formatZipIndexFile($tblItems, $tblComments)
1599
        );
1600
1601
        $zipName = $this->owner->getCompleteName()
1602
            .($this->course ? '_'.$this->course->getCode() : '')
1603
            .'_'.get_lang('Portfolio');
1604
        $tempZipFile = $sysArchivePath."portfolio/$zipName.zip";
1605
        $zip = new PclZip($tempZipFile);
1606
1607
        foreach ($filenames as $filename) {
1608
            $zip->add($filename, PCLZIP_OPT_REMOVE_PATH, $tempPortfolioDirectory);
0 ignored issues
show
The constant PCLZIP_OPT_REMOVE_PATH was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1609
        }
1610
1611
        DocumentManager::file_send_for_download($tempZipFile, true, "$zipName.zip");
1612
1613
        $fs->remove($tempPortfolioDirectory);
1614
        $fs->remove($tempZipFile);
1615
    }
1616
1617
    public function qualifyItem(Portfolio $item)
1618
    {
1619
        global $interbreadcrumb;
1620
1621
        $em = Database::getManager();
1622
1623
        $formAction = $this->baseUrl.http_build_query(['action' => 'qualify', 'item' => $item->getId()]);
1624
1625
        $form = new FormValidator('frm_qualify', 'post', $formAction);
1626
        $form->addUserAvatar('user', get_lang('Author'));
1627
        $form->addLabel(get_lang('Title'), $item->getTitle());
1628
1629
        $itemContent = Security::remove_XSS(
1630
            $this->generateItemContent($item)
1631
        );
1632
1633
        $form->addLabel(get_lang('Content'), $itemContent);
1634
        $form->addNumeric(
1635
            'score',
1636
            [get_lang('QualifyNumeric'), null, ' / '.api_get_course_setting('portfolio_max_score')]
1637
        );
1638
        $form->addButtonSave(get_lang('QualifyThisPortfolioItem'));
1639
1640
        if ($form->validate()) {
1641
            $values = $form->exportValues();
1642
1643
            $item->setScore($values['score']);
1644
1645
            $em->persist($item);
1646
            $em->flush();
1647
1648
            Display::addFlash(
1649
                Display::return_message(get_lang('PortfolioItemGraded'), 'success')
1650
            );
1651
1652
            header("Location: $formAction");
1653
            exit();
1654
        }
1655
1656
        $form->setDefaults(
1657
            [
1658
                'user' => $item->getUser(),
1659
                'score' => (float) $item->getScore(),
1660
            ]
1661
        );
1662
1663
        $interbreadcrumb[] = [
1664
            'name' => get_lang('Portfolio'),
1665
            'url' => $this->baseUrl,
1666
        ];
1667
        $interbreadcrumb[] = [
1668
            'name' => Security::remove_XSS($item->getTitle()),
1669
            'url' => $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()]),
1670
        ];
1671
1672
        $actions = [];
1673
        $actions[] = Display::url(
1674
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
1675
            $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()])
1676
        );
1677
1678
        $this->renderView($form->returnForm(), get_lang('Qualify'), $actions);
1679
    }
1680
1681
    public function qualifyComment(PortfolioComment $comment)
1682
    {
1683
        global $interbreadcrumb;
1684
1685
        $em = Database::getManager();
1686
1687
        $item = $comment->getItem();
1688
        $commentPath = $em->getRepository(PortfolioComment::class)->getPath($comment);
1689
1690
        $template = new Template('', false, false, false, true, false, false);
1691
        $template->assign('item', $item);
1692
        $template->assign('comments_path', $commentPath);
1693
        $commentContext = $template->fetch(
1694
            $template->get_template('portfolio/comment_context.html.twig')
1695
        );
1696
1697
        $formAction = $this->baseUrl.http_build_query(['action' => 'qualify', 'comment' => $comment->getId()]);
1698
1699
        $form = new FormValidator('frm_qualify', 'post', $formAction);
1700
        $form->addHtml($commentContext);
1701
        $form->addUserAvatar('user', get_lang('Author'));
1702
        $form->addLabel(get_lang('Comment'), $comment->getContent());
1703
        $form->addNumeric(
1704
            'score',
1705
            [get_lang('QualifyNumeric'), null, '/ '.api_get_course_setting('portfolio_max_score')]
1706
        );
1707
        $form->addButtonSave(get_lang('QualifyThisPortfolioComment'));
1708
1709
        if ($form->validate()) {
1710
            $values = $form->exportValues();
1711
1712
            $comment->setScore($values['score']);
1713
1714
            $em->persist($comment);
1715
            $em->flush();
1716
1717
            Display::addFlash(
1718
                Display::return_message(get_lang('PortfolioCommentGraded'), 'success')
1719
            );
1720
1721
            header("Location: $formAction");
1722
            exit();
1723
        }
1724
1725
        $form->setDefaults(
1726
            [
1727
                'user' => $comment->getAuthor(),
1728
                'score' => (float) $comment->getScore(),
1729
            ]
1730
        );
1731
1732
        $interbreadcrumb[] = [
1733
            'name' => get_lang('Portfolio'),
1734
            'url' => $this->baseUrl,
1735
        ];
1736
        $interbreadcrumb[] = [
1737
            'name' => Security::remove_XSS($item->getTitle()),
1738
            'url' => $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()]),
1739
        ];
1740
1741
        $actions = [];
1742
        $actions[] = Display::url(
1743
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
1744
            $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()])
1745
        );
1746
1747
        $this->renderView($form->returnForm(), get_lang('Qualify'), $actions);
1748
    }
1749
1750
    public function downloadAttachment(HttpRequest $httpRequest)
1751
    {
1752
        $path = $httpRequest->query->get('file');
1753
1754
        if (empty($path)) {
1755
            api_not_allowed(true);
1756
        }
1757
1758
        $em = Database::getManager();
1759
        $attachmentRepo = $em->getRepository(PortfolioAttachment::class);
1760
1761
        $attachment = $attachmentRepo->findOneByPath($path);
1762
1763
        if (empty($attachment)) {
1764
            api_not_allowed(true);
1765
        }
1766
1767
        $originOwnerId = 0;
1768
1769
        if (PortfolioAttachment::TYPE_ITEM === $attachment->getOriginType()) {
1770
            $item = $em->find(Portfolio::class, $attachment->getOrigin());
1771
1772
            $originOwnerId = $item->getUser()->getId();
1773
        } elseif (PortfolioAttachment::TYPE_COMMENT === $attachment->getOriginType()) {
1774
            $comment = $em->find(PortfolioComment::class, $attachment->getOrigin());
1775
1776
            $originOwnerId = $comment->getAuthor()->getId();
1777
        } else {
1778
            api_not_allowed(true);
1779
        }
1780
1781
        $userDirectory = UserManager::getUserPathById($originOwnerId, 'system');
1782
        $attachmentsDirectory = $userDirectory.'portfolio_attachments/';
1783
        $attachmentFilename = $attachmentsDirectory.$attachment->getPath();
1784
1785
        if (!Security::check_abs_path($attachmentFilename, $attachmentsDirectory)) {
1786
            api_not_allowed(true);
1787
        }
1788
1789
        $downloaded = DocumentManager::file_send_for_download(
1790
            $attachmentFilename,
1791
            true,
1792
            $attachment->getFilename()
1793
        );
1794
1795
        if (!$downloaded) {
1796
            api_not_allowed(true);
1797
        }
1798
    }
1799
1800
    public function deleteAttachment(HttpRequest $httpRequest)
1801
    {
1802
        $currentUserId = api_get_user_id();
1803
1804
        $path = $httpRequest->query->get('file');
1805
1806
        if (empty($path)) {
1807
            api_not_allowed(true);
1808
        }
1809
1810
        $em = Database::getManager();
1811
        $fs = new Filesystem();
1812
1813
        $attachmentRepo = $em->getRepository(PortfolioAttachment::class);
1814
        $attachment = $attachmentRepo->findOneByPath($path);
1815
1816
        if (empty($attachment)) {
1817
            api_not_allowed(true);
1818
        }
1819
1820
        $originOwnerId = 0;
1821
        $itemId = 0;
1822
1823
        if (PortfolioAttachment::TYPE_ITEM === $attachment->getOriginType()) {
1824
            $item = $em->find(Portfolio::class, $attachment->getOrigin());
1825
            $originOwnerId = $item->getUser()->getId();
1826
            $itemId = $item->getId();
1827
        } elseif (PortfolioAttachment::TYPE_COMMENT === $attachment->getOriginType()) {
1828
            $comment = $em->find(PortfolioComment::class, $attachment->getOrigin());
1829
            $originOwnerId = $comment->getAuthor()->getId();
1830
            $itemId = $comment->getItem()->getId();
1831
        }
1832
1833
        if ($currentUserId !== $originOwnerId) {
1834
            api_not_allowed(true);
1835
        }
1836
1837
        $em->remove($attachment);
1838
        $em->flush();
1839
1840
        $userDirectory = UserManager::getUserPathById($originOwnerId, 'system');
1841
        $attachmentsDirectory = $userDirectory.'portfolio_attachments/';
1842
        $attachmentFilename = $attachmentsDirectory.$attachment->getPath();
1843
1844
        $fs->remove($attachmentFilename);
1845
1846
        if ($httpRequest->isXmlHttpRequest()) {
1847
            echo Display::return_message(get_lang('AttachmentFileDeleteSuccess'), 'success');
1848
        } else {
1849
            Display::addFlash(
1850
                Display::return_message(get_lang('AttachmentFileDeleteSuccess'), 'success')
1851
            );
1852
1853
            header('Location: '.$this->baseUrl.http_build_query(['action' => 'view', 'id' => $itemId]));
1854
        }
1855
1856
        exit;
1857
    }
1858
1859
    /**
1860
     * @param bool $showHeader
1861
     */
1862
    private function renderView(string $content, string $toolName, array $actions = [], $showHeader = true)
1863
    {
1864
        global $this_section;
1865
1866
        $this_section = $this->course ? SECTION_COURSES : SECTION_SOCIAL;
1867
1868
        $view = new Template($toolName);
1869
1870
        if ($showHeader) {
1871
            $view->assign('header', $toolName);
1872
        }
1873
1874
        $actionsStr = '';
1875
1876
        if ($this->course) {
1877
            $actionsStr .= Display::return_introduction_section(TOOL_PORTFOLIO);
0 ignored issues
show
Are you sure the usage of Display::return_introduc...section(TOOL_PORTFOLIO) targeting Display::return_introduction_section() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
1878
        }
1879
1880
        if ($actions) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $actions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1881
            $actions = implode(PHP_EOL, $actions);
1882
1883
            $actionsStr .= Display::toolbarAction('portfolio-toolbar', [$actions]);
1884
        }
1885
1886
        $view->assign('baseurl', $this->baseUrl);
1887
        $view->assign('actions', $actionsStr);
1888
1889
        $view->assign('content', $content);
1890
        $view->display_one_col_template();
1891
    }
1892
1893
    private function categoryBelongToOwner(PortfolioCategory $category): bool
1894
    {
1895
        if ($category->getUser()->getId() != $this->owner->getId()) {
1896
            return false;
1897
        }
1898
1899
        return true;
1900
    }
1901
1902
    private function addAttachmentsFieldToForm(FormValidator $form)
1903
    {
1904
        $form->addButton('add_attachment', get_lang('AddAttachment'), 'plus');
1905
        $form->addHtml('<div id="container-attachments" style="display: none;">');
1906
        $form->addFile('attachment_file[]', get_lang('FilesAttachment'));
1907
        $form->addText('attachment_comment[]', get_lang('Description'), false);
1908
        $form->addHtml('</div>');
1909
1910
        $script = "$(function () {
1911
            var attachmentsTemplate = $('#container-attachments').html();
1912
            var \$btnAdd = $('[name=\"add_attachment\"]');
1913
            var \$reference = \$btnAdd.parents('.form-group');
1914
1915
            \$btnAdd.on('click', function (e) {
1916
                e.preventDefault();
1917
1918
                $(attachmentsTemplate).insertBefore(\$reference);
1919
            });
1920
        })";
1921
1922
        $form->addHtml("<script>$script</script>");
1923
    }
1924
1925
    private function processAttachments(
1926
        FormValidator $form,
1927
        User $user,
1928
        int $originId,
1929
        int $originType
1930
    ) {
1931
        $em = Database::getManager();
1932
        $fs = new Filesystem();
1933
1934
        $comments = $form->getSubmitValue('attachment_comment');
1935
1936
        foreach ($_FILES['attachment_file']['error'] as $i => $attachmentFileError) {
1937
            if ($attachmentFileError != UPLOAD_ERR_OK) {
1938
                continue;
1939
            }
1940
1941
            $_file = [
1942
                'name' => $_FILES['attachment_file']['name'][$i],
1943
                'type' => $_FILES['attachment_file']['type'][$i],
1944
                'tmp_name' => $_FILES['attachment_file']['tmp_name'][$i],
1945
                'size' => $_FILES['attachment_file']['size'][$i],
1946
            ];
1947
1948
            if (empty($_file['type'])) {
1949
                $_file['type'] = DocumentManager::file_get_mime_type($_file['name']);
1950
            }
1951
1952
            $newFileName = add_ext_on_mime(stripslashes($_file['name']), $_file['type']);
1953
1954
            if (!filter_extension($newFileName)) {
1955
                Display::addFlash(Display::return_message(get_lang('UplUnableToSaveFileFilteredExtension'), 'error'));
1956
                continue;
1957
            }
1958
1959
            $newFileName = uniqid();
1960
            $attachmentsDirectory = UserManager::getUserPathById($user->getId(), 'system').'portfolio_attachments/';
1961
1962
            if (!$fs->exists($attachmentsDirectory)) {
1963
                $fs->mkdir($attachmentsDirectory, api_get_permissions_for_new_directories());
1964
            }
1965
1966
            $attachmentFilename = $attachmentsDirectory.$newFileName;
1967
1968
            if (is_uploaded_file($_file['tmp_name'])) {
1969
                $moved = move_uploaded_file($_file['tmp_name'], $attachmentFilename);
1970
1971
                if (!$moved) {
1972
                    Display::addFlash(Display::return_message(get_lang('UplUnableToSaveFile'), 'error'));
1973
                    continue;
1974
                }
1975
            }
1976
1977
            $attachment = new PortfolioAttachment();
1978
            $attachment
1979
                ->setFilename($_file['name'])
1980
                ->setComment($comments[$i])
1981
                ->setPath($newFileName)
1982
                ->setOrigin($originId)
1983
                ->setOriginType($originType)
1984
                ->setSize($_file['size']);
1985
1986
            $em->persist($attachment);
1987
            $em->flush();
1988
        }
1989
    }
1990
1991
    private function itemBelongToOwner(Portfolio $item): bool
1992
    {
1993
        if ($item->getUser()->getId() != $this->owner->getId()) {
1994
            return false;
1995
        }
1996
1997
        return true;
1998
    }
1999
2000
    private function createFormTagFilter(bool $listByUser = false): FormValidator
2001
    {
2002
        $extraField = new ExtraField('portfolio');
2003
        $tagFieldInfo = $extraField->get_handler_field_info_by_tags('tags');
2004
2005
        $chbxTagOptions = array_map(
2006
            function (array $tagOption) {
2007
                return $tagOption['tag'];
2008
            },
2009
            $tagFieldInfo['options'] ?? []
2010
        );
2011
2012
        $frmTagList = new FormValidator(
2013
            'frm_tag_list',
2014
            'get',
2015
            $this->baseUrl.($listByUser ? 'user='.$this->owner->getId() : ''),
2016
            '',
2017
            [],
2018
            FormValidator::LAYOUT_BOX
2019
        );
2020
2021
        if (!empty($chbxTagOptions)) {
2022
            $frmTagList->addCheckBoxGroup('tags', $tagFieldInfo['display_text'], $chbxTagOptions);
2023
        }
2024
2025
        $frmTagList->addText('text', get_lang('Search'), false)->setIcon('search');
2026
        $frmTagList->applyFilter('text', 'trim');
2027
        $frmTagList->addHtml('<br>');
2028
        $frmTagList->addButtonFilter(get_lang('Filter'));
2029
2030
        if ($this->course) {
2031
            $frmTagList->addHidden('cidReq', $this->course->getCode());
2032
            $frmTagList->addHidden('id_session', $this->session ? $this->session->getId() : 0);
2033
            $frmTagList->addHidden('gidReq', 0);
2034
            $frmTagList->addHidden('gradebook', 0);
2035
            $frmTagList->addHidden('origin', '');
2036
2037
            if ($listByUser) {
2038
                $frmTagList->addHidden('user', $this->owner->getId());
2039
            }
2040
        }
2041
2042
        return $frmTagList;
2043
    }
2044
2045
    /**
2046
     * @throws \Exception
2047
     *
2048
     * @return \FormValidator
2049
     */
2050
    private function createFormStudentFilter(bool $listByUser = false): FormValidator
2051
    {
2052
        $frmStudentList = new FormValidator(
2053
            'frm_student_list',
2054
            'get',
2055
            $this->baseUrl,
2056
            '',
2057
            [],
2058
            FormValidator::LAYOUT_BOX
2059
        );
2060
        $slctStudentOptions = [];
2061
2062
        if ($listByUser) {
2063
            $slctStudentOptions[$this->owner->getId()] = $this->owner->getCompleteName();
2064
        }
2065
2066
        $urlParams = http_build_query(
2067
            [
2068
                'a' => 'search_user_by_course',
2069
                'course_id' => $this->course->getId(),
2070
                'session_id' => $this->session ? $this->session->getId() : 0,
2071
            ]
2072
        );
2073
2074
        $frmStudentList->addSelectAjax(
2075
            'user',
2076
            get_lang('SelectLearnerPortfolio'),
2077
            $slctStudentOptions,
2078
            [
2079
                'url' => api_get_path(WEB_AJAX_PATH)."course.ajax.php?$urlParams",
2080
                'placeholder' => get_lang('SearchStudent'),
2081
            ]
2082
        );
2083
2084
        if ($listByUser) {
2085
            $link = Display::url(
2086
                get_lang('BackToMainPortfolio'),
2087
                $this->baseUrl
2088
            );
2089
        } else {
2090
            $link = Display::url(
2091
                get_lang('SeeMyPortfolio'),
2092
                $this->baseUrl.http_build_query(['user' => api_get_user_id()])
2093
            );
2094
        }
2095
2096
        $frmStudentList->addHtml($link);
2097
2098
        return $frmStudentList;
2099
    }
2100
2101
    private function getCategoriesForIndex(int $currentUserId): array
2102
    {
2103
        $categoriesCriteria = [];
2104
        $categoriesCriteria['user'] = $this->owner;
2105
2106
        if ($currentUserId !== $this->owner->getId()) {
2107
            $categoriesCriteria['isVisible'] = true;
2108
        }
2109
2110
        return $this->em
2111
            ->getRepository(PortfolioCategory::class)
2112
            ->findBy($categoriesCriteria);
2113
    }
2114
2115
    private function getItemsForIndex(
2116
        bool $listByUser = false,
2117
        FormValidator $frmFilterList = null
2118
    ) {
2119
        $currentUserId = api_get_user_id();
2120
2121
        if ($this->course) {
2122
            $queryBuilder = $this->em->createQueryBuilder();
2123
            $queryBuilder
2124
                ->select('pi')
2125
                ->from(Portfolio::class, 'pi')
2126
                ->where('pi.course = :course');
2127
2128
            $queryBuilder->setParameter('course', $this->course);
2129
2130
            if ($this->session) {
2131
                $queryBuilder->andWhere('pi.session = :session');
2132
                $queryBuilder->setParameter('session', $this->session);
2133
            } else {
2134
                $queryBuilder->andWhere('pi.session IS NULL');
2135
            }
2136
2137
            if ($frmFilterList && $frmFilterList->validate()) {
2138
                $values = $frmFilterList->exportValues();
2139
2140
                if (!empty($values['tags'])) {
2141
                    $queryBuilder
2142
                        ->innerJoin(ExtraFieldRelTag::class, 'efrt', Join::WITH, 'efrt.itemId = pi.id')
2143
                        ->innerJoin(ExtraFieldEntity::class, 'ef', Join::WITH, 'ef.id = efrt.fieldId')
2144
                        ->andWhere('ef.extraFieldType = :efType')
2145
                        ->andWhere('ef.variable = :variable')
2146
                        ->andWhere('efrt.tagId IN (:tags)');
2147
2148
                    $queryBuilder->setParameter('efType', ExtraFieldEntity::PORTFOLIO_TYPE);
2149
                    $queryBuilder->setParameter('variable', 'tags');
2150
                    $queryBuilder->setParameter('tags', $values['tags']);
2151
                }
2152
2153
                if (!empty($values['text'])) {
2154
                    $queryBuilder->andWhere(
2155
                        $queryBuilder->expr()->orX(
2156
                            $queryBuilder->expr()->like('pi.title', ':text'),
2157
                            $queryBuilder->expr()->like('pi.content', ':text')
2158
                        )
2159
                    );
2160
2161
                    $queryBuilder->setParameter('text', '%'.$values['text'].'%');
2162
                }
2163
            }
2164
2165
            if ($listByUser) {
2166
                $queryBuilder
2167
                    ->andWhere('pi.user = :user')
2168
                    ->setParameter('user', $this->owner);
2169
            }
2170
2171
            $queryBuilder
2172
                ->andWhere(
2173
                    $queryBuilder->expr()->orX(
2174
                        'pi.user = :current_user AND (pi.isVisible = TRUE OR pi.isVisible = FALSE)',
2175
                        'pi.user != :current_user AND pi.isVisible = TRUE'
2176
                    )
2177
                )
2178
                ->setParameter('current_user', $currentUserId);
2179
2180
            $queryBuilder->orderBy('pi.creationDate', 'DESC');
2181
2182
            $items = $queryBuilder->getQuery()->getResult();
2183
        } else {
2184
            $itemsCriteria = [];
2185
            $itemsCriteria['category'] = null;
2186
            $itemsCriteria['user'] = $this->owner;
2187
2188
            if ($currentUserId !== $this->owner->getId()) {
2189
                $itemsCriteria['isVisible'] = true;
2190
            }
2191
2192
            $items = $this->em
2193
                ->getRepository(Portfolio::class)
2194
                ->findBy($itemsCriteria, ['creationDate' => 'DESC']);
2195
        }
2196
2197
        return $items;
2198
    }
2199
2200
    /**
2201
     * @throws \Doctrine\ORM\ORMException
2202
     * @throws \Doctrine\ORM\OptimisticLockException
2203
     * @throws \Doctrine\ORM\TransactionRequiredException
2204
     */
2205
    private function createCommentForm(Portfolio $item): string
2206
    {
2207
        $formAction = $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()]);
2208
2209
        $form = new FormValidator('frm_comment', 'post', $formAction);
2210
        $form->addHtmlEditor('content', get_lang('Comments'), true, false, ['ToolbarSet' => 'Minimal']);
2211
        $form->addHidden('item', $item->getId());
2212
        $form->addHidden('parent', 0);
2213
        $form->applyFilter('content', 'trim');
2214
2215
        $this->addAttachmentsFieldToForm($form);
2216
2217
        $form->addButtonSave(get_lang('Save'));
2218
2219
        if ($form->validate()) {
2220
            $values = $form->exportValues();
2221
2222
            $parentComment = $this->em->find(PortfolioComment::class, $values['parent']);
2223
2224
            $comment = new PortfolioComment();
2225
            $comment
2226
                ->setAuthor($this->owner)
2227
                ->setParent($parentComment)
2228
                ->setContent($values['content'])
2229
                ->setDate(api_get_utc_datetime(null, false, true))
2230
                ->setItem($item);
2231
2232
            $this->em->persist($comment);
2233
            $this->em->flush();
2234
2235
            $this->processAttachments(
2236
                $form,
2237
                $comment->getAuthor(),
2238
                $comment->getId(),
2239
                PortfolioAttachment::TYPE_COMMENT
2240
            );
2241
2242
            $hook = HookPortfolioItemCommented::create();
2243
            $hook->setEventData(['comment' => $comment]);
2244
            $hook->notifyItemCommented();
2245
2246
            Display::addFlash(
2247
                Display::return_message(get_lang('CommentAdded'), 'success')
2248
            );
2249
2250
            header("Location: $formAction");
2251
            exit;
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return string. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
2252
        }
2253
2254
        return $form->returnForm();
2255
    }
2256
2257
    private function generateAttachmentList($post, bool $includeHeader = true): string
2258
    {
2259
        $attachmentsRepo = $this->em->getRepository(PortfolioAttachment::class);
2260
2261
        $postOwnerId = 0;
2262
2263
        if ($post instanceof Portfolio) {
2264
            $attachments = $attachmentsRepo->findFromItem($post);
2265
2266
            $postOwnerId = $post->getUser()->getId();
2267
        } elseif ($post instanceof PortfolioComment) {
2268
            $attachments = $attachmentsRepo->findFromComment($post);
2269
2270
            $postOwnerId = $post->getAuthor()->getId();
2271
        }
2272
2273
        if (empty($attachments)) {
2274
            return '';
2275
        }
2276
2277
        $currentUserId = api_get_user_id();
2278
2279
        $listItems = '<ul class="fa-ul">';
2280
2281
        $deleteIcon = Display::return_icon(
2282
            'delete.png',
2283
            get_lang('DeleteAttachment'),
2284
            ['style' => 'display: inline-block'],
2285
            ICON_SIZE_TINY
2286
        );
2287
        $deleteAttrs = ['class' => 'btn-portfolio-delete'];
2288
2289
        /** @var PortfolioAttachment $attachment */
2290
        foreach ($attachments as $attachment) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $attachments does not seem to be defined for all execution paths leading up to this point.
Loading history...
2291
            $downloadParams = http_build_query(['action' => 'download_attachment', 'file' => $attachment->getPath()]);
2292
            $deleteParams = http_build_query(['action' => 'delete_attachment', 'file' => $attachment->getPath()]);
2293
2294
            $listItems .= '<li>'
2295
                .'<span class="fa-li fa fa-paperclip" aria-hidden="true"></span>'
2296
                .Display::url(
2297
                    Security::remove_XSS($attachment->getFilename()),
2298
                    $this->baseUrl.$downloadParams
2299
                );
2300
2301
            if ($currentUserId === $postOwnerId) {
2302
                $listItems .= PHP_EOL.Display::url($deleteIcon, $this->baseUrl.$deleteParams, $deleteAttrs);
2303
            }
2304
2305
            if ($attachment->getComment()) {
2306
                $listItems .= PHP_EOL.Display::span(
2307
                        Security::remove_XSS($attachment->getComment()),
2308
                        ['class' => 'text-muted']
2309
                    );
2310
            }
2311
2312
            $listItems .= '</li>';
2313
        }
2314
2315
        $listItems .= '</ul>';
2316
2317
        if ($includeHeader) {
2318
            $listItems = Display::page_subheader(get_lang('AttachmentFiles'), null, 'h5', ['class' => 'h4'])
2319
                .$listItems;
2320
        }
2321
2322
        return $listItems;
2323
    }
2324
2325
    private function generateItemContent(Portfolio $item): string
2326
    {
2327
        $originId = $item->getOrigin();
2328
2329
        if (empty($originId)) {
2330
            return $item->getContent();
2331
        }
2332
2333
        $em = Database::getManager();
2334
2335
        $originContent = '';
2336
        $originContentFooter = '';
2337
2338
        if (Portfolio::TYPE_ITEM === $item->getOriginType()) {
2339
            $origin = $em->find(Portfolio::class, $item->getOrigin());
2340
2341
            if ($origin) {
2342
                $originContent = $origin->getContent();
2343
                $originContentFooter = vsprintf(
2344
                    get_lang('OriginallyPublishedAsXTitleByYUser'),
2345
                    [$origin->getTitle(), $origin->getUser()->getCompleteName()]
2346
                );
2347
            }
2348
        } elseif (Portfolio::TYPE_COMMENT === $item->getOriginType()) {
2349
            $origin = $em->find(PortfolioComment::class, $item->getOrigin());
2350
2351
            if ($origin) {
2352
                $originContent = $origin->getContent();
2353
                $originContentFooter = vsprintf(
2354
                    get_lang('OriginallyCommentedByXUserInYItem'),
2355
                    [$origin->getAuthor()->getCompleteName(), $origin->getItem()->getTitle()]
2356
                );
2357
            }
2358
        }
2359
2360
        if ($originContent) {
2361
            return "<blockquote>$originContent<footer>$originContentFooter</footer></blockquote>"
2362
                .'<div class="clearfix">'.$item->getContent().'</div>';
2363
        }
2364
2365
        return $item->getContent();
2366
    }
2367
2368
    private function getItemsInHtmlFormatted(array $items): array
2369
    {
2370
        $itemsHtml = [];
2371
2372
        /** @var Portfolio $item */
2373
        foreach ($items as $item) {
2374
            $creationDate = api_convert_and_format_date($item->getCreationDate());
2375
            $updateDate = api_convert_and_format_date($item->getUpdateDate());
2376
2377
            $metadata = '<ul class="list-unstyled text-muted">';
2378
2379
            if ($item->getSession()) {
2380
                $metadata .= '<li>'.get_lang('Course').': '.$item->getSession()->getName().' ('
2381
                    .$item->getCourse()->getTitle().') </li>';
2382
            } elseif (!$item->getSession() && $item->getCourse()) {
2383
                $metadata .= '<li>'.get_lang('Course').': '.$item->getCourse()->getTitle().'</li>';
2384
            }
2385
2386
            $metadata .= '<li>'.sprintf(get_lang('CreationDateXDate'), $creationDate).'</li>';
2387
            $metadata .= '<li>'.sprintf(get_lang('UpdateDateXDate'), $updateDate).'</li>';
2388
2389
            if ($item->getCategory()) {
2390
                $metadata .= '<li>'.sprintf(get_lang('CategoryXName'), $item->getCategory()->getTitle()).'</li>';
2391
            }
2392
2393
            $metadata .= '</ul>';
2394
2395
            $itemContent = Security::remove_XSS(
2396
                $this->generateItemContent($item)
2397
            );
2398
2399
            $itemsHtml[] = Display::panel($itemContent, Security::remove_XSS($item->getTitle()), '', 'info', $metadata);
2400
        }
2401
2402
        return $itemsHtml;
2403
    }
2404
2405
    private function getCommentsInHtmlFormatted(array $comments): array
2406
    {
2407
        $commentsHtml = [];
2408
2409
        /** @var PortfolioComment $comment */
2410
        foreach ($comments as $comment) {
2411
            $item = $comment->getItem();
2412
            $date = api_convert_and_format_date($comment->getDate());
2413
2414
            $metadata = '<ul class="list-unstyled text-muted">';
2415
            $metadata .= '<li>'.sprintf(get_lang('DateXDate'), $date).'</li>';
2416
            $metadata .= '<li>'.sprintf(get_lang('PortfolioItemTitleXName'), Security::remove_XSS($item->getTitle()))
2417
                .'</li>';
2418
            $metadata .= '</ul>';
2419
2420
            $commentsHtml[] = Display::panel(
2421
                Security::remove_XSS($comment->getContent()),
2422
                '',
2423
                '',
2424
                'default',
2425
                $metadata
2426
            );
2427
        }
2428
2429
        return $commentsHtml;
2430
    }
2431
2432
    private function fixImagesSourcesToHtml(string $htmlContent): string
2433
    {
2434
        $doc = new DOMDocument();
2435
        @$doc->loadHTML($htmlContent);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for loadHTML(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

2435
        /** @scrutinizer ignore-unhandled */ @$doc->loadHTML($htmlContent);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2436
2437
        $elements = $doc->getElementsByTagName('img');
2438
2439
        if (empty($elements->length)) {
2440
            return $htmlContent;
2441
        }
2442
2443
        $webCoursePath = api_get_path(WEB_COURSE_PATH);
2444
        $webUploadPath = api_get_path(WEB_UPLOAD_PATH);
2445
2446
        /** @var \DOMElement $element */
2447
        foreach ($elements as $element) {
2448
            $src = trim($element->getAttribute('src'));
2449
2450
            if (strpos($src, 'http') === 0) {
2451
                continue;
2452
            }
2453
2454
            if (strpos($src, '/app/upload/') === 0) {
2455
                $element->setAttribute(
2456
                    'src',
2457
                    preg_replace('/\/app/upload\//', $webUploadPath, $src, 1)
2458
                );
2459
2460
                continue;
2461
            }
2462
2463
            if (strpos($src, '/courses/') === 0) {
2464
                $element->setAttribute(
2465
                    'src',
2466
                    preg_replace('/\/courses\//', $webCoursePath, $src, 1)
2467
                );
2468
2469
                continue;
2470
            }
2471
        }
2472
2473
        return $doc->saveHTML();
2474
    }
2475
2476
    private function formatZipIndexFile(HTML_Table $tblItems, HTML_Table $tblComments): string
2477
    {
2478
        $htmlContent = Display::page_header($this->owner->getCompleteNameWithUsername());
2479
        $htmlContent .= Display::page_subheader2(get_lang('PortfolioItems'));
2480
2481
        $htmlContent .= $tblItems->getRowCount() > 0
2482
            ? $tblItems->toHtml()
2483
            : Display::return_message(get_lang('NoItemsInYourPortfolio'), 'warning');
2484
2485
        $htmlContent .= Display::page_subheader2(get_lang('PortfolioCommentsMade'));
2486
2487
        $htmlContent .= $tblComments->getRowCount() > 0
2488
            ? $tblComments->toHtml()
2489
            : Display::return_message(get_lang('YouHaveNotCommented'), 'warning');
2490
2491
        $webAssetsPath = api_get_path(WEB_PUBLIC_PATH).'assets/';
2492
2493
        $doc = new DOMDocument();
2494
        @$doc->loadHTML($htmlContent);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for loadHTML(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

2494
        /** @scrutinizer ignore-unhandled */ @$doc->loadHTML($htmlContent);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2495
2496
        $stylesheet1 = $doc->createElement('link');
2497
        $stylesheet1->setAttribute('rel', 'stylesheet');
2498
        $stylesheet1->setAttribute('href', $webAssetsPath.'bootstrap/dist/css/bootstrap.min.css');
2499
        $stylesheet2 = $doc->createElement('link');
2500
        $stylesheet2->setAttribute('rel', 'stylesheet');
2501
        $stylesheet2->setAttribute('href', $webAssetsPath.'fontawesome/css/font-awesome.min.css');
2502
        $stylesheet3 = $doc->createElement('link');
2503
        $stylesheet3->setAttribute('rel', 'stylesheet');
2504
        $stylesheet3->setAttribute('href', ChamiloApi::getEditorDocStylePath());
2505
2506
        $head = $doc->createElement('head');
2507
        $head->appendChild($stylesheet1);
2508
        $head->appendChild($stylesheet2);
2509
        $head->appendChild($stylesheet3);
2510
2511
        $doc->documentElement->insertBefore(
2512
            $head,
2513
            $doc->getElementsByTagName('body')->item(0)
2514
        );
2515
2516
        return $doc->saveHTML();
2517
    }
2518
}
2519