Passed
Pull Request — 1.11.x (#4392)
by Angel Fernando Quiroz
09:10
created

PortfolioController::getCategoriesForIndex()   A

Complexity

Conditions 5
Paths 8

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 10
c 0
b 0
f 0
nc 8
nop 2
dl 0
loc 16
rs 9.6111
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\CoreBundle\Entity\PortfolioRelTag;
14
use Chamilo\CoreBundle\Entity\Tag;
15
use Chamilo\UserBundle\Entity\User;
16
use Doctrine\ORM\Query\Expr\Join;
17
use Mpdf\MpdfException;
18
use Symfony\Component\Filesystem\Filesystem;
19
use Symfony\Component\HttpFoundation\Request as HttpRequest;
0 ignored issues
show
Bug introduced by
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...
20
21
/**
22
 * Class PortfolioController.
23
 */
24
class PortfolioController
25
{
26
    /**
27
     * @var string
28
     */
29
    public $baseUrl;
30
    /**
31
     * @var CourseEntity|null
32
     */
33
    private $course;
34
    /**
35
     * @var \Chamilo\CoreBundle\Entity\Session|null
36
     */
37
    private $session;
38
    /**
39
     * @var \Chamilo\UserBundle\Entity\User
40
     */
41
    private $owner;
42
    /**
43
     * @var \Doctrine\ORM\EntityManager
44
     */
45
    private $em;
46
47
    /**
48
     * PortfolioController constructor.
49
     */
50
    public function __construct()
51
    {
52
        $this->em = Database::getManager();
53
54
        $this->owner = api_get_user_entity(api_get_user_id());
55
        $this->course = api_get_course_entity(api_get_course_int_id());
56
        $this->session = api_get_session_entity(api_get_session_id());
57
58
        $cidreq = api_get_cidreq();
59
        $this->baseUrl = api_get_self().'?'.($cidreq ? $cidreq.'&' : '');
60
    }
61
62
    /**
63
     * @throws \Doctrine\ORM\ORMException
64
     * @throws \Doctrine\ORM\OptimisticLockException
65
     */
66
    public function translateCategory($category, $languages, $languageId)
67
    {
68
        global $interbreadcrumb;
69
70
        $originalName = $category->getTitle();
71
        $variableLanguage = '$'.$this->getLanguageVariable($originalName);
72
73
        $translateUrl = api_get_path(WEB_AJAX_PATH).'lang.ajax.php?a=translate_portfolio_category&sec_token='.Security::get_token();
74
        $form = new FormValidator('new_lang_variable', 'POST', $translateUrl);
75
        $form->addHeader(get_lang('AddWordForTheSubLanguage'));
76
        $form->addText('variable_language', get_lang('LanguageVariable'), false);
77
        $form->addText('original_name', get_lang('OriginalName'), false);
78
79
        $languagesOptions = [0 => get_lang('None')];
80
        foreach ($languages as $language) {
81
            $languagesOptions[$language->getId()] = $language->getOriginalName();
82
        }
83
84
        $form->addSelect(
85
            'sub_language',
86
            [get_lang('SubLanguage'), get_lang('OnlyActiveSubLanguagesAreListed')],
87
            $languagesOptions
88
        );
89
90
        if ($languageId) {
91
            $languageInfo = api_get_language_info($languageId);
92
            $form->addText(
93
                'new_language',
94
                [get_lang('Translation'), get_lang('IfThisTranslationExistsThisWillReplaceTheTerm')]
95
            );
96
97
            $form->addHidden('category_id', $category->getId());
98
            $form->addHidden('id', $languageInfo['parent_id']);
99
            $form->addHidden('sub', $languageInfo['id']);
100
            $form->addHidden('sub_language_id', $languageInfo['id']);
101
            $form->addHidden('redirect', true);
102
            $form->addButtonSave(get_lang('Save'));
103
        }
104
105
        $form->setDefaults([
106
            'variable_language' => $variableLanguage,
107
            'original_name' => $originalName,
108
            'sub_language' => $languageId,
109
        ]);
110
        $form->addRule('sub_language', get_lang('Required'), 'required');
111
        $form->freeze(['variable_language', 'original_name']);
112
113
        $interbreadcrumb[] = [
114
            'name' => get_lang('Portfolio'),
115
            'url' => $this->baseUrl,
116
        ];
117
        $interbreadcrumb[] = [
118
            'name' => get_lang('Categories'),
119
            'url' => $this->baseUrl.'action=list_categories&parent_id='.$category->getParentId(),
120
        ];
121
        $interbreadcrumb[] = [
122
            'name' => Security::remove_XSS($category->getTitle()),
123
            'url' => $this->baseUrl.'action=edit_category&id='.$category->getId(),
124
        ];
125
126
        $actions = [];
127
        $actions[] = Display::url(
128
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
129
            $this->baseUrl.'action=edit_category&id='.$category->getId()
130
        );
131
132
        $js = '<script>
133
            $(function() {
134
              $("select[name=\'sub_language\']").on("change", function () {
135
                    location.href += "&sub_language=" + this.value;
136
                });
137
            });
138
        </script>';
139
        $content = $form->returnForm();
140
141
        $this->renderView($content.$js, get_lang('TranslateCategory'), $actions);
142
    }
143
144
    /**
145
     * @throws \Doctrine\ORM\ORMException
146
     * @throws \Doctrine\ORM\OptimisticLockException
147
     */
148
    public function listCategories()
149
    {
150
        global $interbreadcrumb;
151
152
        $parentId = isset($_REQUEST['parent_id']) ? (int) $_REQUEST['parent_id'] : 0;
153
        $table = new HTML_Table(['class' => 'table table-hover table-striped data_table']);
154
        $headers = [
155
            get_lang('Title'),
156
            get_lang('Description'),
157
        ];
158
        if ($parentId === 0) {
159
            $headers[] = get_lang('SubCategories');
160
        }
161
        $headers[] = get_lang('Actions');
162
163
        $column = 0;
164
        foreach ($headers as $header) {
165
            $table->setHeaderContents(0, $column, $header);
166
            $column++;
167
        }
168
        $currentUserId = api_get_user_id();
169
        $row = 1;
170
        $categories = $this->getCategoriesForIndex(null, $parentId);
171
172
        foreach ($categories as $category) {
173
            $column = 0;
174
            $subcategories = $this->getCategoriesForIndex(null, $category->getId());
175
            $linkSubCategories = $category->getTitle();
176
            if (count($subcategories) > 0) {
177
                $linkSubCategories = Display::url(
178
                    $category->getTitle(),
179
                    $this->baseUrl.'action=list_categories&parent_id='.$category->getId()
180
                );
181
            }
182
            $table->setCellContents($row, $column++, $linkSubCategories);
183
            $table->setCellContents($row, $column++, strip_tags($category->getDescription()));
184
            if ($parentId === 0) {
185
                $table->setCellContents($row, $column++, count($subcategories));
186
            }
187
188
            // Actions
189
            $links = null;
190
            // Edit action
191
            $url = $this->baseUrl.'action=edit_category&id='.$category->getId();
192
            $links .= Display::url(Display::return_icon('edit.png', get_lang('Edit')), $url).'&nbsp;';
193
            // Visible action : if active
194
            if ($category->isVisible() != 0) {
195
                $url = $this->baseUrl.'action=hide_category&id='.$category->getId();
196
                $links .= Display::url(Display::return_icon('visible.png', get_lang('Hide')), $url).'&nbsp;';
197
            } else { // else if not active
198
                $url = $this->baseUrl.'action=show_category&id='.$category->getId();
199
                $links .= Display::url(Display::return_icon('invisible.png', get_lang('Show')), $url).'&nbsp;';
200
            }
201
            // Delete action
202
            $url = $this->baseUrl.'action=delete_category&id='.$category->getId();
203
            $links .= Display::url(Display::return_icon('delete.png', get_lang('Delete')), $url, ['onclick' => 'javascript:if(!confirm(\''.get_lang('AreYouSureToDeleteJS').'\')) return false;']);
204
205
            $table->setCellContents($row, $column++, $links);
206
            $row++;
207
        }
208
209
        $interbreadcrumb[] = [
210
            'name' => get_lang('Portfolio'),
211
            'url' => $this->baseUrl,
212
        ];
213
        if ($parentId > 0) {
214
            $interbreadcrumb[] = [
215
                'name' => get_lang('Categories'),
216
                'url' => $this->baseUrl.'action=list_categories',
217
            ];
218
        }
219
220
        $actions = [];
221
        $actions[] = Display::url(
222
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
223
            $this->baseUrl.($parentId > 0 ? 'action=list_categories' : '')
224
        );
225
        if ($currentUserId == $this->owner->getId() && $parentId === 0) {
226
            $actions[] = Display::url(
227
                Display::return_icon('new_folder.png', get_lang('AddCategory'), [], ICON_SIZE_MEDIUM),
228
                $this->baseUrl.'action=add_category'
229
            );
230
        }
231
        $content = $table->toHtml();
232
233
        $pageTitle = get_lang('Categories');
234
        if ($parentId > 0) {
235
            $em = Database::getManager();
236
            $parentCategory = $em->find('ChamiloCoreBundle:PortfolioCategory', $parentId);
237
            $pageTitle = $parentCategory->getTitle().' : '.get_lang('SubCategories');
238
        }
239
240
        $this->renderView($content, $pageTitle, $actions);
241
    }
242
243
    /**
244
     * @throws \Doctrine\ORM\ORMException
245
     * @throws \Doctrine\ORM\OptimisticLockException
246
     */
247
    public function addCategory()
248
    {
249
        global $interbreadcrumb;
250
251
        Display::addFlash(
252
            Display::return_message(get_lang('PortfolioCategoryFieldHelp'), 'info')
253
        );
254
255
        $form = new FormValidator('add_category', 'post', "{$this->baseUrl}&action=add_category");
256
257
        if (api_get_configuration_value('save_titles_as_html')) {
258
            $form->addHtmlEditor('title', get_lang('Title'), true, false, ['ToolbarSet' => 'TitleAsHtml']);
259
        } else {
260
            $form->addText('title', get_lang('Title'));
261
            $form->applyFilter('title', 'trim');
262
        }
263
264
        $form->addHtmlEditor('description', get_lang('Description'), false, false, ['ToolbarSet' => 'Minimal']);
265
266
        $parentSelect = $form->addSelect(
267
            'parent_id',
268
            get_lang('ParentCategory')
269
        );
270
        $parentSelect->addOption(get_lang('Level0'), 0);
271
        $currentUserId = api_get_user_id();
272
        $categories = $this->getCategoriesForIndex(null, 0);
273
        foreach ($categories as $category) {
274
            $parentSelect->addOption($category->getTitle(), $category->getId());
275
        }
276
277
        $form->addButtonCreate(get_lang('Create'));
278
279
        if ($form->validate()) {
280
            $values = $form->exportValues();
281
282
            $category = new PortfolioCategory();
283
            $category
284
                ->setTitle($values['title'])
285
                ->setDescription($values['description'])
286
                ->setParentId($values['parent_id'])
287
                ->setUser($this->owner);
288
289
            $this->em->persist($category);
290
            $this->em->flush();
291
292
            Display::addFlash(
293
                Display::return_message(get_lang('CategoryAdded'), 'success')
294
            );
295
296
            header("Location: {$this->baseUrl}action=list_categories");
297
            exit;
298
        }
299
300
        $interbreadcrumb[] = [
301
            'name' => get_lang('Portfolio'),
302
            'url' => $this->baseUrl,
303
        ];
304
        $interbreadcrumb[] = [
305
            'name' => get_lang('Categories'),
306
            'url' => $this->baseUrl.'action=list_categories',
307
        ];
308
309
        $actions = [];
310
        $actions[] = Display::url(
311
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
312
            $this->baseUrl.'action=list_categories'
313
        );
314
315
        $content = $form->returnForm();
316
317
        $this->renderView($content, get_lang('AddCategory'), $actions);
318
    }
319
320
    /**
321
     * @throws \Doctrine\ORM\ORMException
322
     * @throws \Doctrine\ORM\OptimisticLockException
323
     * @throws \Exception
324
     */
325
    public function editCategory(PortfolioCategory $category)
326
    {
327
        global $interbreadcrumb;
328
329
        if (!api_is_platform_admin()) {
330
            api_not_allowed(true);
331
        }
332
333
        Display::addFlash(
334
            Display::return_message(get_lang('PortfolioCategoryFieldHelp'), 'info')
335
        );
336
337
        $form = new FormValidator(
338
            'edit_category',
339
            'post',
340
            $this->baseUrl."action=edit_category&id={$category->getId()}"
341
        );
342
343
        if (api_get_configuration_value('save_titles_as_html')) {
344
            $form->addHtmlEditor('title', get_lang('Title'), true, false, ['ToolbarSet' => 'TitleAsHtml']);
345
        } else {
346
            $translateUrl = $this->baseUrl.'action=translate_category&id='.$category->getId();
347
            $translateButton = Display::toolbarButton(get_lang('TranslateThisTerm'), $translateUrl, 'language', 'link');
348
            $form->addText(
349
                'title',
350
                [get_lang('Title'), $translateButton]
351
            );
352
            $form->applyFilter('title', 'trim');
353
        }
354
355
        $form->addHtmlEditor('description', get_lang('Description'), false, false, ['ToolbarSet' => 'Minimal']);
356
        $form->addButtonUpdate(get_lang('Update'));
357
        $form->setDefaults(
358
            [
359
                'title' => $category->getTitle(),
360
                'description' => $category->getDescription(),
361
            ]
362
        );
363
364
        if ($form->validate()) {
365
            $values = $form->exportValues();
366
367
            $category
368
                ->setTitle($values['title'])
369
                ->setDescription($values['description']);
370
371
            $this->em->persist($category);
372
            $this->em->flush();
373
374
            Display::addFlash(
375
                Display::return_message(get_lang('Updated'), 'success')
376
            );
377
378
            header("Location: {$this->baseUrl}action=list_categories&parent_id=".$category->getParentId());
379
            exit;
380
        }
381
382
        $interbreadcrumb[] = [
383
            'name' => get_lang('Portfolio'),
384
            'url' => $this->baseUrl,
385
        ];
386
        $interbreadcrumb[] = [
387
            'name' => get_lang('Categories'),
388
            'url' => $this->baseUrl.'action=list_categories',
389
        ];
390
        if ($category->getParentId() > 0) {
391
            $em = Database::getManager();
392
            $parentCategory = $em->find('ChamiloCoreBundle:PortfolioCategory', $category->getParentId());
393
            $pageTitle = $parentCategory->getTitle().' : '.get_lang('SubCategories');
394
            $interbreadcrumb[] = [
395
                'name' => Security::remove_XSS($pageTitle),
396
                'url' => $this->baseUrl.'action=list_categories&parent_id='.$category->getParentId(),
397
            ];
398
        }
399
400
        $actions = [];
401
        $actions[] = Display::url(
402
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
403
            $this->baseUrl.'action=list_categories&parent_id='.$category->getParentId()
404
        );
405
406
        $content = $form->returnForm();
407
408
        $this->renderView($content, get_lang('EditCategory'), $actions);
409
    }
410
411
    /**
412
     * @throws \Doctrine\ORM\ORMException
413
     * @throws \Doctrine\ORM\OptimisticLockException
414
     */
415
    public function showHideCategory(PortfolioCategory $category)
416
    {
417
        if (!$this->categoryBelongToOwner($category)) {
418
            api_not_allowed(true);
419
        }
420
421
        $category->setIsVisible(!$category->isVisible());
422
423
        $this->em->persist($category);
424
        $this->em->flush();
425
426
        Display::addFlash(
427
            Display::return_message(get_lang('VisibilityChanged'), 'success')
428
        );
429
430
        header("Location: {$this->baseUrl}action=list_categories");
431
        exit;
432
    }
433
434
    /**
435
     * @throws \Doctrine\ORM\ORMException
436
     * @throws \Doctrine\ORM\OptimisticLockException
437
     */
438
    public function deleteCategory(PortfolioCategory $category)
439
    {
440
        if (!api_is_platform_admin()) {
441
            api_not_allowed(true);
442
        }
443
444
        $this->em->remove($category);
445
        $this->em->flush();
446
447
        Display::addFlash(
448
            Display::return_message(get_lang('CategoryDeleted'), 'success')
449
        );
450
451
        header("Location: {$this->baseUrl}action=list_categories");
452
        exit;
453
    }
454
455
    /**
456
     * @throws \Doctrine\ORM\ORMException
457
     * @throws \Doctrine\ORM\OptimisticLockException
458
     * @throws \Doctrine\ORM\TransactionRequiredException
459
     * @throws \Exception
460
     */
461
    public function addItem()
462
    {
463
        global $interbreadcrumb;
464
465
        $templates = $this->em
466
            ->getRepository(Portfolio::class)
467
            ->findBy(
468
                [
469
                    'isTemplate' => true,
470
                    'course' => $this->course,
471
                    'session' => $this->session,
472
                    'user' => $this->owner,
473
                ]
474
            );
475
476
        $form = new FormValidator('add_portfolio', 'post', $this->baseUrl.'action=add_item');
477
        $form->addSelectFromCollection(
478
            'template',
479
            [
480
                get_lang('Template'),
481
                null,
482
                '<span id="portfolio-spinner" class="fa fa-fw fa-spinner fa-spin" style="display: none;"
483
                    aria-hidden="true" aria-label="'.get_lang('Loading').'"></span>',
484
            ],
485
            $templates,
486
            [],
487
            true,
488
            'getTitle'
489
        );
490
491
        if (api_get_configuration_value('save_titles_as_html')) {
492
            $form->addHtmlEditor('title', get_lang('Title'), true, false, ['ToolbarSet' => 'TitleAsHtml']);
493
        } else {
494
            $form->addText('title', get_lang('Title'));
495
            $form->applyFilter('title', 'trim');
496
        }
497
        $editorConfig = [
498
            'ToolbarSet' => 'NotebookStudent',
499
            'Width' => '100%',
500
            'Height' => '400',
501
            'cols-size' => [2, 10, 0],
502
        ];
503
        $form->addHtmlEditor('content', get_lang('Content'), true, false, $editorConfig);
504
505
        $categoriesSelect = $form->addSelect(
506
            'category',
507
            [get_lang('Category'), get_lang('PortfolioCategoryFieldHelp')]
508
        );
509
        $categoriesSelect->addOption(get_lang('SelectACategory'), 0);
510
        $parentCategories = $this->getCategoriesForIndex(null, 0);
511
        foreach ($parentCategories as $parentCategory) {
512
            $categoriesSelect->addOption($this->translateDisplayName($parentCategory->getTitle()), $parentCategory->getId());
513
            $subCategories = $this->getCategoriesForIndex(null, $parentCategory->getId());
514
            if (count($subCategories) > 0) {
515
                foreach ($subCategories as $subCategory) {
516
                    $categoriesSelect->addOption(' &mdash; '.$this->translateDisplayName($subCategory->getTitle()), $subCategory->getId());
517
                }
518
            }
519
        }
520
521
        $extraField = new ExtraField('portfolio');
522
        $extra = $extraField->addElements(
523
            $form,
524
            0,
525
            $this->course ? [] : ['tags']
526
        );
527
528
        $this->addAttachmentsFieldToForm($form);
529
530
        $form->addButtonCreate(get_lang('Create'));
531
532
        if ($form->validate()) {
533
            $values = $form->exportValues();
534
            $currentTime = new DateTime(
535
                api_get_utc_datetime(),
536
                new DateTimeZone('UTC')
537
            );
538
539
            $portfolio = new Portfolio();
540
            $portfolio
541
                ->setTitle($values['title'])
542
                ->setContent($values['content'])
543
                ->setUser($this->owner)
544
                ->setCourse($this->course)
545
                ->setSession($this->session)
546
                ->setCategory(
547
                    $this->em->find('ChamiloCoreBundle:PortfolioCategory', $values['category'])
548
                )
549
                ->setCreationDate($currentTime)
550
                ->setUpdateDate($currentTime);
551
552
            $this->em->persist($portfolio);
553
            $this->em->flush();
554
555
            $values['item_id'] = $portfolio->getId();
556
557
            $extraFieldValue = new ExtraFieldValue('portfolio');
558
            $extraFieldValue->saveFieldValues($values);
559
560
            $this->processAttachments(
561
                $form,
562
                $portfolio->getUser(),
563
                $portfolio->getId(),
564
                PortfolioAttachment::TYPE_ITEM
565
            );
566
567
            $hook = HookPortfolioItemAdded::create();
568
            $hook->setEventData(['portfolio' => $portfolio]);
569
            $hook->notifyItemAdded();
570
571
            if (1 == api_get_course_setting('email_alert_teachers_new_post')) {
572
                if ($this->session) {
573
                    $messageCourseTitle = "{$this->course->getTitle()} ({$this->session->getName()})";
0 ignored issues
show
Bug introduced by
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

573
                    $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...
574
575
                    $teachers = SessionManager::getCoachesByCourseSession(
576
                        $this->session->getId(),
577
                        $this->course->getId()
578
                    );
579
                    $userIdListToSend = array_values($teachers);
580
                } else {
581
                    $messageCourseTitle = $this->course->getTitle();
582
583
                    $teachers = CourseManager::get_teacher_list_from_course_code($this->course->getCode());
584
585
                    $userIdListToSend = array_keys($teachers);
586
                }
587
588
                $messageSubject = sprintf(get_lang('PortfolioAlertNewPostSubject'), $messageCourseTitle);
589
                $messageContent = sprintf(
590
                    get_lang('PortfolioAlertNewPostContent'),
591
                    $this->owner->getCompleteName(),
592
                    $messageCourseTitle,
593
                    $this->baseUrl.http_build_query(['action' => 'view', 'id' => $portfolio->getId()])
594
                );
595
                $messageContent .= '<br><br><dl>'
596
                    .'<dt>'.Security::remove_XSS($portfolio->getTitle()).'</dt>'
597
                    .'<dd>'.$portfolio->getExcerpt().'</dd>'.'</dl>';
598
599
                foreach ($userIdListToSend as $userIdToSend) {
600
                    MessageManager::send_message_simple(
601
                        $userIdToSend,
602
                        $messageSubject,
603
                        $messageContent,
604
                        0,
605
                        false,
606
                        false,
607
                        [],
608
                        false
609
                    );
610
                }
611
            }
612
613
            Display::addFlash(
614
                Display::return_message(get_lang('PortfolioItemAdded'), 'success')
615
            );
616
617
            header("Location: $this->baseUrl");
618
            exit;
619
        }
620
621
        $interbreadcrumb[] = [
622
            'name' => get_lang('Portfolio'),
623
            'url' => $this->baseUrl,
624
        ];
625
626
        $actions = [];
627
        $actions[] = Display::url(
628
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
629
            $this->baseUrl
630
        );
631
        $actions[] = '<a id="hide_bar_template" href="#" role="button">'.
632
            Display::return_icon('expand.png', get_lang('Expand'), ['id' => 'expand'], ICON_SIZE_MEDIUM).
633
            Display::return_icon('contract.png', get_lang('Collapse'), ['id' => 'contract', 'class' => 'hide'], ICON_SIZE_MEDIUM).'</a>';
634
635
        $js = '<script>
636
            $(function() {
637
                $(".scrollbar-light").scrollbar();
638
                $(".scroll-wrapper").css("height", "550px");
639
                expandColumnToogle("#hide_bar_template", {
640
                    selector: "#template_col",
641
                    width: 3
642
                }, {
643
                    selector: "#doc_form",
644
                    width: 9
645
                });
646
                CKEDITOR.on("instanceReady", function (e) {
647
                    showTemplates();
648
                });
649
                $(window).on("load", function () {
650
                    $("input[name=\'title\']").focus();
651
                });
652
                $(\'#add_portfolio_template\').on(\'change\', function () {
653
                    $(\'#portfolio-spinner\').show();
654
                
655
                    $.getJSON(_p.web_ajax + \'portfolio.ajax.php?a=find_template&item=\' + this.value)
656
                        .done(function(response) {
657
                            if (CKEDITOR.instances.title) {
658
                                CKEDITOR.instances.title.setData(response.title);
659
                            } else {
660
                                document.getElementById(\'add_portfolio_title\').value = response.title;
661
                            }
662
663
                            CKEDITOR.instances.content.setData(response.content);
664
                        })
665
                        .fail(function () {
666
                            if (CKEDITOR.instances.title) {
667
                                CKEDITOR.instances.title.setData(\'\');
668
                            } else {
669
                                document.getElementById(\'add_portfolio_title\').value = \'\';
670
                            }
671
672
                            CKEDITOR.instances.content.setData(\'\');
673
                        })
674
                        .always(function() {
675
                          $(\'#portfolio-spinner\').hide();
676
                        });
677
                });
678
                '.$extra['jquery_ready_content'].'
679
            });
680
        </script>';
681
        $content = '<div class="page-create">
682
            <div class="row" style="overflow:hidden">
683
            <div id="template_col" class="col-md-3">
684
                <div class="panel panel-default">
685
                <div class="panel-body">
686
                    <div id="frmModel" class="items-templates scrollbar-light"></div>
687
                </div>
688
                </div>
689
            </div>
690
            <div id="doc_form" class="col-md-9">
691
                '.$form->returnForm().'
692
            </div>
693
          </div></div>';
694
695
        $this->renderView(
696
            $content.$js,
697
            get_lang('AddPortfolioItem'),
698
            $actions
699
        );
700
    }
701
702
    /**
703
     * @throws \Doctrine\ORM\ORMException
704
     * @throws \Doctrine\ORM\OptimisticLockException
705
     * @throws \Doctrine\ORM\TransactionRequiredException
706
     * @throws \Exception
707
     */
708
    public function editItem(Portfolio $item)
709
    {
710
        global $interbreadcrumb;
711
712
        if (!api_is_allowed_to_edit() && !$this->itemBelongToOwner($item)) {
713
            api_not_allowed(true);
714
        }
715
716
        $itemCourse = $item->getCourse();
717
        $itemSession = $item->getSession();
718
719
        $form = new FormValidator('edit_portfolio', 'post', $this->baseUrl."action=edit_item&id={$item->getId()}");
720
721
        if (api_get_configuration_value('save_titles_as_html')) {
722
            $form->addHtmlEditor('title', get_lang('Title'), true, false, ['ToolbarSet' => 'TitleAsHtml']);
723
        } else {
724
            $form->addText('title', get_lang('Title'));
725
            $form->applyFilter('title', 'trim');
726
        }
727
728
        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...
729
            if (Portfolio::TYPE_ITEM === $item->getOriginType()) {
730
                $origin = $this->em->find(Portfolio::class, $item->getOrigin());
731
732
                $form->addLabel(
733
                    sprintf(get_lang('PortfolioItemFromXUser'), $origin->getUser()->getCompleteName()),
734
                    Display::panel(
735
                        Security::remove_XSS($origin->getContent())
736
                    )
737
                );
738
            } elseif (Portfolio::TYPE_COMMENT === $item->getOriginType()) {
739
                $origin = $this->em->find(PortfolioComment::class, $item->getOrigin());
740
741
                $form->addLabel(
742
                    sprintf(get_lang('PortfolioCommentFromXUser'), $origin->getAuthor()->getCompleteName()),
743
                    Display::panel(
744
                        Security::remove_XSS($origin->getContent())
745
                    )
746
                );
747
            }
748
        }
749
        $editorConfig = [
750
            'ToolbarSet' => 'NotebookStudent',
751
            'Width' => '100%',
752
            'Height' => '400',
753
            'cols-size' => [2, 10, 0],
754
        ];
755
        $form->addHtmlEditor('content', get_lang('Content'), true, false, $editorConfig);
756
        $categoriesSelect = $form->addSelect(
757
            'category',
758
            [get_lang('Category'), get_lang('PortfolioCategoryFieldHelp')]
759
        );
760
        $categoriesSelect->addOption(get_lang('SelectACategory'), 0);
761
        $parentCategories = $this->getCategoriesForIndex(null, 0);
762
        foreach ($parentCategories as $parentCategory) {
763
            $categoriesSelect->addOption($this->translateDisplayName($parentCategory->getTitle()), $parentCategory->getId());
764
            $subCategories = $this->getCategoriesForIndex(null, $parentCategory->getId());
765
            if (count($subCategories) > 0) {
766
                foreach ($subCategories as $subCategory) {
767
                    $categoriesSelect->addOption(' &mdash; '.$this->translateDisplayName($subCategory->getTitle()), $subCategory->getId());
768
                }
769
            }
770
        }
771
772
        $extraField = new ExtraField('portfolio');
773
        $extra = $extraField->addElements(
774
            $form,
775
            $item->getId(),
776
            $this->course ? [] : ['tags']
777
        );
778
779
        $attachmentList = $this->generateAttachmentList($item, false);
780
781
        if (!empty($attachmentList)) {
782
            $form->addLabel(get_lang('AttachmentFiles'), $attachmentList);
783
        }
784
785
        $this->addAttachmentsFieldToForm($form);
786
787
        $form->addButtonUpdate(get_lang('Update'));
788
        $form->setDefaults(
789
            [
790
                'title' => $item->getTitle(),
791
                'content' => $item->getContent(),
792
                'category' => $item->getCategory() ? $item->getCategory()->getId() : '',
793
            ]
794
        );
795
796
        if ($form->validate()) {
797
            if ($itemCourse) {
0 ignored issues
show
introduced by
$itemCourse is of type Chamilo\CoreBundle\Entity\Course, thus it always evaluated to true.
Loading history...
798
                api_item_property_update(
799
                    api_get_course_info($itemCourse->getCode()),
800
                    TOOL_PORTFOLIO,
801
                    $item->getId(),
802
                    'PortfolioUpdated',
803
                    api_get_user_id(),
804
                    [],
805
                    null,
806
                    '',
807
                    '',
808
                    $itemSession ? $itemSession->getId() : 0
0 ignored issues
show
introduced by
$itemSession is of type Chamilo\CoreBundle\Entity\Session, thus it always evaluated to true.
Loading history...
809
                );
810
            }
811
812
            $values = $form->exportValues();
813
            $currentTime = new DateTime(api_get_utc_datetime(), new DateTimeZone('UTC'));
814
815
            $item
816
                ->setTitle($values['title'])
817
                ->setContent($values['content'])
818
                ->setUpdateDate($currentTime)
819
                ->setCategory(
820
                    $this->em->find('ChamiloCoreBundle:PortfolioCategory', $values['category'])
821
                );
822
823
            $values['item_id'] = $item->getId();
824
825
            $extraFieldValue = new ExtraFieldValue('portfolio');
826
            $extraFieldValue->saveFieldValues($values);
827
828
            $this->em->persist($item);
829
            $this->em->flush();
830
831
            $this->processAttachments(
832
                $form,
833
                $item->getUser(),
834
                $item->getId(),
835
                PortfolioAttachment::TYPE_ITEM
836
            );
837
838
            Display::addFlash(
839
                Display::return_message(get_lang('ItemUpdated'), 'success')
840
            );
841
842
            header("Location: $this->baseUrl");
843
            exit;
844
        }
845
846
        $interbreadcrumb[] = [
847
            'name' => get_lang('Portfolio'),
848
            'url' => $this->baseUrl,
849
        ];
850
        $actions = [];
851
        $actions[] = Display::url(
852
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
853
            $this->baseUrl
854
        );
855
        $actions[] = '<a id="hide_bar_template" href="#" role="button">'.
856
            Display::return_icon('expand.png', get_lang('Expand'), ['id' => 'expand'], ICON_SIZE_MEDIUM).
857
            Display::return_icon('contract.png', get_lang('Collapse'), ['id' => 'contract', 'class' => 'hide'], ICON_SIZE_MEDIUM).'</a>';
858
859
        $js = '<script>
860
            $(function() {
861
                $(".scrollbar-light").scrollbar();
862
                $(".scroll-wrapper").css("height", "550px");
863
                expandColumnToogle("#hide_bar_template", {
864
                    selector: "#template_col",
865
                    width: 3
866
                }, {
867
                    selector: "#doc_form",
868
                    width: 9
869
                });
870
                CKEDITOR.on("instanceReady", function (e) {
871
                    showTemplates();
872
                });
873
                $(window).on("load", function () {
874
                    $("input[name=\'title\']").focus();
875
                });
876
                '.$extra['jquery_ready_content'].'
877
            });
878
        </script>';
879
        $content = '<div class="page-create">
880
            <div class="row" style="overflow:hidden">
881
            <div id="template_col" class="col-md-3">
882
                <div class="panel panel-default">
883
                <div class="panel-body">
884
                    <div id="frmModel" class="items-templates scrollbar-light"></div>
885
                </div>
886
                </div>
887
            </div>
888
            <div id="doc_form" class="col-md-9">
889
                '.$form->returnForm().'
890
            </div>
891
          </div></div>';
892
893
        $this->renderView(
894
            $content.$js,
895
            get_lang('EditPortfolioItem'),
896
            $actions
897
        );
898
    }
899
900
    /**
901
     * @throws \Doctrine\ORM\ORMException
902
     * @throws \Doctrine\ORM\OptimisticLockException
903
     */
904
    public function showHideItem(Portfolio $item)
905
    {
906
        if (!$this->itemBelongToOwner($item)) {
907
            api_not_allowed(true);
908
        }
909
910
        switch ($item->getVisibility()) {
911
            case Portfolio::VISIBILITY_HIDDEN:
912
                $item->setVisibility(Portfolio::VISIBILITY_VISIBLE);
913
                break;
914
            case Portfolio::VISIBILITY_VISIBLE:
915
                $item->setVisibility(Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER);
916
                break;
917
            case Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER:
918
            default:
919
                $item->setVisibility(Portfolio::VISIBILITY_HIDDEN);
920
                break;
921
        }
922
923
        $this->em->persist($item);
924
        $this->em->flush();
925
926
        Display::addFlash(
927
            Display::return_message(get_lang('VisibilityChanged'), 'success')
928
        );
929
930
        header("Location: $this->baseUrl");
931
        exit;
932
    }
933
934
    /**
935
     * @throws \Doctrine\ORM\ORMException
936
     * @throws \Doctrine\ORM\OptimisticLockException
937
     */
938
    public function deleteItem(Portfolio $item)
939
    {
940
        if (!$this->itemBelongToOwner($item)) {
941
            api_not_allowed(true);
942
        }
943
944
        $this->em->remove($item);
945
        $this->em->flush();
946
947
        Display::addFlash(
948
            Display::return_message(get_lang('ItemDeleted'), 'success')
949
        );
950
951
        header("Location: $this->baseUrl");
952
        exit;
953
    }
954
955
    /**
956
     * @throws \Exception
957
     */
958
    public function index(HttpRequest $httpRequest)
959
    {
960
        $listByUser = false;
961
        $listHighlighted = $httpRequest->query->has('list_highlighted');
962
963
        if ($httpRequest->query->has('user')) {
964
            $this->owner = api_get_user_entity($httpRequest->query->getInt('user'));
965
966
            if (empty($this->owner)) {
967
                api_not_allowed(true);
968
            }
969
970
            $listByUser = true;
971
        }
972
973
        $currentUserId = api_get_user_id();
974
975
        $actions = [];
976
977
        if (api_is_platform_admin()) {
978
            $actions[] = Display::url(
979
                Display::return_icon('add.png', get_lang('Add'), [], ICON_SIZE_MEDIUM),
980
                $this->baseUrl.'action=add_item'
981
            );
982
            $actions[] = Display::url(
983
                Display::return_icon('folder.png', get_lang('AddCategory'), [], ICON_SIZE_MEDIUM),
984
                $this->baseUrl.'action=list_categories'
985
            );
986
            $actions[] = Display::url(
987
                Display::return_icon('waiting_list.png', get_lang('PortfolioDetails'), [], ICON_SIZE_MEDIUM),
988
                $this->baseUrl.'action=details'
989
            );
990
        } else {
991
            if ($currentUserId == $this->owner->getId()) {
992
                $actions[] = Display::url(
993
                    Display::return_icon('add.png', get_lang('Add'), [], ICON_SIZE_MEDIUM),
994
                    $this->baseUrl.'action=add_item'
995
                );
996
                $actions[] = Display::url(
997
                    Display::return_icon('waiting_list.png', get_lang('PortfolioDetails'), [], ICON_SIZE_MEDIUM),
998
                    $this->baseUrl.'action=details'
999
                );
1000
            } else {
1001
                $actions[] = Display::url(
1002
                    Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
1003
                    $this->baseUrl
1004
                );
1005
            }
1006
        }
1007
1008
        if (api_is_allowed_to_edit()) {
1009
            $actions[] = Display::url(
1010
                Display::return_icon('tickets.png', get_lang('Tags'), [], ICON_SIZE_MEDIUM),
1011
                $this->baseUrl.'action=tags'
1012
            );
1013
        }
1014
1015
        $frmStudentList = null;
1016
        $frmTagList = null;
1017
1018
        $categories = [];
1019
        $portfolio = [];
1020
        if ($this->course) {
1021
            $frmTagList = $this->createFormTagFilter($listByUser);
1022
            $frmStudentList = $this->createFormStudentFilter($listByUser, $listHighlighted);
1023
            $frmStudentList->setDefaults(['user' => $this->owner->getId()]);
1024
            // it translates the category title with the current user language
1025
            $categories = $this->getCategoriesForIndex(null, 0);
1026
            if (count($categories) > 0) {
1027
                foreach ($categories as &$category) {
1028
                    $translated = $this->translateDisplayName($category->getTitle());
1029
                    $category->setTitle($translated);
1030
                }
1031
            }
1032
        } else {
1033
            // it displays the list in Network Social for the current user
1034
            $portfolio = $this->getCategoriesForIndex();
1035
        }
1036
1037
        $foundComments = [];
1038
1039
        if ($listHighlighted) {
1040
            $items = $this->getHighlightedItems();
1041
        } else {
1042
            $items = $this->getItemsForIndex($listByUser, $frmTagList);
1043
1044
            $foundComments = $this->getCommentsForIndex($frmTagList);
1045
        }
1046
1047
        // it gets and translate the sub-categories
1048
        $categoryId = $httpRequest->query->getInt('categoryId');
1049
        $subCategoryIdsReq = isset($_REQUEST['subCategoryIds']) ? Security::remove_XSS($_REQUEST['subCategoryIds']) : '';
1050
        $subCategoryIds = $subCategoryIdsReq;
1051
        if ('all' !== $subCategoryIdsReq) {
1052
            $subCategoryIds = !empty($subCategoryIdsReq) ? explode(',', $subCategoryIdsReq) : [];
1053
        }
1054
        $subcategories = [];
1055
        if ($categoryId > 0) {
1056
            $subcategories = $this->getCategoriesForIndex(null, $categoryId);
1057
            if (count($subcategories) > 0) {
1058
                foreach ($subcategories as &$subcategory) {
1059
                    $translated = $this->translateDisplayName($subcategory->getTitle());
1060
                    $subcategory->setTitle($translated);
1061
                }
1062
            }
1063
        }
1064
1065
        $template = new Template(null, false, false, false, false, false, false);
1066
        $template->assign('user', $this->owner);
1067
        $template->assign('course', $this->course);
1068
        $template->assign('session', $this->session);
1069
        $template->assign('portfolio', $portfolio);
1070
        $template->assign('categories', $categories);
1071
        $template->assign('uncategorized_items', $items);
1072
        $template->assign('frm_student_list', $this->course ? $frmStudentList->returnForm() : '');
1073
        $template->assign('frm_tag_list', $this->course ? $frmTagList->returnForm() : '');
1074
        $template->assign('category_id', $categoryId);
1075
        $template->assign('subcategories', $subcategories);
1076
        $template->assign('subcategory_ids', $subCategoryIds);
1077
        $template->assign('found_comments', $foundComments);
1078
1079
        $js = '<script>
1080
            $(function() {
1081
                $(".category-filters").bind("click", function() {
1082
                    var categoryId = parseInt($(this).find("input[type=\'radio\']").val());
1083
                    $("input[name=\'categoryId\']").val(categoryId);
1084
                    $("input[name=\'subCategoryIds\']").val("all");
1085
                    $("#frm_tag_list_submit").trigger("click");
1086
                });
1087
                $(".subcategory-filters").bind("click", function() {
1088
                    var checkedVals = $(".subcategory-filters:checkbox:checked").map(function() {
1089
                        return this.value;
1090
                    }).get();
1091
                    $("input[name=\'subCategoryIds\']").val(checkedVals.join(","));
1092
                    $("#frm_tag_list_submit").trigger("click");
1093
                });
1094
            });
1095
        </script>';
1096
        $template->assign('js_script', $js);
1097
        $layout = $template->get_template('portfolio/list.html.twig');
1098
1099
        Display::addFlash(
1100
            Display::return_message(get_lang('PortfolioPostAddHelp'), 'info', false)
1101
        );
1102
1103
        $content = $template->fetch($layout);
1104
1105
        $this->renderView($content, get_lang('Portfolio'), $actions);
1106
    }
1107
1108
    /**
1109
     * @throws \Doctrine\ORM\ORMException
1110
     * @throws \Doctrine\ORM\OptimisticLockException
1111
     * @throws \Doctrine\ORM\TransactionRequiredException
1112
     */
1113
    public function view(Portfolio $item)
1114
    {
1115
        global $interbreadcrumb;
1116
1117
        if (!$this->itemBelongToOwner($item)) {
1118
            if ($item->getVisibility() === Portfolio::VISIBILITY_HIDDEN
1119
                || ($item->getVisibility() === Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER && !api_is_allowed_to_edit())
1120
            ) {
1121
                api_not_allowed(true);
1122
            }
1123
        }
1124
1125
        HookPortfolioItemViewed::create()
1126
            ->setEventData(['portfolio' => $item])
1127
            ->notifyItemViewed()
1128
        ;
1129
1130
        $itemCourse = $item->getCourse();
1131
        $itemSession = $item->getSession();
1132
1133
        $form = $this->createCommentForm($item);
1134
1135
        $commentsRepo = $this->em->getRepository(PortfolioComment::class);
1136
1137
        $query = $commentsRepo->createQueryBuilder('comment')
1138
            ->where('comment.item = :item')
1139
            ->orderBy('comment.root, comment.lft', 'ASC')
1140
            ->setParameter('item', $item)
1141
            ->getQuery();
1142
1143
        $clockIcon = Display::returnFontAwesomeIcon('clock-o', '', true);
1144
1145
        $commentsHtml = $commentsRepo->buildTree(
1146
            $query->getArrayResult(),
1147
            [
1148
                'decorate' => true,
1149
                'rootOpen' => '<div class="media-list">',
1150
                'rootClose' => '</div>',
1151
                'childOpen' => function ($node) use ($commentsRepo) {
1152
                    /** @var PortfolioComment $comment */
1153
                    $comment = $commentsRepo->find($node['id']);
1154
                    $author = $comment->getAuthor();
1155
1156
                    $userPicture = UserManager::getUserPicture(
1157
                        $comment->getAuthor()->getId(),
1158
                        USER_IMAGE_SIZE_SMALL,
1159
                        null,
1160
                        [
1161
                            'picture_uri' => $author->getPictureUri(),
1162
                            'email' => $author->getEmail(),
1163
                        ]
1164
                    );
1165
1166
                    return '<article class="media" id="comment-'.$node['id'].'">
1167
                        <div class="media-left"><img class="media-object thumbnail" src="'.$userPicture.'" alt="'
1168
                        .$author->getCompleteName().'"></div>
1169
                        <div class="media-body">';
1170
                },
1171
                'childClose' => '</div></article>',
1172
                'nodeDecorator' => function ($node) use ($commentsRepo, $clockIcon, $item) {
1173
                    $commentActions = [];
1174
                    /** @var PortfolioComment $comment */
1175
                    $comment = $commentsRepo->find($node['id']);
1176
1177
                    if ($this->commentBelongsToOwner($comment)) {
1178
                        $commentActions[] = Display::url(
1179
                            Display::return_icon(
1180
                                $comment->isTemplate() ? 'wizard.png' : 'wizard_na.png',
1181
                                $comment->isTemplate() ? get_lang('RemoveAsTemplate') : get_lang('AddAsTemplate')
1182
                            ),
1183
                            $this->baseUrl.http_build_query(['action' => 'template_comment', 'id' => $comment->getId()])
1184
                        );
1185
                    }
1186
1187
                    $commentActions[] = Display::url(
1188
                        Display::return_icon('discuss.png', get_lang('ReplyToThisComment')),
1189
                        '#',
1190
                        [
1191
                            'data-comment' => htmlspecialchars(
1192
                                json_encode(['id' => $comment->getId()])
1193
                            ),
1194
                            'role' => 'button',
1195
                            'class' => 'btn-reply-to',
1196
                        ]
1197
                    );
1198
                    $commentActions[] = Display::url(
1199
                        Display::return_icon('copy.png', get_lang('CopyToMyPortfolio')),
1200
                        $this->baseUrl.http_build_query(
1201
                            [
1202
                                'action' => 'copy',
1203
                                'copy' => 'comment',
1204
                                'id' => $comment->getId(),
1205
                            ]
1206
                        )
1207
                    );
1208
1209
                    $isAllowedToEdit = api_is_allowed_to_edit();
1210
1211
                    if ($isAllowedToEdit) {
1212
                        $commentActions[] = Display::url(
1213
                            Display::return_icon('copy.png', get_lang('CopyToStudentPortfolio')),
1214
                            $this->baseUrl.http_build_query(
1215
                                [
1216
                                    'action' => 'teacher_copy',
1217
                                    'copy' => 'comment',
1218
                                    'id' => $comment->getId(),
1219
                                ]
1220
                            )
1221
                        );
1222
1223
                        if ($comment->isImportant()) {
1224
                            $commentActions[] = Display::url(
1225
                                Display::return_icon('drawing-pin.png', get_lang('UnmarkCommentAsImportant')),
1226
                                $this->baseUrl.http_build_query(
1227
                                    [
1228
                                        'action' => 'mark_important',
1229
                                        'item' => $item->getId(),
1230
                                        'id' => $comment->getId(),
1231
                                    ]
1232
                                )
1233
                            );
1234
                        } else {
1235
                            $commentActions[] = Display::url(
1236
                                Display::return_icon('drawing-pin.png', get_lang('MarkCommentAsImportant')),
1237
                                $this->baseUrl.http_build_query(
1238
                                    [
1239
                                        'action' => 'mark_important',
1240
                                        'item' => $item->getId(),
1241
                                        'id' => $comment->getId(),
1242
                                    ]
1243
                                )
1244
                            );
1245
                        }
1246
1247
                        if ($this->course && '1' === api_get_course_setting('qualify_portfolio_comment')) {
1248
                            $commentActions[] = Display::url(
1249
                                Display::return_icon('quiz.png', get_lang('QualifyThisPortfolioComment')),
1250
                                $this->baseUrl.http_build_query(
1251
                                    [
1252
                                        'action' => 'qualify',
1253
                                        'comment' => $comment->getId(),
1254
                                    ]
1255
                                )
1256
                            );
1257
                        }
1258
                    }
1259
1260
                    $nodeHtml = '<div class="pull-right">'.implode(PHP_EOL, $commentActions).'</div>'.PHP_EOL
1261
                        .'<footer class="media-heading h4">'.PHP_EOL
1262
                        .'<p>'.$comment->getAuthor()->getCompleteName().'</p>'.PHP_EOL;
1263
1264
                    if ($comment->isImportant()
1265
                        && ($this->itemBelongToOwner($comment->getItem()) || $isAllowedToEdit)
1266
                    ) {
1267
                        $nodeHtml .= '<span class="pull-right label label-warning origin-style">'
1268
                            .get_lang('CommentMarkedAsImportant')
1269
                            .'</span>'.PHP_EOL;
1270
                    }
1271
1272
                    $nodeHtml .= '<p class="small">'.$clockIcon.PHP_EOL
1273
                        .Display::dateToStringAgoAndLongDate($comment->getDate()).'</p>'.PHP_EOL;
1274
1275
                    $nodeHtml .= '</footer>'.PHP_EOL
1276
                        .Security::remove_XSS($comment->getContent()).PHP_EOL;
1277
1278
                    $nodeHtml .= $this->generateAttachmentList($comment);
1279
1280
                    return $nodeHtml;
1281
                },
1282
            ]
1283
        );
1284
1285
        $template = new Template(null, false, false, false, false, false, false);
1286
        $template->assign('baseurl', $this->baseUrl);
1287
        $template->assign('item', $item);
1288
        $template->assign('item_content', $this->generateItemContent($item));
1289
        $template->assign('comments', $commentsHtml);
1290
        $template->assign('form', $form);
1291
        $template->assign('attachment_list', $this->generateAttachmentList($item));
1292
1293
        if ($itemCourse) {
1294
            $propertyInfo = api_get_item_property_info(
1295
                $itemCourse->getId(),
1296
                TOOL_PORTFOLIO,
1297
                $item->getId(),
1298
                $itemSession ? $itemSession->getId() : 0
1299
            );
1300
1301
            if ($propertyInfo) {
1302
                $template->assign(
1303
                    'last_edit',
1304
                    [
1305
                        'date' => $propertyInfo['lastedit_date'],
1306
                        'user' => api_get_user_entity($propertyInfo['lastedit_user_id'])->getCompleteName(),
1307
                    ]
1308
                );
1309
            }
1310
        }
1311
1312
        $layout = $template->get_template('portfolio/view.html.twig');
1313
        $content = $template->fetch($layout);
1314
1315
        $interbreadcrumb[] = ['name' => get_lang('Portfolio'), 'url' => $this->baseUrl];
1316
1317
        $editLink = Display::url(
1318
            Display::return_icon('edit.png', get_lang('Edit'), [], ICON_SIZE_MEDIUM),
1319
            $this->baseUrl.http_build_query(['action' => 'edit_item', 'id' => $item->getId()])
1320
        );
1321
1322
        $actions = [];
1323
        $actions[] = Display::url(
1324
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
1325
            $this->baseUrl
1326
        );
1327
1328
        if ($this->itemBelongToOwner($item)) {
1329
            $actions[] = $editLink;
1330
1331
            $actions[] = Display::url(
1332
                Display::return_icon(
1333
                    $item->isTemplate() ? 'wizard.png' : 'wizard_na.png',
1334
                    $item->isTemplate() ? get_lang('RemoveAsTemplate') : get_lang('AddAsTemplate'),
1335
                    [],
1336
                    ICON_SIZE_MEDIUM
1337
                ),
1338
                $this->baseUrl.http_build_query(['action' => 'template', 'id' => $item->getId()])
1339
            );
1340
1341
            $visibilityUrl = $this->baseUrl.http_build_query(['action' => 'visibility', 'id' => $item->getId()]);
1342
1343
            if ($item->getVisibility() === Portfolio::VISIBILITY_HIDDEN) {
1344
                $actions[] = Display::url(
1345
                    Display::return_icon('invisible.png', get_lang('MakeVisible'), [], ICON_SIZE_MEDIUM),
1346
                    $visibilityUrl
1347
                );
1348
            } elseif ($item->getVisibility() === Portfolio::VISIBILITY_VISIBLE) {
1349
                $actions[] = Display::url(
1350
                    Display::return_icon('visible.png', get_lang('MakeVisibleForTeachers'), [], ICON_SIZE_MEDIUM),
1351
                    $visibilityUrl
1352
                );
1353
            } elseif ($item->getVisibility() === Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER) {
1354
                $actions[] = Display::url(
1355
                    Display::return_icon('eye-slash.png', get_lang('MakeInvisible'), [], ICON_SIZE_MEDIUM),
1356
                    $visibilityUrl
1357
                );
1358
            }
1359
1360
            $actions[] = Display::url(
1361
                Display::return_icon('delete.png', get_lang('Delete'), [], ICON_SIZE_MEDIUM),
1362
                $this->baseUrl.http_build_query(['action' => 'delete_item', 'id' => $item->getId()])
1363
            );
1364
        } else {
1365
            $actions[] = Display::url(
1366
                Display::return_icon('copy.png', get_lang('CopyToMyPortfolio'), [], ICON_SIZE_MEDIUM),
1367
                $this->baseUrl.http_build_query(['action' => 'copy', 'id' => $item->getId()])
1368
            );
1369
        }
1370
1371
        if (api_is_allowed_to_edit()) {
1372
            $actions[] = Display::url(
1373
                Display::return_icon('copy.png', get_lang('CopyToStudentPortfolio'), [], ICON_SIZE_MEDIUM),
1374
                $this->baseUrl.http_build_query(['action' => 'teacher_copy', 'copy' => 'item', 'id' => $item->getId()])
1375
            );
1376
            $actions[] = $editLink;
1377
1378
            $highlightedUrl = $this->baseUrl.http_build_query(['action' => 'highlighted', 'id' => $item->getId()]);
1379
1380
            if ($item->isHighlighted()) {
1381
                $actions[] = Display::url(
1382
                    Display::return_icon('award_red.png', get_lang('UnmarkAsHighlighted'), [], ICON_SIZE_MEDIUM),
1383
                    $highlightedUrl
1384
                );
1385
            } else {
1386
                $actions[] = Display::url(
1387
                    Display::return_icon('award_red_na.png', get_lang('MarkAsHighlighted'), [], ICON_SIZE_MEDIUM),
1388
                    $highlightedUrl
1389
                );
1390
            }
1391
1392
            if ($itemCourse && '1' === api_get_course_setting('qualify_portfolio_item')) {
1393
                $actions[] = Display::url(
1394
                    Display::return_icon('quiz.png', get_lang('QualifyThisPortfolioItem'), [], ICON_SIZE_MEDIUM),
1395
                    $this->baseUrl.http_build_query(['action' => 'qualify', 'item' => $item->getId()])
1396
                );
1397
            }
1398
        }
1399
1400
        $this->renderView($content, $item->getTitle(true), $actions, false);
1401
    }
1402
1403
    /**
1404
     * @throws \Doctrine\ORM\ORMException
1405
     * @throws \Doctrine\ORM\OptimisticLockException
1406
     */
1407
    public function copyItem(Portfolio $originItem)
1408
    {
1409
        $currentTime = api_get_utc_datetime(null, false, true);
1410
1411
        $portfolio = new Portfolio();
1412
        $portfolio
1413
            ->setVisibility(Portfolio::VISIBILITY_HIDDEN)
1414
            ->setTitle(
1415
                sprintf(get_lang('PortfolioItemFromXUser'), $originItem->getUser()->getCompleteName())
1416
            )
1417
            ->setContent('')
1418
            ->setUser($this->owner)
1419
            ->setOrigin($originItem->getId())
1420
            ->setOriginType(Portfolio::TYPE_ITEM)
1421
            ->setCourse($this->course)
1422
            ->setSession($this->session)
1423
            ->setCreationDate($currentTime)
1424
            ->setUpdateDate($currentTime);
1425
1426
        $this->em->persist($portfolio);
1427
        $this->em->flush();
1428
1429
        Display::addFlash(
1430
            Display::return_message(get_lang('PortfolioItemAdded'), 'success')
1431
        );
1432
1433
        header("Location: $this->baseUrl".http_build_query(['action' => 'edit_item', 'id' => $portfolio->getId()]));
1434
        exit;
1435
    }
1436
1437
    /**
1438
     * @throws \Doctrine\ORM\ORMException
1439
     * @throws \Doctrine\ORM\OptimisticLockException
1440
     */
1441
    public function copyComment(PortfolioComment $originComment)
1442
    {
1443
        $currentTime = api_get_utc_datetime(null, false, true);
1444
1445
        $portfolio = new Portfolio();
1446
        $portfolio
1447
            ->setVisibility(Portfolio::VISIBILITY_HIDDEN)
1448
            ->setTitle(
1449
                sprintf(get_lang('PortfolioCommentFromXUser'), $originComment->getAuthor()->getCompleteName())
1450
            )
1451
            ->setContent('')
1452
            ->setUser($this->owner)
1453
            ->setOrigin($originComment->getId())
1454
            ->setOriginType(Portfolio::TYPE_COMMENT)
1455
            ->setCourse($this->course)
1456
            ->setSession($this->session)
1457
            ->setCreationDate($currentTime)
1458
            ->setUpdateDate($currentTime);
1459
1460
        $this->em->persist($portfolio);
1461
        $this->em->flush();
1462
1463
        Display::addFlash(
1464
            Display::return_message(get_lang('PortfolioItemAdded'), 'success')
1465
        );
1466
1467
        header("Location: $this->baseUrl".http_build_query(['action' => 'edit_item', 'id' => $portfolio->getId()]));
1468
        exit;
1469
    }
1470
1471
    /**
1472
     * @throws \Doctrine\ORM\ORMException
1473
     * @throws \Doctrine\ORM\OptimisticLockException
1474
     * @throws \Exception
1475
     */
1476
    public function teacherCopyItem(Portfolio $originItem)
1477
    {
1478
        $actionParams = http_build_query(['action' => 'teacher_copy', 'copy' => 'item', 'id' => $originItem->getId()]);
1479
1480
        $form = new FormValidator('teacher_copy_portfolio', 'post', $this->baseUrl.$actionParams);
1481
1482
        if (api_get_configuration_value('save_titles_as_html')) {
1483
            $form->addHtmlEditor('title', get_lang('Title'), true, false, ['ToolbarSet' => 'TitleAsHtml']);
1484
        } else {
1485
            $form->addText('title', get_lang('Title'));
1486
            $form->applyFilter('title', 'trim');
1487
        }
1488
1489
        $form->addLabel(
1490
            sprintf(get_lang('PortfolioItemFromXUser'), $originItem->getUser()->getCompleteName()),
1491
            Display::panel(
1492
                Security::remove_XSS($originItem->getContent())
1493
            )
1494
        );
1495
        $form->addHtmlEditor('content', get_lang('Content'), true, false, ['ToolbarSet' => 'NotebookStudent']);
1496
1497
        $urlParams = http_build_query(
1498
            [
1499
                'a' => 'search_user_by_course',
1500
                'course_id' => $this->course->getId(),
1501
                'session_id' => $this->session ? $this->session->getId() : 0,
1502
            ]
1503
        );
1504
        $form->addSelectAjax(
1505
            'students',
1506
            get_lang('Students'),
1507
            [],
1508
            [
1509
                'url' => api_get_path(WEB_AJAX_PATH)."course.ajax.php?$urlParams",
1510
                'multiple' => true,
1511
            ]
1512
        );
1513
        $form->addRule('students', get_lang('ThisFieldIsRequired'), 'required');
1514
        $form->addButtonCreate(get_lang('Save'));
1515
1516
        $toolName = get_lang('CopyToStudentPortfolio');
1517
        $content = $form->returnForm();
1518
1519
        if ($form->validate()) {
1520
            $values = $form->exportValues();
1521
1522
            $currentTime = api_get_utc_datetime(null, false, true);
1523
1524
            foreach ($values['students'] as $studentId) {
1525
                $owner = api_get_user_entity($studentId);
1526
1527
                $portfolio = new Portfolio();
1528
                $portfolio
1529
                    ->setVisibility(Portfolio::VISIBILITY_HIDDEN)
1530
                    ->setTitle($values['title'])
1531
                    ->setContent($values['content'])
1532
                    ->setUser($owner)
1533
                    ->setOrigin($originItem->getId())
1534
                    ->setOriginType(Portfolio::TYPE_ITEM)
1535
                    ->setCourse($this->course)
1536
                    ->setSession($this->session)
1537
                    ->setCreationDate($currentTime)
1538
                    ->setUpdateDate($currentTime);
1539
1540
                $this->em->persist($portfolio);
1541
            }
1542
1543
            $this->em->flush();
1544
1545
            Display::addFlash(
1546
                Display::return_message(get_lang('PortfolioItemAddedToStudents'), 'success')
1547
            );
1548
1549
            header("Location: $this->baseUrl");
1550
            exit;
1551
        }
1552
1553
        $this->renderView($content, $toolName);
1554
    }
1555
1556
    /**
1557
     * @throws \Doctrine\ORM\ORMException
1558
     * @throws \Doctrine\ORM\OptimisticLockException
1559
     * @throws \Exception
1560
     */
1561
    public function teacherCopyComment(PortfolioComment $originComment)
1562
    {
1563
        $actionParams = http_build_query(
1564
            [
1565
                'action' => 'teacher_copy',
1566
                'copy' => 'comment',
1567
                'id' => $originComment->getId(),
1568
            ]
1569
        );
1570
1571
        $form = new FormValidator('teacher_copy_portfolio', 'post', $this->baseUrl.$actionParams);
1572
1573
        if (api_get_configuration_value('save_titles_as_html')) {
1574
            $form->addHtmlEditor('title', get_lang('Title'), true, false, ['ToolbarSet' => 'TitleAsHtml']);
1575
        } else {
1576
            $form->addText('title', get_lang('Title'));
1577
            $form->applyFilter('title', 'trim');
1578
        }
1579
1580
        $form->addLabel(
1581
            sprintf(get_lang('PortfolioCommentFromXUser'), $originComment->getAuthor()->getCompleteName()),
1582
            Display::panel(
1583
                Security::remove_XSS($originComment->getContent())
1584
            )
1585
        );
1586
        $form->addHtmlEditor('content', get_lang('Content'), true, false, ['ToolbarSet' => 'NotebookStudent']);
1587
1588
        $urlParams = http_build_query(
1589
            [
1590
                'a' => 'search_user_by_course',
1591
                'course_id' => $this->course->getId(),
1592
                'session_id' => $this->session ? $this->session->getId() : 0,
1593
            ]
1594
        );
1595
        $form->addSelectAjax(
1596
            'students',
1597
            get_lang('Students'),
1598
            [],
1599
            [
1600
                'url' => api_get_path(WEB_AJAX_PATH)."course.ajax.php?$urlParams",
1601
                'multiple' => true,
1602
            ]
1603
        );
1604
        $form->addRule('students', get_lang('ThisFieldIsRequired'), 'required');
1605
        $form->addButtonCreate(get_lang('Save'));
1606
1607
        $toolName = get_lang('CopyToStudentPortfolio');
1608
        $content = $form->returnForm();
1609
1610
        if ($form->validate()) {
1611
            $values = $form->exportValues();
1612
1613
            $currentTime = api_get_utc_datetime(null, false, true);
1614
1615
            foreach ($values['students'] as $studentId) {
1616
                $owner = api_get_user_entity($studentId);
1617
1618
                $portfolio = new Portfolio();
1619
                $portfolio
1620
                    ->setVisibility(Portfolio::VISIBILITY_HIDDEN)
1621
                    ->setTitle($values['title'])
1622
                    ->setContent($values['content'])
1623
                    ->setUser($owner)
1624
                    ->setOrigin($originComment->getId())
1625
                    ->setOriginType(Portfolio::TYPE_COMMENT)
1626
                    ->setCourse($this->course)
1627
                    ->setSession($this->session)
1628
                    ->setCreationDate($currentTime)
1629
                    ->setUpdateDate($currentTime);
1630
1631
                $this->em->persist($portfolio);
1632
            }
1633
1634
            $this->em->flush();
1635
1636
            Display::addFlash(
1637
                Display::return_message(get_lang('PortfolioItemAddedToStudents'), 'success')
1638
            );
1639
1640
            header("Location: $this->baseUrl");
1641
            exit;
1642
        }
1643
1644
        $this->renderView($content, $toolName);
1645
    }
1646
1647
    /**
1648
     * @throws \Doctrine\ORM\ORMException
1649
     * @throws \Doctrine\ORM\OptimisticLockException
1650
     */
1651
    public function markImportantCommentInItem(Portfolio $item, PortfolioComment $comment)
1652
    {
1653
        if ($comment->getItem()->getId() !== $item->getId()) {
1654
            api_not_allowed(true);
1655
        }
1656
1657
        $comment->setIsImportant(
1658
            !$comment->isImportant()
1659
        );
1660
1661
        $this->em->persist($comment);
1662
        $this->em->flush();
1663
1664
        Display::addFlash(
1665
            Display::return_message(get_lang('CommentMarkedAsImportant'), 'success')
1666
        );
1667
1668
        header("Location: $this->baseUrl".http_build_query(['action' => 'view', 'id' => $item->getId()]));
1669
        exit;
1670
    }
1671
1672
    /**
1673
     * @throws \Exception
1674
     */
1675
    public function details(HttpRequest $httpRequest)
1676
    {
1677
        $currentUserId = api_get_user_id();
1678
        $isAllowedToFilterStudent = $this->course && api_is_allowed_to_edit();
1679
1680
        $actions = [];
1681
        $actions[] = Display::url(
1682
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
1683
            $this->baseUrl
1684
        );
1685
        $actions[] = Display::url(
1686
            Display::return_icon('pdf.png', get_lang('ExportMyPortfolioDataPdf'), [], ICON_SIZE_MEDIUM),
1687
            $this->baseUrl.http_build_query(['action' => 'export_pdf'])
1688
        );
1689
        $actions[] = Display::url(
1690
            Display::return_icon('save_pack.png', get_lang('ExportMyPortfolioDataZip'), [], ICON_SIZE_MEDIUM),
1691
            $this->baseUrl.http_build_query(['action' => 'export_zip'])
1692
        );
1693
1694
        $frmStudent = null;
1695
1696
        if ($isAllowedToFilterStudent) {
1697
            if ($httpRequest->query->has('user')) {
1698
                $this->owner = api_get_user_entity($httpRequest->query->getInt('user'));
1699
1700
                if (empty($this->owner)) {
1701
                    api_not_allowed(true);
1702
                }
1703
1704
                $actions[1] = Display::url(
1705
                    Display::return_icon('pdf.png', get_lang('ExportMyPortfolioDataPdf'), [], ICON_SIZE_MEDIUM),
1706
                    $this->baseUrl.http_build_query(['action' => 'export_pdf', 'user' => $this->owner->getId()])
1707
                );
1708
                $actions[2] = Display::url(
1709
                    Display::return_icon('save_pack.png', get_lang('ExportMyPortfolioDataZip'), [], ICON_SIZE_MEDIUM),
1710
                    $this->baseUrl.http_build_query(['action' => 'export_zip', 'user' => $this->owner->getId()])
1711
                );
1712
            }
1713
1714
            $frmStudent = new FormValidator('frm_student_list', 'get');
1715
1716
            $urlParams = http_build_query(
1717
                [
1718
                    'a' => 'search_user_by_course',
1719
                    'course_id' => $this->course->getId(),
1720
                    'session_id' => $this->session ? $this->session->getId() : 0,
1721
                ]
1722
            );
1723
1724
            $frmStudent
1725
                ->addSelectAjax(
1726
                    'user',
1727
                    get_lang('SelectLearnerPortfolio'),
1728
                    [],
1729
                    [
1730
                        'url' => api_get_path(WEB_AJAX_PATH)."course.ajax.php?$urlParams",
1731
                        'placeholder' => get_lang('SearchStudent'),
1732
                        'formatResult' => SelectAjax::templateResultForUsersInCourse(),
1733
                        'formatSelection' => SelectAjax::templateSelectionForUsersInCourse(),
1734
                    ]
1735
                )
1736
                ->addOption(
1737
                    $this->owner->getCompleteName(),
1738
                    $this->owner->getId(),
1739
                    [
1740
                        'data-avatarurl' => UserManager::getUserPicture($this->owner->getId()),
1741
                        'data-username' => $this->owner->getUsername(),
1742
                    ]
1743
                )
1744
            ;
1745
            $frmStudent->setDefaults(['user' => $this->owner->getId()]);
1746
            $frmStudent->addHidden('action', 'details');
1747
            $frmStudent->addHidden('cidReq', $this->course->getCode());
1748
            $frmStudent->addHidden('id_session', $this->session ? $this->session->getId() : 0);
1749
            $frmStudent->addButtonFilter(get_lang('Filter'));
1750
        }
1751
1752
        $itemsRepo = $this->em->getRepository(Portfolio::class);
1753
        $commentsRepo = $this->em->getRepository(PortfolioComment::class);
1754
1755
        $getItemsTotalNumber = function () use ($itemsRepo, $isAllowedToFilterStudent, $currentUserId) {
1756
            $qb = $itemsRepo->createQueryBuilder('i');
1757
            $qb
1758
                ->select('COUNT(i)')
1759
                ->where('i.user = :user')
1760
                ->setParameter('user', $this->owner);
1761
1762
            if ($this->course) {
1763
                $qb
1764
                    ->andWhere('i.course = :course')
1765
                    ->setParameter('course', $this->course);
1766
1767
                if ($this->session) {
1768
                    $qb
1769
                        ->andWhere('i.session = :session')
1770
                        ->setParameter('session', $this->session);
1771
                } else {
1772
                    $qb->andWhere('i.session IS NULL');
1773
                }
1774
            }
1775
1776
            if ($isAllowedToFilterStudent && $currentUserId !== $this->owner->getId()) {
1777
                $visibilityCriteria = [
1778
                    Portfolio::VISIBILITY_VISIBLE,
1779
                    Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER,
1780
                ];
1781
1782
                $qb->andWhere(
1783
                    $qb->expr()->in('i.visibility', $visibilityCriteria)
1784
                );
1785
            }
1786
1787
            return $qb->getQuery()->getSingleScalarResult();
1788
        };
1789
        $getItemsData = function ($from, $limit, $columnNo, $orderDirection) use ($itemsRepo, $isAllowedToFilterStudent, $currentUserId) {
1790
            $qb = $itemsRepo->createQueryBuilder('item')
1791
                ->where('item.user = :user')
1792
                ->leftJoin('item.category', 'category')
1793
                ->leftJoin('item.course', 'course')
1794
                ->leftJoin('item.session', 'session')
1795
                ->setParameter('user', $this->owner);
1796
1797
            if ($this->course) {
1798
                $qb
1799
                    ->andWhere('item.course = :course_id')
1800
                    ->setParameter('course_id', $this->course);
1801
1802
                if ($this->session) {
1803
                    $qb
1804
                        ->andWhere('item.session = :session')
1805
                        ->setParameter('session', $this->session);
1806
                } else {
1807
                    $qb->andWhere('item.session IS NULL');
1808
                }
1809
            }
1810
1811
            if ($isAllowedToFilterStudent && $currentUserId !== $this->owner->getId()) {
1812
                $visibilityCriteria = [
1813
                    Portfolio::VISIBILITY_VISIBLE,
1814
                    Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER,
1815
                ];
1816
1817
                $qb->andWhere(
1818
                    $qb->expr()->in('item.visibility', $visibilityCriteria)
1819
                );
1820
            }
1821
1822
            if (0 == $columnNo) {
1823
                $qb->orderBy('item.title', $orderDirection);
1824
            } elseif (1 == $columnNo) {
1825
                $qb->orderBy('item.creationDate', $orderDirection);
1826
            } elseif (2 == $columnNo) {
1827
                $qb->orderBy('item.updateDate', $orderDirection);
1828
            } elseif (3 == $columnNo) {
1829
                $qb->orderBy('category.title', $orderDirection);
1830
            } elseif (5 == $columnNo) {
1831
                $qb->orderBy('item.score', $orderDirection);
1832
            } elseif (6 == $columnNo) {
1833
                $qb->orderBy('course.title', $orderDirection);
1834
            } elseif (7 == $columnNo) {
1835
                $qb->orderBy('session.name', $orderDirection);
1836
            }
1837
1838
            $qb->setFirstResult($from)->setMaxResults($limit);
1839
1840
            return array_map(
1841
                function (Portfolio $item) {
1842
                    $category = $item->getCategory();
1843
1844
                    $row = [];
1845
                    $row[] = $item;
1846
                    $row[] = $item->getCreationDate();
1847
                    $row[] = $item->getUpdateDate();
1848
                    $row[] = $category ? $item->getCategory()->getTitle() : null;
0 ignored issues
show
introduced by
$category is of type Chamilo\CoreBundle\Entity\PortfolioCategory, thus it always evaluated to true.
Loading history...
1849
                    $row[] = $item->getComments()->count();
1850
                    $row[] = $item->getScore();
1851
1852
                    if (!$this->course) {
1853
                        $row[] = $item->getCourse();
1854
                        $row[] = $item->getSession();
1855
                    }
1856
1857
                    return $row;
1858
                },
1859
                $qb->getQuery()->getResult()
1860
            );
1861
        };
1862
1863
        $portfolioItemColumnFilter = function (Portfolio $item) {
1864
            return Display::url(
1865
                $item->getTitle(true),
1866
                $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()])
1867
            );
1868
        };
1869
        $convertFormatDateColumnFilter = function (DateTime $date) {
1870
            return api_convert_and_format_date($date);
1871
        };
1872
1873
        $tblItems = new SortableTable('tbl_items', $getItemsTotalNumber, $getItemsData, 1, 20, 'DESC');
1874
        $tblItems->set_additional_parameters(['action' => 'details', 'user' => $this->owner->getId()]);
1875
        $tblItems->set_header(0, get_lang('Title'));
1876
        $tblItems->set_column_filter(0, $portfolioItemColumnFilter);
1877
        $tblItems->set_header(1, get_lang('CreationDate'), true, [], ['class' => 'text-center']);
1878
        $tblItems->set_column_filter(1, $convertFormatDateColumnFilter);
1879
        $tblItems->set_header(2, get_lang('LastUpdate'), true, [], ['class' => 'text-center']);
1880
        $tblItems->set_column_filter(2, $convertFormatDateColumnFilter);
1881
        $tblItems->set_header(3, get_lang('Category'));
1882
        $tblItems->set_header(4, get_lang('Comments'), false, [], ['class' => 'text-right']);
1883
        $tblItems->set_header(5, get_lang('Score'), true, [], ['class' => 'text-right']);
1884
1885
        if (!$this->course) {
1886
            $tblItems->set_header(6, get_lang('Course'));
1887
            $tblItems->set_header(7, get_lang('Session'));
1888
        }
1889
1890
        $getCommentsTotalNumber = function () use ($commentsRepo) {
1891
            $qb = $commentsRepo->createQueryBuilder('c');
1892
            $qb
1893
                ->select('COUNT(c)')
1894
                ->where('c.author = :author')
1895
                ->setParameter('author', $this->owner);
1896
1897
            if ($this->course) {
1898
                $qb
1899
                    ->innerJoin('c.item', 'i')
1900
                    ->andWhere('i.course = :course')
1901
                    ->setParameter('course', $this->course);
1902
1903
                if ($this->session) {
1904
                    $qb
1905
                        ->andWhere('i.session = :session')
1906
                        ->setParameter('session', $this->session);
1907
                } else {
1908
                    $qb->andWhere('i.session IS NULL');
1909
                }
1910
            }
1911
1912
            return $qb->getQuery()->getSingleScalarResult();
1913
        };
1914
        $getCommentsData = function ($from, $limit, $columnNo, $orderDirection) use ($commentsRepo) {
1915
            $qb = $commentsRepo->createQueryBuilder('comment');
1916
            $qb
1917
                ->where('comment.author = :user')
1918
                ->innerJoin('comment.item', 'item')
1919
                ->setParameter('user', $this->owner);
1920
1921
            if ($this->course) {
1922
                $qb
1923
                    ->innerJoin('comment.item', 'i')
1924
                    ->andWhere('item.course = :course')
1925
                    ->setParameter('course', $this->course);
1926
1927
                if ($this->session) {
1928
                    $qb
1929
                        ->andWhere('item.session = :session')
1930
                        ->setParameter('session', $this->session);
1931
                } else {
1932
                    $qb->andWhere('item.session IS NULL');
1933
                }
1934
            }
1935
1936
            if (0 == $columnNo) {
1937
                $qb->orderBy('comment.content', $orderDirection);
1938
            } elseif (1 == $columnNo) {
1939
                $qb->orderBy('comment.date', $orderDirection);
1940
            } elseif (2 == $columnNo) {
1941
                $qb->orderBy('item.title', $orderDirection);
1942
            } elseif (3 == $columnNo) {
1943
                $qb->orderBy('comment.score', $orderDirection);
1944
            }
1945
1946
            $qb->setFirstResult($from)->setMaxResults($limit);
1947
1948
            return array_map(
1949
                function (PortfolioComment $comment) {
1950
                    return [
1951
                        $comment,
1952
                        $comment->getDate(),
1953
                        $comment->getItem(),
1954
                        $comment->getScore(),
1955
                    ];
1956
                },
1957
                $qb->getQuery()->getResult()
1958
            );
1959
        };
1960
1961
        $tblComments = new SortableTable('tbl_comments', $getCommentsTotalNumber, $getCommentsData, 1, 20, 'DESC');
1962
        $tblComments->set_additional_parameters(['action' => 'details', 'user' => $this->owner->getId()]);
1963
        $tblComments->set_header(0, get_lang('Resume'));
1964
        $tblComments->set_column_filter(
1965
            0,
1966
            function (PortfolioComment $comment) {
1967
                return Display::url(
1968
                    $comment->getExcerpt(),
1969
                    $this->baseUrl.http_build_query(['action' => 'view', 'id' => $comment->getItem()->getId()])
1970
                    .'#comment-'.$comment->getId()
1971
                );
1972
            }
1973
        );
1974
        $tblComments->set_header(1, get_lang('Date'), true, [], ['class' => 'text-center']);
1975
        $tblComments->set_column_filter(1, $convertFormatDateColumnFilter);
1976
        $tblComments->set_header(2, get_lang('PortfolioItemTitle'));
1977
        $tblComments->set_column_filter(2, $portfolioItemColumnFilter);
1978
        $tblComments->set_header(3, get_lang('Score'), true, [], ['class' => 'text-right']);
1979
1980
        $content = '';
1981
1982
        if ($frmStudent) {
1983
            $content .= $frmStudent->returnForm();
1984
        }
1985
1986
        $totalNumberOfItems = $tblItems->get_total_number_of_items();
1987
        $totalNumberOfComments = $tblComments->get_total_number_of_items();
1988
        $requiredNumberOfItems = (int) api_get_course_setting('portfolio_number_items');
1989
        $requiredNumberOfComments = (int) api_get_course_setting('portfolio_number_comments');
1990
1991
        $itemsSubtitle = '';
1992
1993
        if ($requiredNumberOfItems > 0) {
1994
            $itemsSubtitle = sprintf(
1995
                get_lang('XAddedYRequired'),
1996
                $totalNumberOfItems,
1997
                $requiredNumberOfItems
1998
            );
1999
        }
2000
2001
        $content .= Display::page_subheader2(
2002
            get_lang('PortfolioItems'),
2003
            $itemsSubtitle
2004
        ).PHP_EOL;
2005
2006
        if ($totalNumberOfItems > 0) {
2007
            $content .= $tblItems->return_table().PHP_EOL;
2008
        } else {
2009
            $content .= Display::return_message(get_lang('NoItemsInYourPortfolio'), 'warning');
2010
        }
2011
2012
        $commentsSubtitle = '';
2013
2014
        if ($requiredNumberOfComments > 0) {
2015
            $commentsSubtitle = sprintf(
2016
                get_lang('XAddedYRequired'),
2017
                $totalNumberOfComments,
2018
                $requiredNumberOfComments
2019
            );
2020
        }
2021
2022
        $content .= Display::page_subheader2(
2023
            get_lang('PortfolioCommentsMade'),
2024
            $commentsSubtitle
2025
        ).PHP_EOL;
2026
2027
        if ($totalNumberOfComments > 0) {
2028
            $content .= $tblComments->return_table().PHP_EOL;
2029
        } else {
2030
            $content .= Display::return_message(get_lang('YouHaveNotCommented'), 'warning');
2031
        }
2032
2033
        $this->renderView($content, get_lang('PortfolioDetails'), $actions);
2034
    }
2035
2036
    /**
2037
     * @throws MpdfException
2038
     */
2039
    public function exportPdf(HttpRequest $httpRequest)
2040
    {
2041
        $currentUserId = api_get_user_id();
2042
        $isAllowedToFilterStudent = $this->course && api_is_allowed_to_edit();
2043
2044
        if ($isAllowedToFilterStudent) {
2045
            if ($httpRequest->query->has('user')) {
2046
                $this->owner = api_get_user_entity($httpRequest->query->getInt('user'));
2047
2048
                if (empty($this->owner)) {
2049
                    api_not_allowed(true);
2050
                }
2051
            }
2052
        }
2053
2054
        $pdfContent = Display::page_header($this->owner->getCompleteName());
2055
2056
        if ($this->course) {
2057
            $pdfContent .= '<p>'.get_lang('Course').': ';
2058
2059
            if ($this->session) {
2060
                $pdfContent .= $this->session->getName().' ('.$this->course->getTitle().')';
2061
            } else {
2062
                $pdfContent .= $this->course->getTitle();
2063
            }
2064
2065
            $pdfContent .= '</p>';
2066
        }
2067
2068
        $visibility = [];
2069
2070
        if ($isAllowedToFilterStudent && $currentUserId !== $this->owner->getId()) {
2071
            $visibility[] = Portfolio::VISIBILITY_VISIBLE;
2072
            $visibility[] = Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER;
2073
        }
2074
2075
        $items = $this->em
2076
            ->getRepository(Portfolio::class)
2077
            ->findItemsByUser(
2078
                $this->owner,
2079
                $this->course,
2080
                $this->session,
2081
                null,
2082
                $visibility
2083
            );
2084
        $comments = $this->em
2085
            ->getRepository(PortfolioComment::class)
2086
            ->findCommentsByUser($this->owner, $this->course, $this->session);
2087
2088
        $itemsHtml = $this->getItemsInHtmlFormatted($items);
2089
        $commentsHtml = $this->getCommentsInHtmlFormatted($comments);
2090
2091
        $totalNumberOfItems = count($itemsHtml);
2092
        $totalNumberOfComments = count($commentsHtml);
2093
        $requiredNumberOfItems = (int) api_get_course_setting('portfolio_number_items');
2094
        $requiredNumberOfComments = (int) api_get_course_setting('portfolio_number_comments');
2095
2096
        $itemsSubtitle = '';
2097
        $commentsSubtitle = '';
2098
2099
        if ($requiredNumberOfItems > 0) {
2100
            $itemsSubtitle = sprintf(
2101
                get_lang('XAddedYRequired'),
2102
                $totalNumberOfItems,
2103
                $requiredNumberOfItems
2104
            );
2105
        }
2106
2107
        if ($requiredNumberOfComments > 0) {
2108
            $commentsSubtitle = sprintf(
2109
                get_lang('XAddedYRequired'),
2110
                $totalNumberOfComments,
2111
                $requiredNumberOfComments
2112
            );
2113
        }
2114
2115
        $pdfContent .= Display::page_subheader2(
2116
            get_lang('PortfolioItems'),
2117
            $itemsSubtitle
2118
        );
2119
2120
        if ($totalNumberOfItems > 0) {
2121
            $pdfContent .= implode(PHP_EOL, $itemsHtml);
2122
        } else {
2123
            $pdfContent .= Display::return_message(get_lang('NoItemsInYourPortfolio'), 'warning');
2124
        }
2125
2126
        $pdfContent .= Display::page_subheader2(
2127
            get_lang('PortfolioCommentsMade'),
2128
            $commentsSubtitle
2129
        );
2130
2131
        if ($totalNumberOfComments > 0) {
2132
            $pdfContent .= implode(PHP_EOL, $commentsHtml);
2133
        } else {
2134
            $pdfContent .= Display::return_message(get_lang('YouHaveNotCommented'), 'warning');
2135
        }
2136
2137
        $pdfName = $this->owner->getCompleteName()
2138
            .($this->course ? '_'.$this->course->getCode() : '')
2139
            .'_'.get_lang('Portfolio');
2140
2141
        $pdf = new PDF();
2142
        $pdf->content_to_pdf(
2143
            $pdfContent,
2144
            null,
2145
            $pdfName,
2146
            $this->course ? $this->course->getCode() : null,
2147
            'D',
2148
            false,
2149
            null,
2150
            false,
2151
            true
2152
        );
2153
    }
2154
2155
    public function exportZip(HttpRequest $httpRequest)
2156
    {
2157
        $currentUserId = api_get_user_id();
2158
        $isAllowedToFilterStudent = $this->course && api_is_allowed_to_edit();
2159
2160
        if ($isAllowedToFilterStudent) {
2161
            if ($httpRequest->query->has('user')) {
2162
                $this->owner = api_get_user_entity($httpRequest->query->getInt('user'));
2163
2164
                if (empty($this->owner)) {
2165
                    api_not_allowed(true);
2166
                }
2167
            }
2168
        }
2169
2170
        $itemsRepo = $this->em->getRepository(Portfolio::class);
2171
        $commentsRepo = $this->em->getRepository(PortfolioComment::class);
2172
        $attachmentsRepo = $this->em->getRepository(PortfolioAttachment::class);
2173
2174
        $visibility = [];
2175
2176
        if ($isAllowedToFilterStudent && $currentUserId !== $this->owner->getId()) {
2177
            $visibility[] = Portfolio::VISIBILITY_VISIBLE;
2178
            $visibility[] = Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER;
2179
        }
2180
2181
        $items = $itemsRepo->findItemsByUser(
2182
            $this->owner,
2183
            $this->course,
2184
            $this->session,
2185
            null,
2186
            $visibility
2187
        );
2188
        $comments = $commentsRepo->findCommentsByUser($this->owner, $this->course, $this->session);
2189
2190
        $itemsHtml = $this->getItemsInHtmlFormatted($items);
2191
        $commentsHtml = $this->getCommentsInHtmlFormatted($comments);
2192
2193
        $sysArchivePath = api_get_path(SYS_ARCHIVE_PATH);
2194
        $tempPortfolioDirectory = $sysArchivePath."portfolio/{$this->owner->getId()}";
2195
2196
        $userDirectory = UserManager::getUserPathById($this->owner->getId(), 'system');
2197
        $attachmentsDirectory = $userDirectory.'portfolio_attachments/';
2198
2199
        $tblItemsHeaders = [];
2200
        $tblItemsHeaders[] = get_lang('Title');
2201
        $tblItemsHeaders[] = get_lang('CreationDate');
2202
        $tblItemsHeaders[] = get_lang('LastUpdate');
2203
        $tblItemsHeaders[] = get_lang('Category');
2204
        $tblItemsHeaders[] = get_lang('Category');
2205
        $tblItemsHeaders[] = get_lang('Score');
2206
        $tblItemsHeaders[] = get_lang('Course');
2207
        $tblItemsHeaders[] = get_lang('Session');
2208
        $tblItemsData = [];
2209
2210
        $tblCommentsHeaders = [];
2211
        $tblCommentsHeaders[] = get_lang('Resume');
2212
        $tblCommentsHeaders[] = get_lang('Date');
2213
        $tblCommentsHeaders[] = get_lang('PortfolioItemTitle');
2214
        $tblCommentsHeaders[] = get_lang('Score');
2215
        $tblCommentsData = [];
2216
2217
        $filenames = [];
2218
2219
        $fs = new Filesystem();
2220
2221
        /**
2222
         * @var int       $i
2223
         * @var Portfolio $item
2224
         */
2225
        foreach ($items as $i => $item) {
2226
            $itemCategory = $item->getCategory();
2227
            $itemCourse = $item->getCourse();
2228
            $itemSession = $item->getSession();
2229
2230
            $itemDirectory = $item->getCreationDate()->format('Y-m-d-H-i-s');
2231
2232
            $itemFilename = sprintf('%s/items/%s/item.html', $tempPortfolioDirectory, $itemDirectory);
2233
            $itemFileContent = $this->fixImagesSourcesToHtml($itemsHtml[$i]);
2234
2235
            $fs->dumpFile($itemFilename, $itemFileContent);
2236
2237
            $filenames[] = $itemFilename;
2238
2239
            $attachments = $attachmentsRepo->findFromItem($item);
2240
2241
            /** @var PortfolioAttachment $attachment */
2242
            foreach ($attachments as $attachment) {
2243
                $attachmentFilename = sprintf(
2244
                    '%s/items/%s/attachments/%s',
2245
                    $tempPortfolioDirectory,
2246
                    $itemDirectory,
2247
                    $attachment->getFilename()
2248
                );
2249
2250
                $fs->copy(
2251
                    $attachmentsDirectory.$attachment->getPath(),
2252
                    $attachmentFilename
2253
                );
2254
2255
                $filenames[] = $attachmentFilename;
2256
            }
2257
2258
            $tblItemsData[] = [
2259
                Display::url(
2260
                    Security::remove_XSS($item->getTitle()),
2261
                    sprintf('items/%s/item.html', $itemDirectory)
2262
                ),
2263
                api_convert_and_format_date($item->getCreationDate()),
2264
                api_convert_and_format_date($item->getUpdateDate()),
2265
                $itemCategory ? $itemCategory->getTitle() : null,
2266
                $item->getComments()->count(),
2267
                $item->getScore(),
2268
                $itemCourse->getTitle(),
2269
                $itemSession ? $itemSession->getName() : null,
2270
            ];
2271
        }
2272
2273
        /**
2274
         * @var int              $i
2275
         * @var PortfolioComment $comment
2276
         */
2277
        foreach ($comments as $i => $comment) {
2278
            $commentDirectory = $comment->getDate()->format('Y-m-d-H-i-s');
2279
2280
            $commentFileContent = $this->fixImagesSourcesToHtml($commentsHtml[$i]);
2281
            $commentFilename = sprintf('%s/comments/%s/comment.html', $tempPortfolioDirectory, $commentDirectory);
2282
2283
            $fs->dumpFile($commentFilename, $commentFileContent);
2284
2285
            $filenames[] = $commentFilename;
2286
2287
            $attachments = $attachmentsRepo->findFromComment($comment);
2288
2289
            /** @var PortfolioAttachment $attachment */
2290
            foreach ($attachments as $attachment) {
2291
                $attachmentFilename = sprintf(
2292
                    '%s/comments/%s/attachments/%s',
2293
                    $tempPortfolioDirectory,
2294
                    $commentDirectory,
2295
                    $attachment->getFilename()
2296
                );
2297
2298
                $fs->copy(
2299
                    $attachmentsDirectory.$attachment->getPath(),
2300
                    $attachmentFilename
2301
                );
2302
2303
                $filenames[] = $attachmentFilename;
2304
            }
2305
2306
            $tblCommentsData[] = [
2307
                Display::url(
2308
                    $comment->getExcerpt(),
2309
                    sprintf('comments/%s/comment.html', $commentDirectory)
2310
                ),
2311
                api_convert_and_format_date($comment->getDate()),
2312
                Security::remove_XSS($comment->getItem()->getTitle()),
2313
                $comment->getScore(),
2314
            ];
2315
        }
2316
2317
        $tblItems = new HTML_Table(['class' => 'table table-hover table-striped table-bordered data_table']);
2318
        $tblItems->setHeaders($tblItemsHeaders);
2319
        $tblItems->setData($tblItemsData);
2320
2321
        $tblComments = new HTML_Table(['class' => 'table table-hover table-striped table-bordered data_table']);
2322
        $tblComments->setHeaders($tblCommentsHeaders);
2323
        $tblComments->setData($tblCommentsData);
2324
2325
        $itemFilename = sprintf('%s/index.html', $tempPortfolioDirectory);
2326
2327
        $filenames[] = $itemFilename;
2328
2329
        $fs->dumpFile(
2330
            $itemFilename,
2331
            $this->formatZipIndexFile($tblItems, $tblComments)
2332
        );
2333
2334
        $zipName = $this->owner->getCompleteName()
2335
            .($this->course ? '_'.$this->course->getCode() : '')
2336
            .'_'.get_lang('Portfolio');
2337
        $tempZipFile = $sysArchivePath."portfolio/$zipName.zip";
2338
        $zip = new PclZip($tempZipFile);
2339
2340
        foreach ($filenames as $filename) {
2341
            $zip->add($filename, PCLZIP_OPT_REMOVE_PATH, $tempPortfolioDirectory);
0 ignored issues
show
Bug introduced by
The constant PCLZIP_OPT_REMOVE_PATH was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2342
        }
2343
2344
        DocumentManager::file_send_for_download($tempZipFile, true, "$zipName.zip");
2345
2346
        $fs->remove($tempPortfolioDirectory);
2347
        $fs->remove($tempZipFile);
2348
    }
2349
2350
    public function qualifyItem(Portfolio $item)
2351
    {
2352
        global $interbreadcrumb;
2353
2354
        $em = Database::getManager();
2355
2356
        $formAction = $this->baseUrl.http_build_query(['action' => 'qualify', 'item' => $item->getId()]);
2357
2358
        $form = new FormValidator('frm_qualify', 'post', $formAction);
2359
        $form->addUserAvatar('user', get_lang('Author'));
2360
        $form->addLabel(get_lang('Title'), $item->getTitle());
2361
2362
        $itemContent = $this->generateItemContent($item);
2363
2364
        $form->addLabel(get_lang('Content'), $itemContent);
2365
        $form->addNumeric(
2366
            'score',
2367
            [get_lang('QualifyNumeric'), null, ' / '.api_get_course_setting('portfolio_max_score')]
2368
        );
2369
        $form->addButtonSave(get_lang('QualifyThisPortfolioItem'));
2370
2371
        if ($form->validate()) {
2372
            $values = $form->exportValues();
2373
2374
            $item->setScore($values['score']);
2375
2376
            $em->persist($item);
2377
            $em->flush();
2378
2379
            Display::addFlash(
2380
                Display::return_message(get_lang('PortfolioItemGraded'), 'success')
2381
            );
2382
2383
            header("Location: $formAction");
2384
            exit();
2385
        }
2386
2387
        $form->setDefaults(
2388
            [
2389
                'user' => $item->getUser(),
2390
                'score' => (float) $item->getScore(),
2391
            ]
2392
        );
2393
2394
        $interbreadcrumb[] = [
2395
            'name' => get_lang('Portfolio'),
2396
            'url' => $this->baseUrl,
2397
        ];
2398
        $interbreadcrumb[] = [
2399
            'name' => $item->getTitle(true),
2400
            'url' => $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()]),
2401
        ];
2402
2403
        $actions = [];
2404
        $actions[] = Display::url(
2405
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
2406
            $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()])
2407
        );
2408
2409
        $this->renderView($form->returnForm(), get_lang('Qualify'), $actions);
2410
    }
2411
2412
    public function qualifyComment(PortfolioComment $comment)
2413
    {
2414
        global $interbreadcrumb;
2415
2416
        $em = Database::getManager();
2417
2418
        $item = $comment->getItem();
2419
        $commentPath = $em->getRepository(PortfolioComment::class)->getPath($comment);
2420
2421
        $template = new Template('', false, false, false, true, false, false);
2422
        $template->assign('item', $item);
2423
        $template->assign('comments_path', $commentPath);
2424
        $commentContext = $template->fetch(
2425
            $template->get_template('portfolio/comment_context.html.twig')
2426
        );
2427
2428
        $formAction = $this->baseUrl.http_build_query(['action' => 'qualify', 'comment' => $comment->getId()]);
2429
2430
        $form = new FormValidator('frm_qualify', 'post', $formAction);
2431
        $form->addHtml($commentContext);
2432
        $form->addUserAvatar('user', get_lang('Author'));
2433
        $form->addLabel(get_lang('Comment'), $comment->getContent());
2434
        $form->addNumeric(
2435
            'score',
2436
            [get_lang('QualifyNumeric'), null, '/ '.api_get_course_setting('portfolio_max_score')]
2437
        );
2438
        $form->addButtonSave(get_lang('QualifyThisPortfolioComment'));
2439
2440
        if ($form->validate()) {
2441
            $values = $form->exportValues();
2442
2443
            $comment->setScore($values['score']);
2444
2445
            $em->persist($comment);
2446
            $em->flush();
2447
2448
            Display::addFlash(
2449
                Display::return_message(get_lang('PortfolioCommentGraded'), 'success')
2450
            );
2451
2452
            header("Location: $formAction");
2453
            exit();
2454
        }
2455
2456
        $form->setDefaults(
2457
            [
2458
                'user' => $comment->getAuthor(),
2459
                'score' => (float) $comment->getScore(),
2460
            ]
2461
        );
2462
2463
        $interbreadcrumb[] = [
2464
            'name' => get_lang('Portfolio'),
2465
            'url' => $this->baseUrl,
2466
        ];
2467
        $interbreadcrumb[] = [
2468
            'name' => $item->getTitle(true),
2469
            'url' => $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()]),
2470
        ];
2471
2472
        $actions = [];
2473
        $actions[] = Display::url(
2474
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
2475
            $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()])
2476
        );
2477
2478
        $this->renderView($form->returnForm(), get_lang('Qualify'), $actions);
2479
    }
2480
2481
    public function downloadAttachment(HttpRequest $httpRequest)
2482
    {
2483
        $path = $httpRequest->query->get('file');
2484
2485
        if (empty($path)) {
2486
            api_not_allowed(true);
2487
        }
2488
2489
        $em = Database::getManager();
2490
        $attachmentRepo = $em->getRepository(PortfolioAttachment::class);
2491
2492
        $attachment = $attachmentRepo->findOneByPath($path);
2493
2494
        if (empty($attachment)) {
2495
            api_not_allowed(true);
2496
        }
2497
2498
        $originOwnerId = 0;
2499
2500
        if (PortfolioAttachment::TYPE_ITEM === $attachment->getOriginType()) {
2501
            $item = $em->find(Portfolio::class, $attachment->getOrigin());
2502
2503
            $originOwnerId = $item->getUser()->getId();
2504
        } elseif (PortfolioAttachment::TYPE_COMMENT === $attachment->getOriginType()) {
2505
            $comment = $em->find(PortfolioComment::class, $attachment->getOrigin());
2506
2507
            $originOwnerId = $comment->getAuthor()->getId();
2508
        } else {
2509
            api_not_allowed(true);
2510
        }
2511
2512
        $userDirectory = UserManager::getUserPathById($originOwnerId, 'system');
2513
        $attachmentsDirectory = $userDirectory.'portfolio_attachments/';
2514
        $attachmentFilename = $attachmentsDirectory.$attachment->getPath();
2515
2516
        if (!Security::check_abs_path($attachmentFilename, $attachmentsDirectory)) {
2517
            api_not_allowed(true);
2518
        }
2519
2520
        $downloaded = DocumentManager::file_send_for_download(
2521
            $attachmentFilename,
2522
            true,
2523
            $attachment->getFilename()
2524
        );
2525
2526
        if (!$downloaded) {
2527
            api_not_allowed(true);
2528
        }
2529
    }
2530
2531
    public function deleteAttachment(HttpRequest $httpRequest)
2532
    {
2533
        $currentUserId = api_get_user_id();
2534
2535
        $path = $httpRequest->query->get('file');
2536
2537
        if (empty($path)) {
2538
            api_not_allowed(true);
2539
        }
2540
2541
        $em = Database::getManager();
2542
        $fs = new Filesystem();
2543
2544
        $attachmentRepo = $em->getRepository(PortfolioAttachment::class);
2545
        $attachment = $attachmentRepo->findOneByPath($path);
2546
2547
        if (empty($attachment)) {
2548
            api_not_allowed(true);
2549
        }
2550
2551
        $originOwnerId = 0;
2552
        $itemId = 0;
2553
2554
        if (PortfolioAttachment::TYPE_ITEM === $attachment->getOriginType()) {
2555
            $item = $em->find(Portfolio::class, $attachment->getOrigin());
2556
            $originOwnerId = $item->getUser()->getId();
2557
            $itemId = $item->getId();
2558
        } elseif (PortfolioAttachment::TYPE_COMMENT === $attachment->getOriginType()) {
2559
            $comment = $em->find(PortfolioComment::class, $attachment->getOrigin());
2560
            $originOwnerId = $comment->getAuthor()->getId();
2561
            $itemId = $comment->getItem()->getId();
2562
        }
2563
2564
        if ($currentUserId !== $originOwnerId) {
2565
            api_not_allowed(true);
2566
        }
2567
2568
        $em->remove($attachment);
2569
        $em->flush();
2570
2571
        $userDirectory = UserManager::getUserPathById($originOwnerId, 'system');
2572
        $attachmentsDirectory = $userDirectory.'portfolio_attachments/';
2573
        $attachmentFilename = $attachmentsDirectory.$attachment->getPath();
2574
2575
        $fs->remove($attachmentFilename);
2576
2577
        if ($httpRequest->isXmlHttpRequest()) {
2578
            echo Display::return_message(get_lang('AttachmentFileDeleteSuccess'), 'success');
2579
        } else {
2580
            Display::addFlash(
2581
                Display::return_message(get_lang('AttachmentFileDeleteSuccess'), 'success')
2582
            );
2583
2584
            header('Location: '.$this->baseUrl.http_build_query(['action' => 'view', 'id' => $itemId]));
2585
        }
2586
2587
        exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
2588
    }
2589
2590
    /**
2591
     * @throws \Doctrine\ORM\OptimisticLockException
2592
     * @throws \Doctrine\ORM\ORMException
2593
     */
2594
    public function markAsHighlighted(Portfolio $item)
2595
    {
2596
        if ($item->getCourse()->getId() !== (int) api_get_course_int_id()) {
2597
            api_not_allowed(true);
2598
        }
2599
2600
        $item->setIsHighlighted(
2601
            !$item->isHighlighted()
2602
        );
2603
2604
        Database::getManager()->flush();
2605
2606
        Display::addFlash(
2607
            Display::return_message(
2608
                $item->isHighlighted() ? get_lang('MarkedAsHighlighted') : get_lang('UnmarkedAsHighlighted'),
2609
                'success'
2610
            )
2611
        );
2612
2613
        header("Location: $this->baseUrl".http_build_query(['action' => 'view', 'id' => $item->getId()]));
2614
        exit;
2615
    }
2616
2617
    public function markAsTemplate(Portfolio $item)
2618
    {
2619
        if (!$this->itemBelongToOwner($item)) {
2620
            api_not_allowed(true);
2621
        }
2622
2623
        $item->setIsTemplate(
2624
            !$item->isTemplate()
2625
        );
2626
2627
        Database::getManager()->flush($item);
2628
2629
        Display::addFlash(
2630
            Display::return_message(
2631
                $item->isTemplate() ? get_lang('PortfolioItemSetAsTemplate') : get_lang('PortfolioItemUnsetAsTemplate'),
2632
                'success'
2633
            )
2634
        );
2635
2636
        header("Location: $this->baseUrl".http_build_query(['action' => 'view', 'id' => $item->getId()]));
2637
        exit;
2638
    }
2639
2640
    public function markAsTemplateComment(PortfolioComment $comment)
2641
    {
2642
        if (!$this->commentBelongsToOwner($comment)) {
2643
            api_not_allowed(true);
2644
        }
2645
2646
        $comment->setIsTemplate(
2647
            !$comment->isTemplate()
2648
        );
2649
2650
        Database::getManager()->flush();
2651
2652
        Display::addFlash(
2653
            Display::return_message(
2654
                $comment->isTemplate() ? get_lang('PortfolioCommentSetAsTemplate') : get_lang('PortfolioCommentUnsetAsTemplate'),
2655
                'success'
2656
            )
2657
        );
2658
2659
        header("Location: $this->baseUrl".http_build_query(['action' => 'view', 'id' => $comment->getItem()->getId()]));
2660
        exit;
2661
    }
2662
2663
    public function listTags(HttpRequest $request)
2664
    {
2665
        global $interbreadcrumb;
2666
2667
        api_protect_course_script();
2668
        api_protect_teacher_script();
2669
2670
        $em = Database::getManager();
2671
        $tagRepo = $em->getRepository(Tag::class);
2672
2673
        $tagsQuery = $tagRepo->findForPortfolioInCourseQuery($this->course, $this->session);
2674
2675
        $tag = $request->query->has('id')
2676
            ? $tagRepo->find($request->query->getInt('id'))
2677
            : null;
2678
2679
        $formAction = ['action' => $request->query->get('action')];
2680
2681
        if ($tag) {
2682
            $formAction['id'] = $tag->getId();
2683
        }
2684
2685
        $form = new FormValidator('frm_add_tag', 'post', $this->baseUrl.http_build_query($formAction));
2686
        $form->addText('name', get_lang('Tag'));
2687
2688
        if ($tag) {
2689
            $form->addButtonUpdate(get_lang('Edit'));
2690
        } else {
2691
            $form->addButtonCreate(get_lang('Add'));
2692
        }
2693
2694
        if ($form->validate()) {
2695
            $values = $form->exportValues();
2696
2697
            $extraFieldInfo = (new ExtraField('portfolio'))->get_handler_field_info_by_field_variable('tags');
2698
2699
            if (!$tag) {
2700
                $tag = (new Tag())->setCount(0);
2701
2702
                $portfolioRelTag = (new PortfolioRelTag())
2703
                    ->setTag($tag)
2704
                    ->setCourse($this->course)
2705
                    ->setSession($this->session)
2706
                ;
2707
2708
                $em->persist($tag);
2709
                $em->persist($portfolioRelTag);
2710
            }
2711
2712
            $tag
2713
                ->setTag($values['name'])
2714
                ->setFieldId((int) $extraFieldInfo['id'])
2715
            ;
2716
2717
            $em->flush();
2718
2719
            Display::addFlash(
2720
                Display::return_message(get_lang('TagSaved'), 'success')
2721
            );
2722
2723
            header('Location: '.$this->baseUrl.http_build_query($formAction));
2724
            exit();
2725
        } else {
2726
            $form->protect();
2727
2728
            if ($tag) {
2729
                $form->setDefaults(['name' => $tag->getTag()]);
2730
            }
2731
        }
2732
2733
        $langTags = get_lang('Tags');
2734
        $langEdit = get_lang('Edit');
2735
2736
        $deleteIcon = Display::return_icon('delete.png', get_lang('Delete'));
2737
        $editIcon = Display::return_icon('edit.png', $langEdit);
2738
2739
        $table = new SortableTable(
2740
            'portfolio_tags',
2741
            function () use ($tagsQuery) {
2742
                return (int) $tagsQuery
2743
                    ->select('COUNT(t)')
2744
                    ->getQuery()
2745
                    ->getSingleScalarResult()
2746
                ;
2747
            },
2748
            function ($from, $limit, $column, $direction) use ($tagsQuery) {
2749
                $data = [];
2750
2751
                /** @var array<int, Tag> $tags */
2752
                $tags = $tagsQuery
2753
                    ->select('t')
2754
                    ->orderBy('t.tag', $direction)
2755
                    ->setFirstResult($from)
2756
                    ->setMaxResults($limit)
2757
                    ->getQuery()
2758
                    ->getResult();
2759
2760
                foreach ($tags as $tag) {
2761
                    $data[] = [
2762
                        $tag->getTag(),
2763
                        $tag->getId(),
2764
                    ];
2765
                }
2766
2767
                return $data;
2768
            },
2769
            0,
2770
            40
2771
        );
2772
        $table->set_header(0, get_lang('Name'));
2773
        $table->set_header(1, get_lang('Actions'), false, ['class' => 'text-right'], ['class' => 'text-right']);
2774
        $table->set_column_filter(
2775
            1,
2776
            function ($id) use ($editIcon, $deleteIcon) {
2777
                $editParams = http_build_query(['action' => 'edit_tag', 'id' => $id]);
2778
                $deleteParams = http_build_query(['action' => 'delete_tag', 'id' => $id]);
2779
2780
                return Display::url($editIcon, $this->baseUrl.$editParams).PHP_EOL
2781
                    .Display::url($deleteIcon, $this->baseUrl.$deleteParams).PHP_EOL;
2782
            }
2783
        );
2784
        $table->set_additional_parameters(
2785
            [
2786
                'action' => 'tags',
2787
                'cidReq' => $this->course->getCode(),
2788
                'id_session' => $this->session ? $this->session->getId() : 0,
2789
                'gidReq' => 0,
2790
            ]
2791
        );
2792
2793
        $content = $form->returnForm().PHP_EOL
2794
            .$table->return_table();
2795
2796
        $interbreadcrumb[] = [
2797
            'name' => get_lang('Portfolio'),
2798
            'url' => $this->baseUrl,
2799
        ];
2800
2801
        $pageTitle = $langTags;
2802
2803
        if ($tag) {
2804
            $pageTitle = $langEdit;
2805
2806
            $interbreadcrumb[] = [
2807
                'name' => $langTags,
2808
                'url' => $this->baseUrl.'action=tags',
2809
            ];
2810
        }
2811
2812
        $this->renderView($content, $pageTitle);
2813
    }
2814
2815
    public function deleteTag(Tag $tag)
2816
    {
2817
        api_protect_course_script();
2818
        api_protect_teacher_script();
2819
2820
        $em = Database::getManager();
2821
        $portfolioTagRepo = $em->getRepository(PortfolioRelTag::class);
2822
2823
        $portfolioTag = $portfolioTagRepo
2824
            ->findOneBy(['tag' => $tag, 'course' => $this->course, 'session' => $this->session]);
2825
2826
        if ($portfolioTag) {
2827
            $em->remove($portfolioTag);
2828
            $em->flush();
2829
2830
            Display::addFlash(
2831
                Display::return_message(get_lang('TagDeleted'), 'success')
2832
            );
2833
        }
2834
2835
        header('Location: '.$this->baseUrl.http_build_query(['action' => 'tags']));
2836
        exit();
2837
    }
2838
2839
    /**
2840
     * @param bool $showHeader
2841
     */
2842
    private function renderView(string $content, string $toolName, array $actions = [], $showHeader = true)
2843
    {
2844
        global $this_section;
2845
2846
        $this_section = $this->course ? SECTION_COURSES : SECTION_SOCIAL;
2847
2848
        $view = new Template($toolName);
2849
2850
        if ($showHeader) {
2851
            $view->assign('header', $toolName);
2852
        }
2853
2854
        $actionsStr = '';
2855
2856
        if ($this->course) {
2857
            $actionsStr .= Display::return_introduction_section(TOOL_PORTFOLIO);
0 ignored issues
show
Bug introduced by
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...
2858
        }
2859
2860
        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...
2861
            $actions = implode('', $actions);
2862
2863
            $actionsStr .= Display::toolbarAction('portfolio-toolbar', [$actions]);
2864
        }
2865
2866
        $view->assign('baseurl', $this->baseUrl);
2867
        $view->assign('actions', $actionsStr);
2868
2869
        $view->assign('content', $content);
2870
        $view->display_one_col_template();
2871
    }
2872
2873
    private function categoryBelongToOwner(PortfolioCategory $category): bool
2874
    {
2875
        if ($category->getUser()->getId() != $this->owner->getId()) {
2876
            return false;
2877
        }
2878
2879
        return true;
2880
    }
2881
2882
    private function addAttachmentsFieldToForm(FormValidator $form)
2883
    {
2884
        $form->addButton('add_attachment', get_lang('AddAttachment'), 'plus');
2885
        $form->addHtml('<div id="container-attachments" style="display: none;">');
2886
        $form->addFile('attachment_file[]', get_lang('FilesAttachment'));
2887
        $form->addText('attachment_comment[]', get_lang('Description'), false);
2888
        $form->addHtml('</div>');
2889
2890
        $script = "$(function () {
2891
            var attachmentsTemplate = $('#container-attachments').html();
2892
            var \$btnAdd = $('[name=\"add_attachment\"]');
2893
            var \$reference = \$btnAdd.parents('.form-group');
2894
2895
            \$btnAdd.on('click', function (e) {
2896
                e.preventDefault();
2897
2898
                $(attachmentsTemplate).insertBefore(\$reference);
2899
            });
2900
        })";
2901
2902
        $form->addHtml("<script>$script</script>");
2903
    }
2904
2905
    private function processAttachments(
2906
        FormValidator $form,
2907
        User $user,
2908
        int $originId,
2909
        int $originType
2910
    ) {
2911
        $em = Database::getManager();
2912
        $fs = new Filesystem();
2913
2914
        $comments = $form->getSubmitValue('attachment_comment');
2915
2916
        foreach ($_FILES['attachment_file']['error'] as $i => $attachmentFileError) {
2917
            if ($attachmentFileError != UPLOAD_ERR_OK) {
2918
                continue;
2919
            }
2920
2921
            $_file = [
2922
                'name' => $_FILES['attachment_file']['name'][$i],
2923
                'type' => $_FILES['attachment_file']['type'][$i],
2924
                'tmp_name' => $_FILES['attachment_file']['tmp_name'][$i],
2925
                'size' => $_FILES['attachment_file']['size'][$i],
2926
            ];
2927
2928
            if (empty($_file['type'])) {
2929
                $_file['type'] = DocumentManager::file_get_mime_type($_file['name']);
2930
            }
2931
2932
            $newFileName = add_ext_on_mime(stripslashes($_file['name']), $_file['type']);
2933
2934
            if (!filter_extension($newFileName)) {
2935
                Display::addFlash(Display::return_message(get_lang('UplUnableToSaveFileFilteredExtension'), 'error'));
2936
                continue;
2937
            }
2938
2939
            $newFileName = uniqid();
2940
            $attachmentsDirectory = UserManager::getUserPathById($user->getId(), 'system').'portfolio_attachments/';
2941
2942
            if (!$fs->exists($attachmentsDirectory)) {
2943
                $fs->mkdir($attachmentsDirectory, api_get_permissions_for_new_directories());
2944
            }
2945
2946
            $attachmentFilename = $attachmentsDirectory.$newFileName;
2947
2948
            if (is_uploaded_file($_file['tmp_name'])) {
2949
                $moved = move_uploaded_file($_file['tmp_name'], $attachmentFilename);
2950
2951
                if (!$moved) {
2952
                    Display::addFlash(Display::return_message(get_lang('UplUnableToSaveFile'), 'error'));
2953
                    continue;
2954
                }
2955
            }
2956
2957
            $attachment = new PortfolioAttachment();
2958
            $attachment
2959
                ->setFilename($_file['name'])
2960
                ->setComment($comments[$i])
2961
                ->setPath($newFileName)
2962
                ->setOrigin($originId)
2963
                ->setOriginType($originType)
2964
                ->setSize($_file['size']);
2965
2966
            $em->persist($attachment);
2967
            $em->flush();
2968
        }
2969
    }
2970
2971
    private function itemBelongToOwner(Portfolio $item): bool
2972
    {
2973
        if ($item->getUser()->getId() != $this->owner->getId()) {
2974
            return false;
2975
        }
2976
2977
        return true;
2978
    }
2979
2980
    private function commentBelongsToOwner(PortfolioComment $comment): bool
2981
    {
2982
        return $comment->getAuthor() === $this->owner;
2983
    }
2984
2985
    private function createFormTagFilter(bool $listByUser = false): FormValidator
2986
    {
2987
        $tags = Database::getManager()
2988
            ->getRepository(Tag::class)
2989
            ->findForPortfolioInCourseQuery($this->course, $this->session)
2990
            ->getQuery()
2991
            ->getResult()
2992
        ;
2993
2994
        $frmTagList = new FormValidator(
2995
            'frm_tag_list',
2996
            'get',
2997
            $this->baseUrl.($listByUser ? 'user='.$this->owner->getId() : ''),
2998
            '',
2999
            [],
3000
            FormValidator::LAYOUT_BOX
3001
        );
3002
3003
        $frmTagList->addDatePicker('date', get_lang('CreationDate'));
3004
3005
        $frmTagList->addSelectFromCollection(
3006
            'tags',
3007
            get_lang('Tags'),
3008
            $tags,
3009
            ['multiple' => 'multiple'],
3010
            false,
3011
            'getTag'
3012
        );
3013
3014
        $frmTagList->addText('text', get_lang('Search'), false)->setIcon('search');
3015
        $frmTagList->applyFilter('text', 'trim');
3016
        $frmTagList->addHtml('<br>');
3017
        $frmTagList->addButtonFilter(get_lang('Filter'));
3018
3019
        if ($this->course) {
3020
            $frmTagList->addHidden('cidReq', $this->course->getCode());
3021
            $frmTagList->addHidden('id_session', $this->session ? $this->session->getId() : 0);
3022
            $frmTagList->addHidden('gidReq', 0);
3023
            $frmTagList->addHidden('gradebook', 0);
3024
            $frmTagList->addHidden('origin', '');
3025
            $frmTagList->addHidden('categoryId', 0);
3026
            $frmTagList->addHidden('subCategoryIds', '');
3027
3028
            if ($listByUser) {
3029
                $frmTagList->addHidden('user', $this->owner->getId());
3030
            }
3031
        }
3032
3033
        return $frmTagList;
3034
    }
3035
3036
    /**
3037
     * @throws Exception
3038
     */
3039
    private function createFormStudentFilter(bool $listByUser = false, bool $listHighlighted = false): FormValidator
3040
    {
3041
        $frmStudentList = new FormValidator(
3042
            'frm_student_list',
3043
            'get',
3044
            $this->baseUrl,
3045
            '',
3046
            [],
3047
            FormValidator::LAYOUT_BOX
3048
        );
3049
3050
        $urlParams = http_build_query(
3051
            [
3052
                'a' => 'search_user_by_course',
3053
                'course_id' => $this->course->getId(),
3054
                'session_id' => $this->session ? $this->session->getId() : 0,
3055
            ]
3056
        );
3057
3058
        /** @var SelectAjax $slctUser */
3059
        $slctUser = $frmStudentList->addSelectAjax(
3060
            'user',
3061
            get_lang('SelectLearnerPortfolio'),
3062
            [],
3063
            [
3064
                'url' => api_get_path(WEB_AJAX_PATH)."course.ajax.php?$urlParams",
3065
                'placeholder' => get_lang('SearchStudent'),
3066
                'formatResult' => SelectAjax::templateResultForUsersInCourse(),
3067
                'formatSelection' => SelectAjax::templateSelectionForUsersInCourse(),
3068
            ]
3069
        );
3070
3071
        if ($listByUser) {
3072
            $slctUser->addOption(
3073
                $this->owner->getCompleteName(),
3074
                $this->owner->getId(),
3075
                [
3076
                    'data-avatarurl' => UserManager::getUserPicture($this->owner->getId()),
3077
                    'data-username' => $this->owner->getUsername(),
3078
                ]
3079
            );
3080
3081
            $link = Display::url(
3082
                get_lang('BackToMainPortfolio'),
3083
                $this->baseUrl
3084
            );
3085
        } else {
3086
            $link = Display::url(
3087
                get_lang('SeeMyPortfolio'),
3088
                $this->baseUrl.http_build_query(['user' => api_get_user_id()])
3089
            );
3090
        }
3091
3092
        $frmStudentList->addHtml("<p>$link</p>");
3093
3094
        if ($listHighlighted) {
3095
            $link = Display::url(
3096
                get_lang('BackToMainPortfolio'),
3097
                $this->baseUrl
3098
            );
3099
        } else {
3100
            $link = Display::url(
3101
                get_lang('SeeHighlights'),
3102
                $this->baseUrl.http_build_query(['list_highlighted' => true])
3103
            );
3104
        }
3105
3106
        $frmStudentList->addHtml("<p>$link</p>");
3107
3108
        return $frmStudentList;
3109
    }
3110
3111
    private function getCategoriesForIndex(?int $currentUserId = null, ?int $parentId = null): array
3112
    {
3113
        $categoriesCriteria = [];
3114
        if (isset($currentUserId)) {
3115
            $categoriesCriteria['user'] = $this->owner;
3116
        }
3117
        if (!api_is_platform_admin() && $currentUserId !== $this->owner->getId()) {
3118
            $categoriesCriteria['isVisible'] = true;
3119
        }
3120
        if (isset($parentId)) {
3121
            $categoriesCriteria['parentId'] = $parentId;
3122
        }
3123
3124
        return $this->em
3125
            ->getRepository(PortfolioCategory::class)
3126
            ->findBy($categoriesCriteria);
3127
    }
3128
3129
    private function getHighlightedItems()
3130
    {
3131
        $queryBuilder = $this->em->createQueryBuilder();
3132
        $queryBuilder
3133
            ->select('pi')
3134
            ->from(Portfolio::class, 'pi')
3135
            ->where('pi.course = :course')
3136
            ->andWhere('pi.isHighlighted = TRUE')
3137
            ->setParameter('course', $this->course);
3138
3139
        if ($this->session) {
3140
            $queryBuilder->andWhere('pi.session = :session');
3141
            $queryBuilder->setParameter('session', $this->session);
3142
        } else {
3143
            $queryBuilder->andWhere('pi.session IS NULL');
3144
        }
3145
3146
        $visibilityCriteria = [Portfolio::VISIBILITY_VISIBLE];
3147
3148
        if (api_is_allowed_to_edit()) {
3149
            $visibilityCriteria[] = Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER;
3150
        }
3151
3152
        $queryBuilder
3153
            ->andWhere(
3154
                $queryBuilder->expr()->orX(
3155
                    'pi.user = :current_user',
3156
                    $queryBuilder->expr()->andX(
3157
                        'pi.user != :current_user',
3158
                        $queryBuilder->expr()->in('pi.visibility', $visibilityCriteria)
3159
                    )
3160
                )
3161
            )
3162
            ->setParameter('current_user', api_get_user_id());
3163
3164
        $queryBuilder->orderBy('pi.creationDate', 'DESC');
3165
3166
        return $queryBuilder->getQuery()->getResult();
3167
    }
3168
3169
    private function getItemsForIndex(
3170
        bool $listByUser = false,
3171
        FormValidator $frmFilterList = null
3172
    ) {
3173
        $currentUserId = api_get_user_id();
3174
3175
        if ($this->course) {
3176
            $queryBuilder = $this->em->createQueryBuilder();
3177
            $queryBuilder
3178
                ->select('pi')
3179
                ->from(Portfolio::class, 'pi')
3180
                ->where('pi.course = :course');
3181
3182
            $queryBuilder->setParameter('course', $this->course);
3183
3184
            if ($this->session) {
3185
                $queryBuilder->andWhere('pi.session = :session');
3186
                $queryBuilder->setParameter('session', $this->session);
3187
            } else {
3188
                $queryBuilder->andWhere('pi.session IS NULL');
3189
            }
3190
3191
            if ($frmFilterList && $frmFilterList->validate()) {
3192
                $values = $frmFilterList->exportValues();
3193
3194
                if (!empty($values['date'])) {
3195
                    $queryBuilder
3196
                        ->andWhere('pi.creationDate >= :date')
3197
                        ->setParameter(':date', api_get_utc_datetime($values['date'], false, true))
3198
                    ;
3199
                }
3200
3201
                if (!empty($values['tags'])) {
3202
                    $queryBuilder
3203
                        ->innerJoin(ExtraFieldRelTag::class, 'efrt', Join::WITH, 'efrt.itemId = pi.id')
3204
                        ->innerJoin(ExtraFieldEntity::class, 'ef', Join::WITH, 'ef.id = efrt.fieldId')
3205
                        ->andWhere('ef.extraFieldType = :efType')
3206
                        ->andWhere('ef.variable = :variable')
3207
                        ->andWhere('efrt.tagId IN (:tags)');
3208
3209
                    $queryBuilder->setParameter('efType', ExtraFieldEntity::PORTFOLIO_TYPE);
3210
                    $queryBuilder->setParameter('variable', 'tags');
3211
                    $queryBuilder->setParameter('tags', $values['tags']);
3212
                }
3213
3214
                if (!empty($values['text'])) {
3215
                    $queryBuilder->andWhere(
3216
                        $queryBuilder->expr()->orX(
3217
                            $queryBuilder->expr()->like('pi.title', ':text'),
3218
                            $queryBuilder->expr()->like('pi.content', ':text')
3219
                        )
3220
                    );
3221
3222
                    $queryBuilder->setParameter('text', '%'.$values['text'].'%');
3223
                }
3224
3225
                // Filters by category level 0
3226
                $searchCategories = [];
3227
                if (!empty($values['categoryId'])) {
3228
                    $searchCategories[] = $values['categoryId'];
3229
                    $subCategories = $this->getCategoriesForIndex(null, $values['categoryId']);
3230
                    if (count($subCategories) > 0) {
3231
                        foreach ($subCategories as $subCategory) {
3232
                            $searchCategories[] = $subCategory->getId();
3233
                        }
3234
                    }
3235
                    $queryBuilder->andWhere('pi.category IN('.implode(',', $searchCategories).')');
3236
                }
3237
3238
                // Filters by sub-category, don't show the selected values
3239
                $diff = [];
3240
                if (!empty($values['subCategoryIds']) && !('all' === $values['subCategoryIds'])) {
3241
                    $subCategoryIds = explode(',', $values['subCategoryIds']);
3242
                    $diff = array_diff($searchCategories, $subCategoryIds);
3243
                } else {
3244
                    if (trim($values['subCategoryIds']) === '') {
3245
                        $diff = $searchCategories;
3246
                    }
3247
                }
3248
                if (!empty($diff)) {
3249
                    unset($diff[0]);
3250
                    if (!empty($diff)) {
3251
                        $queryBuilder->andWhere('pi.category NOT IN('.implode(',', $diff).')');
3252
                    }
3253
                }
3254
            }
3255
3256
            if ($listByUser) {
3257
                $queryBuilder
3258
                    ->andWhere('pi.user = :user')
3259
                    ->setParameter('user', $this->owner);
3260
            }
3261
3262
            $visibilityCriteria = [Portfolio::VISIBILITY_VISIBLE];
3263
3264
            if (api_is_allowed_to_edit()) {
3265
                $visibilityCriteria[] = Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER;
3266
            }
3267
3268
            $queryBuilder
3269
                ->andWhere(
3270
                    $queryBuilder->expr()->orX(
3271
                        'pi.user = :current_user',
3272
                        $queryBuilder->expr()->andX(
3273
                            'pi.user != :current_user',
3274
                            $queryBuilder->expr()->in('pi.visibility', $visibilityCriteria)
3275
                        )
3276
                    )
3277
                )
3278
                ->setParameter('current_user', $currentUserId);
3279
3280
            $queryBuilder->orderBy('pi.creationDate', 'DESC');
3281
3282
            $items = $queryBuilder->getQuery()->getResult();
3283
        } else {
3284
            $itemsCriteria = [];
3285
            $itemsCriteria['category'] = null;
3286
            $itemsCriteria['user'] = $this->owner;
3287
3288
            if ($currentUserId !== $this->owner->getId()) {
3289
                $itemsCriteria['visibility'] = Portfolio::VISIBILITY_VISIBLE;
3290
            }
3291
3292
            $items = $this->em
3293
                ->getRepository(Portfolio::class)
3294
                ->findBy($itemsCriteria, ['creationDate' => 'DESC']);
3295
        }
3296
3297
        return $items;
3298
    }
3299
3300
    /**
3301
     * @throws \Doctrine\ORM\ORMException
3302
     * @throws \Doctrine\ORM\OptimisticLockException
3303
     * @throws \Doctrine\ORM\TransactionRequiredException
3304
     */
3305
    private function createCommentForm(Portfolio $item): string
3306
    {
3307
        $formAction = $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()]);
3308
3309
        $templates = $this->em
3310
            ->getRepository(PortfolioComment::class)
3311
            ->findBy(
3312
                [
3313
                    'isTemplate' => true,
3314
                    'author' => $this->owner,
3315
                ]
3316
            );
3317
3318
        $form = new FormValidator('frm_comment', 'post', $formAction);
3319
        $form->addHeader(get_lang('AddNewComment'));
3320
        $form->addSelectFromCollection(
3321
            'template',
3322
            [
3323
                get_lang('Template'),
3324
                null,
3325
                '<span id="portfolio-spinner" class="fa fa-fw fa-spinner fa-spin" style="display: none;"
3326
                    aria-hidden="true" aria-label="'.get_lang('Loading').'"></span>',
3327
            ],
3328
            $templates,
3329
            [],
3330
            true,
3331
            'getExcerpt'
3332
        );
3333
        $form->addHtmlEditor('content', get_lang('Comments'), true, false, ['ToolbarSet' => 'Minimal']);
3334
        $form->addHidden('item', $item->getId());
3335
        $form->addHidden('parent', 0);
3336
        $form->applyFilter('content', 'trim');
3337
3338
        $this->addAttachmentsFieldToForm($form);
3339
3340
        $form->addButtonSave(get_lang('Save'));
3341
3342
        if ($form->validate()) {
3343
            $values = $form->exportValues();
3344
3345
            $parentComment = $this->em->find(PortfolioComment::class, $values['parent']);
3346
3347
            $comment = new PortfolioComment();
3348
            $comment
3349
                ->setAuthor($this->owner)
3350
                ->setParent($parentComment)
3351
                ->setContent($values['content'])
3352
                ->setDate(api_get_utc_datetime(null, false, true))
3353
                ->setItem($item);
3354
3355
            $this->em->persist($comment);
3356
            $this->em->flush();
3357
3358
            $this->processAttachments(
3359
                $form,
3360
                $comment->getAuthor(),
3361
                $comment->getId(),
3362
                PortfolioAttachment::TYPE_COMMENT
3363
            );
3364
3365
            $hook = HookPortfolioItemCommented::create();
3366
            $hook->setEventData(['comment' => $comment]);
3367
            $hook->notifyItemCommented();
3368
3369
            PortfolioNotifier::notifyTeachersAndAuthor($comment);
3370
3371
            Display::addFlash(
3372
                Display::return_message(get_lang('CommentAdded'), 'success')
3373
            );
3374
3375
            header("Location: $formAction");
3376
            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...
3377
        }
3378
3379
        $js = '<script>
3380
            $(function() {
3381
                $(\'#frm_comment_template\').on(\'change\', function () {
3382
                    $(\'#portfolio-spinner\').show();
3383
                
3384
                    $.getJSON(_p.web_ajax + \'portfolio.ajax.php?a=find_template_comment&comment=\' + this.value)
3385
                        .done(function(response) {
3386
                            CKEDITOR.instances.content.setData(response.content);
3387
                        })
3388
                        .fail(function () {
3389
                            CKEDITOR.instances.content.setData(\'\');
3390
                        })
3391
                        .always(function() {
3392
                          $(\'#portfolio-spinner\').hide();
3393
                        });
3394
                });
3395
            });
3396
        </script>';
3397
3398
        return $form->returnForm().$js;
3399
    }
3400
3401
    private function generateAttachmentList($post, bool $includeHeader = true): string
3402
    {
3403
        $attachmentsRepo = $this->em->getRepository(PortfolioAttachment::class);
3404
3405
        $postOwnerId = 0;
3406
3407
        if ($post instanceof Portfolio) {
3408
            $attachments = $attachmentsRepo->findFromItem($post);
3409
3410
            $postOwnerId = $post->getUser()->getId();
3411
        } elseif ($post instanceof PortfolioComment) {
3412
            $attachments = $attachmentsRepo->findFromComment($post);
3413
3414
            $postOwnerId = $post->getAuthor()->getId();
3415
        }
3416
3417
        if (empty($attachments)) {
3418
            return '';
3419
        }
3420
3421
        $currentUserId = api_get_user_id();
3422
3423
        $listItems = '<ul class="fa-ul">';
3424
3425
        $deleteIcon = Display::return_icon(
3426
            'delete.png',
3427
            get_lang('DeleteAttachment'),
3428
            ['style' => 'display: inline-block'],
3429
            ICON_SIZE_TINY
3430
        );
3431
        $deleteAttrs = ['class' => 'btn-portfolio-delete'];
3432
3433
        /** @var PortfolioAttachment $attachment */
3434
        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...
3435
            $downloadParams = http_build_query(['action' => 'download_attachment', 'file' => $attachment->getPath()]);
3436
            $deleteParams = http_build_query(['action' => 'delete_attachment', 'file' => $attachment->getPath()]);
3437
3438
            $listItems .= '<li>'
3439
                .'<span class="fa-li fa fa-paperclip" aria-hidden="true"></span>'
3440
                .Display::url(
3441
                    Security::remove_XSS($attachment->getFilename()),
3442
                    $this->baseUrl.$downloadParams
3443
                );
3444
3445
            if ($currentUserId === $postOwnerId) {
3446
                $listItems .= PHP_EOL.Display::url($deleteIcon, $this->baseUrl.$deleteParams, $deleteAttrs);
3447
            }
3448
3449
            if ($attachment->getComment()) {
3450
                $listItems .= '<p class="text-muted">'.Security::remove_XSS($attachment->getComment()).'</p>';
3451
            }
3452
3453
            $listItems .= '</li>';
3454
        }
3455
3456
        $listItems .= '</ul>';
3457
3458
        if ($includeHeader) {
3459
            $listItems = '<h1 class="h4">'.get_lang('AttachmentFiles').'</h1>'
3460
                .$listItems;
3461
        }
3462
3463
        return $listItems;
3464
    }
3465
3466
    private function generateItemContent(Portfolio $item): string
3467
    {
3468
        $originId = $item->getOrigin();
3469
3470
        if (empty($originId)) {
3471
            return $item->getContent();
3472
        }
3473
3474
        $em = Database::getManager();
3475
3476
        $originContent = '';
3477
        $originContentFooter = '';
3478
3479
        if (Portfolio::TYPE_ITEM === $item->getOriginType()) {
3480
            $origin = $em->find(Portfolio::class, $item->getOrigin());
3481
3482
            if ($origin) {
3483
                $originContent = $origin->getContent();
3484
                $originContentFooter = vsprintf(
3485
                    get_lang('OriginallyPublishedAsXTitleByYUser'),
3486
                    [
3487
                        "<cite>{$origin->getTitle(true)}</cite>",
3488
                        $origin->getUser()->getCompleteName(),
3489
                    ]
3490
                );
3491
            }
3492
        } elseif (Portfolio::TYPE_COMMENT === $item->getOriginType()) {
3493
            $origin = $em->find(PortfolioComment::class, $item->getOrigin());
3494
3495
            if ($origin) {
3496
                $originContent = $origin->getContent();
3497
                $originContentFooter = vsprintf(
3498
                    get_lang('OriginallyCommentedByXUserInYItem'),
3499
                    [
3500
                        $origin->getAuthor()->getCompleteName(),
3501
                        "<cite>{$origin->getItem()->getTitle(true)}</cite>",
3502
                    ]
3503
                );
3504
            }
3505
        }
3506
3507
        if ($originContent) {
3508
            return "<figure>
3509
                    <blockquote>$originContent</blockquote>
3510
                    <figcaption style=\"margin-bottom: 10px;\">$originContentFooter</figcaption>
3511
                </figure>
3512
                <div class=\"clearfix\">".Security::remove_XSS($item->getContent()).'</div>'
3513
            ;
3514
        }
3515
3516
        return Security::remove_XSS($item->getContent());
3517
    }
3518
3519
    private function getItemsInHtmlFormatted(array $items): array
3520
    {
3521
        $itemsHtml = [];
3522
3523
        /** @var Portfolio $item */
3524
        foreach ($items as $item) {
3525
            $itemCourse = $item->getCourse();
3526
            $itemSession = $item->getSession();
3527
3528
            $creationDate = api_convert_and_format_date($item->getCreationDate());
3529
            $updateDate = api_convert_and_format_date($item->getUpdateDate());
3530
3531
            $metadata = '<ul class="list-unstyled text-muted">';
3532
3533
            if ($itemSession) {
3534
                $metadata .= '<li>'.get_lang('Course').': '.$itemSession->getName().' ('
3535
                    .$itemCourse->getTitle().') </li>';
3536
            } elseif ($itemCourse) {
3537
                $metadata .= '<li>'.get_lang('Course').': '.$itemCourse->getTitle().'</li>';
3538
            }
3539
3540
            $metadata .= '<li>'.sprintf(get_lang('CreationDateXDate'), $creationDate).'</li>';
3541
3542
            if ($itemCourse) {
3543
                $propertyInfo = api_get_item_property_info(
3544
                    $itemCourse->getId(),
3545
                    TOOL_PORTFOLIO,
3546
                    $item->getId(),
3547
                    $itemSession ? $itemSession->getId() : 0
3548
                );
3549
3550
                if ($propertyInfo) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $propertyInfo 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...
3551
                    $metadata .= '<li>'
3552
                        .sprintf(
3553
                            get_lang('UpdatedOnDateXByUserY'),
3554
                            api_convert_and_format_date($propertyInfo['lastedit_date'], DATE_TIME_FORMAT_LONG),
3555
                            api_get_user_entity($propertyInfo['lastedit_user_id'])->getCompleteName()
3556
                        )
3557
                        .'</li>';
3558
                }
3559
            } else {
3560
                $metadata .= '<li>'.sprintf(get_lang('UpdateDateXDate'), $updateDate).'</li>';
3561
            }
3562
3563
            if ($item->getCategory()) {
3564
                $metadata .= '<li>'.sprintf(get_lang('CategoryXName'), $item->getCategory()->getTitle()).'</li>';
3565
            }
3566
3567
            $metadata .= '</ul>';
3568
3569
            $itemContent = $this->generateItemContent($item);
3570
3571
            $itemsHtml[] = Display::panel($itemContent, Security::remove_XSS($item->getTitle()), '', 'info', $metadata);
3572
        }
3573
3574
        return $itemsHtml;
3575
    }
3576
3577
    private function getCommentsInHtmlFormatted(array $comments): array
3578
    {
3579
        $commentsHtml = [];
3580
3581
        /** @var PortfolioComment $comment */
3582
        foreach ($comments as $comment) {
3583
            $item = $comment->getItem();
3584
            $date = api_convert_and_format_date($comment->getDate());
3585
3586
            $metadata = '<ul class="list-unstyled text-muted">';
3587
            $metadata .= '<li>'.sprintf(get_lang('DateXDate'), $date).'</li>';
3588
            $metadata .= '<li>'.sprintf(get_lang('PortfolioItemTitleXName'), Security::remove_XSS($item->getTitle()))
3589
                .'</li>';
3590
            $metadata .= '</ul>';
3591
3592
            $commentsHtml[] = Display::panel(
3593
                Security::remove_XSS($comment->getContent()),
3594
                '',
3595
                '',
3596
                'default',
3597
                $metadata
3598
            );
3599
        }
3600
3601
        return $commentsHtml;
3602
    }
3603
3604
    private function fixImagesSourcesToHtml(string $htmlContent): string
3605
    {
3606
        $doc = new DOMDocument();
3607
        @$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

3607
        /** @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...
3608
3609
        $elements = $doc->getElementsByTagName('img');
3610
3611
        if (empty($elements->length)) {
3612
            return $htmlContent;
3613
        }
3614
3615
        $webCoursePath = api_get_path(WEB_COURSE_PATH);
3616
        $webUploadPath = api_get_path(WEB_UPLOAD_PATH);
3617
3618
        /** @var \DOMElement $element */
3619
        foreach ($elements as $element) {
3620
            $src = trim($element->getAttribute('src'));
3621
3622
            if (strpos($src, 'http') === 0) {
3623
                continue;
3624
            }
3625
3626
            if (strpos($src, '/app/upload/') === 0) {
3627
                $element->setAttribute(
3628
                    'src',
3629
                    preg_replace('/\/app/upload\//', $webUploadPath, $src, 1)
3630
                );
3631
3632
                continue;
3633
            }
3634
3635
            if (strpos($src, '/courses/') === 0) {
3636
                $element->setAttribute(
3637
                    'src',
3638
                    preg_replace('/\/courses\//', $webCoursePath, $src, 1)
3639
                );
3640
3641
                continue;
3642
            }
3643
        }
3644
3645
        return $doc->saveHTML();
3646
    }
3647
3648
    private function formatZipIndexFile(HTML_Table $tblItems, HTML_Table $tblComments): string
3649
    {
3650
        $htmlContent = Display::page_header($this->owner->getCompleteNameWithUsername());
3651
        $htmlContent .= Display::page_subheader2(get_lang('PortfolioItems'));
3652
3653
        $htmlContent .= $tblItems->getRowCount() > 0
3654
            ? $tblItems->toHtml()
3655
            : Display::return_message(get_lang('NoItemsInYourPortfolio'), 'warning');
3656
3657
        $htmlContent .= Display::page_subheader2(get_lang('PortfolioCommentsMade'));
3658
3659
        $htmlContent .= $tblComments->getRowCount() > 0
3660
            ? $tblComments->toHtml()
3661
            : Display::return_message(get_lang('YouHaveNotCommented'), 'warning');
3662
3663
        $webAssetsPath = api_get_path(WEB_PUBLIC_PATH).'assets/';
3664
3665
        $doc = new DOMDocument();
3666
        @$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

3666
        /** @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...
3667
3668
        $stylesheet1 = $doc->createElement('link');
3669
        $stylesheet1->setAttribute('rel', 'stylesheet');
3670
        $stylesheet1->setAttribute('href', $webAssetsPath.'bootstrap/dist/css/bootstrap.min.css');
3671
        $stylesheet2 = $doc->createElement('link');
3672
        $stylesheet2->setAttribute('rel', 'stylesheet');
3673
        $stylesheet2->setAttribute('href', $webAssetsPath.'fontawesome/css/font-awesome.min.css');
3674
        $stylesheet3 = $doc->createElement('link');
3675
        $stylesheet3->setAttribute('rel', 'stylesheet');
3676
        $stylesheet3->setAttribute('href', ChamiloApi::getEditorDocStylePath());
3677
3678
        $head = $doc->createElement('head');
3679
        $head->appendChild($stylesheet1);
3680
        $head->appendChild($stylesheet2);
3681
        $head->appendChild($stylesheet3);
3682
3683
        $doc->documentElement->insertBefore(
3684
            $head,
3685
            $doc->getElementsByTagName('body')->item(0)
3686
        );
3687
3688
        return $doc->saveHTML();
3689
    }
3690
3691
    /**
3692
     * It parsers a title for a variable in lang.
3693
     *
3694
     * @param $defaultDisplayText
3695
     *
3696
     * @return string
3697
     */
3698
    private function getLanguageVariable($defaultDisplayText)
3699
    {
3700
        $variableLanguage = api_replace_dangerous_char(strtolower($defaultDisplayText));
3701
        $variableLanguage = preg_replace('/[^A-Za-z0-9\_]/', '', $variableLanguage); // Removes special chars except underscore.
3702
        if (is_numeric($variableLanguage[0])) {
3703
            $variableLanguage = '_'.$variableLanguage;
3704
        }
3705
        $variableLanguage = api_underscore_to_camel_case($variableLanguage);
3706
3707
        return $variableLanguage;
3708
    }
3709
3710
    /**
3711
     * It translates the text as parameter.
3712
     *
3713
     * @param $defaultDisplayText
3714
     *
3715
     * @return mixed
3716
     */
3717
    private function translateDisplayName($defaultDisplayText)
3718
    {
3719
        $variableLanguage = $this->getLanguageVariable($defaultDisplayText);
3720
3721
        return isset($GLOBALS[$variableLanguage]) ? $GLOBALS[$variableLanguage] : $defaultDisplayText;
3722
    }
3723
3724
    private function getCommentsForIndex(FormValidator $frmFilterList = null): array
3725
    {
3726
        if (!$frmFilterList->validate()) {
0 ignored issues
show
Bug introduced by
The method validate() 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

3726
        if (!$frmFilterList->/** @scrutinizer ignore-call */ validate()) {

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...
3727
            return [];
3728
        }
3729
3730
        $values = $frmFilterList->exportValues();
3731
3732
        if (empty($values['date']) && empty($values['text'])) {
3733
            return [];
3734
        }
3735
3736
        $queryBuilder = $this->em->createQueryBuilder()
3737
            ->select('c')
3738
            ->from(PortfolioComment::class, 'c')
3739
        ;
3740
3741
        if (!empty($values['date'])) {
3742
            $queryBuilder
3743
                ->andWhere('c.date >= :date')
3744
                ->setParameter(':date', api_get_utc_datetime($values['date'], false, true))
3745
            ;
3746
        }
3747
3748
        if (!empty($values['text'])) {
3749
            $queryBuilder
3750
                ->andWhere('c.content LIKE :text')
3751
                ->setParameter('text', '%'.$values['text'].'%')
3752
            ;
3753
        }
3754
3755
        $queryBuilder->orderBy('c.date', 'DESC');
3756
3757
        return $queryBuilder->getQuery()->getResult();
3758
    }
3759
}
3760