PortfolioController   F
last analyzed

Complexity

Total Complexity 415

Size/Duplication

Total Lines 4512
Duplicated Lines 0 %

Importance

Changes 5
Bugs 1 Features 2
Metric Value
wmc 415
eloc 2612
dl 0
loc 4512
rs 0.8
c 5
b 1
f 2

58 Methods

Rating   Name   Duplication   Size   Complexity  
A showHideCategory() 0 17 2
F editItem() 0 194 16
A addCategory() 0 71 4
A deleteCategory() 0 15 2
A translateCategory() 0 76 3
A __construct() 0 13 3
F listCategories() 0 93 13
B editCategory() 0 84 5
A deleteItem() 0 20 2
A showHideItem() 0 28 5
C addItem() 0 240 10
A copyComment() 0 28 1
A copyItem() 0 30 1
A markImportantCommentInItem() 0 19 2
F view() 0 373 42
B teacherCopyComment() 0 84 5
B teacherCopyItem() 0 80 5
F exportPdf() 0 118 15
F index() 0 152 19
F details() 0 361 38
B generateAttachmentList() 0 63 8
C commentVisibilityChooser() 0 131 10
B processAttachments() 0 63 8
A getCommentsInHtmlFormatted() 0 25 2
A createFormTagFilter() 0 49 5
C itemVisibilityChooser() 0 132 10
F getItemsForIndex() 0 173 27
A qualifyComment() 0 72 2
A markAsTemplate() 0 21 3
B generateItemContent() 0 51 7
A getHighlightedItems() 0 63 4
A deleteTag() 0 22 2
B deleteAttachment() 0 63 9
C listTags() 0 150 10
B editComment() 0 100 5
A markAsHighlighted() 0 28 4
B downloadAttachment() 0 47 7
A renderView() 0 29 5
B createCommentForm() 0 108 5
A addAttachmentsFieldToForm() 0 21 1
A getCategoriesForIndex() 0 16 5
A categoryBelongToOwner() 0 7 2
A blockIsNotAllowed() 0 4 2
B createFormStudentFilter() 0 86 6
A commentBelongsToOwner() 0 3 1
A itemBelongToOwner() 0 7 2
B getItemsInHtmlFormatted() 0 56 8
A deleteComment() 0 20 2
A isAllowed() 0 18 5
A markAsTemplateComment() 0 21 3
A qualifyItem() 0 65 2
A translateDisplayName() 0 5 2
C fixMediaSourcesToHtml() 0 64 14
B getCommentsForIndex() 0 66 8
F exportZip() 0 213 17
A getLabelForCommentDate() 0 26 4
A formatZipIndexFile() 0 41 3
A getLanguageVariable() 0 10 2

How to fix   Complexity   

Complex Class

Complex classes like PortfolioController often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use PortfolioController, and based on these observations, apply Extract Interface, too.

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\CourseBundle\Entity\CItemProperty;
16
use Chamilo\UserBundle\Entity\User;
17
use Doctrine\ORM\Query\Expr\Join;
18
use Mpdf\MpdfException;
19
use Symfony\Component\Filesystem\Filesystem;
20
use Symfony\Component\HttpFoundation\Request as HttpRequest;
21
22
/**
23
 * Class PortfolioController.
24
 */
25
class PortfolioController
26
{
27
    /**
28
     * @var string
29
     */
30
    public $baseUrl;
31
    /**
32
     * @var CourseEntity|null
33
     */
34
    private $course;
35
    /**
36
     * @var \Chamilo\CoreBundle\Entity\Session|null
37
     */
38
    private $session;
39
    /**
40
     * @var \Chamilo\UserBundle\Entity\User
41
     */
42
    private $owner;
43
    /**
44
     * @var \Doctrine\ORM\EntityManager
45
     */
46
    private $em;
47
    /**
48
     * @var bool
49
     */
50
    private $advancedSharingEnabled;
51
52
    /**
53
     * PortfolioController constructor.
54
     */
55
    public function __construct()
56
    {
57
        $this->em = Database::getManager();
58
59
        $this->owner = api_get_user_entity(api_get_user_id());
60
        $this->course = api_get_course_entity(api_get_course_int_id());
61
        $this->session = api_get_session_entity(api_get_session_id());
62
63
        $cidreq = api_get_cidreq();
64
        $this->baseUrl = api_get_self().'?'.($cidreq ? $cidreq.'&' : '');
65
66
        $this->advancedSharingEnabled = true === api_get_configuration_value('portfolio_advanced_sharing')
67
            && $this->course;
68
    }
69
70
    /**
71
     * @throws \Doctrine\ORM\ORMException
72
     * @throws \Doctrine\ORM\OptimisticLockException
73
     */
74
    public function translateCategory($category, $languages, $languageId)
75
    {
76
        global $interbreadcrumb;
77
78
        $originalName = $category->getTitle();
79
        $variableLanguage = '$'.$this->getLanguageVariable($originalName);
80
81
        $translateUrl = api_get_path(WEB_AJAX_PATH).'lang.ajax.php?a=translate_portfolio_category&sec_token='.Security::get_token();
82
        $form = new FormValidator('new_lang_variable', 'POST', $translateUrl);
83
        $form->addHeader(get_lang('AddWordForTheSubLanguage'));
84
        $form->addText('variable_language', get_lang('LanguageVariable'), false);
85
        $form->addText('original_name', get_lang('OriginalName'), false);
86
87
        $languagesOptions = [0 => get_lang('None')];
88
        foreach ($languages as $language) {
89
            $languagesOptions[$language->getId()] = $language->getOriginalName();
90
        }
91
92
        $form->addSelect(
93
            'sub_language',
94
            [get_lang('SubLanguage'), get_lang('OnlyActiveSubLanguagesAreListed')],
95
            $languagesOptions
96
        );
97
98
        if ($languageId) {
99
            $languageInfo = api_get_language_info($languageId);
100
            $form->addText(
101
                'new_language',
102
                [get_lang('Translation'), get_lang('IfThisTranslationExistsThisWillReplaceTheTerm')]
103
            );
104
105
            $form->addHidden('category_id', $category->getId());
106
            $form->addHidden('id', $languageInfo['parent_id']);
107
            $form->addHidden('sub', $languageInfo['id']);
108
            $form->addHidden('sub_language_id', $languageInfo['id']);
109
            $form->addHidden('redirect', true);
110
            $form->addButtonSave(get_lang('Save'));
111
        }
112
113
        $form->setDefaults([
114
            'variable_language' => $variableLanguage,
115
            'original_name' => $originalName,
116
            'sub_language' => $languageId,
117
        ]);
118
        $form->addRule('sub_language', get_lang('Required'), 'required');
119
        $form->freeze(['variable_language', 'original_name']);
120
121
        $interbreadcrumb[] = [
122
            'name' => get_lang('Portfolio'),
123
            'url' => $this->baseUrl,
124
        ];
125
        $interbreadcrumb[] = [
126
            'name' => get_lang('Categories'),
127
            'url' => $this->baseUrl.'action=list_categories&parent_id='.$category->getParentId(),
128
        ];
129
        $interbreadcrumb[] = [
130
            'name' => Security::remove_XSS($category->getTitle()),
131
            'url' => $this->baseUrl.'action=edit_category&id='.$category->getId(),
132
        ];
133
134
        $actions = [];
135
        $actions[] = Display::url(
136
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
137
            $this->baseUrl.'action=edit_category&id='.$category->getId()
138
        );
139
140
        $js = '<script>
141
            $(function() {
142
              $("select[name=\'sub_language\']").on("change", function () {
143
                    location.href += "&sub_language=" + this.value;
144
                });
145
            });
146
        </script>';
147
        $content = $form->returnForm();
148
149
        $this->renderView($content.$js, get_lang('TranslateCategory'), $actions);
150
    }
151
152
    /**
153
     * @throws \Doctrine\ORM\ORMException
154
     * @throws \Doctrine\ORM\OptimisticLockException
155
     */
156
    public function listCategories()
157
    {
158
        global $interbreadcrumb;
159
160
        $parentId = isset($_REQUEST['parent_id']) ? (int) $_REQUEST['parent_id'] : 0;
161
        $table = new HTML_Table(['class' => 'table table-hover table-striped data_table']);
162
        $headers = [
163
            get_lang('Title'),
164
            get_lang('Description'),
165
        ];
166
        if ($parentId === 0) {
167
            $headers[] = get_lang('SubCategories');
168
        }
169
        $headers[] = get_lang('Actions');
170
171
        $column = 0;
172
        foreach ($headers as $header) {
173
            $table->setHeaderContents(0, $column, $header);
174
            $column++;
175
        }
176
        $currentUserId = api_get_user_id();
177
        $row = 1;
178
        $categories = $this->getCategoriesForIndex(null, $parentId);
179
180
        foreach ($categories as $category) {
181
            $column = 0;
182
            $subcategories = $this->getCategoriesForIndex(null, $category->getId());
183
            $linkSubCategories = $category->getTitle();
184
            if (count($subcategories) > 0) {
185
                $linkSubCategories = Display::url(
186
                    $category->getTitle(),
187
                    $this->baseUrl.'action=list_categories&parent_id='.$category->getId()
188
                );
189
            }
190
            $table->setCellContents($row, $column++, $linkSubCategories);
191
            $table->setCellContents($row, $column++, strip_tags($category->getDescription()));
192
            if ($parentId === 0) {
193
                $table->setCellContents($row, $column++, count($subcategories));
194
            }
195
196
            // Actions
197
            $links = null;
198
            // Edit action
199
            $url = $this->baseUrl.'action=edit_category&id='.$category->getId();
200
            $links .= Display::url(Display::return_icon('edit.png', get_lang('Edit')), $url).'&nbsp;';
201
            // Visible action : if active
202
            if ($category->isVisible() != 0) {
203
                $url = $this->baseUrl.'action=hide_category&id='.$category->getId();
204
                $links .= Display::url(Display::return_icon('visible.png', get_lang('Hide')), $url).'&nbsp;';
205
            } else { // else if not active
206
                $url = $this->baseUrl.'action=show_category&id='.$category->getId();
207
                $links .= Display::url(Display::return_icon('invisible.png', get_lang('Show')), $url).'&nbsp;';
208
            }
209
            // Delete action
210
            $url = $this->baseUrl.'action=delete_category&id='.$category->getId();
211
            $links .= Display::url(Display::return_icon('delete.png', get_lang('Delete')), $url, ['onclick' => 'javascript:if(!confirm(\''.get_lang('AreYouSureToDeleteJS').'\')) return false;']);
212
213
            $table->setCellContents($row, $column++, $links);
214
            $row++;
215
        }
216
217
        $interbreadcrumb[] = [
218
            'name' => get_lang('Portfolio'),
219
            'url' => $this->baseUrl,
220
        ];
221
        if ($parentId > 0) {
222
            $interbreadcrumb[] = [
223
                'name' => get_lang('Categories'),
224
                'url' => $this->baseUrl.'action=list_categories',
225
            ];
226
        }
227
228
        $actions = [];
229
        $actions[] = Display::url(
230
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
231
            $this->baseUrl.($parentId > 0 ? 'action=list_categories' : '')
232
        );
233
        if ($currentUserId == $this->owner->getId() && $parentId === 0) {
234
            $actions[] = Display::url(
235
                Display::return_icon('new_folder.png', get_lang('AddCategory'), [], ICON_SIZE_MEDIUM),
236
                $this->baseUrl.'action=add_category'
237
            );
238
        }
239
        $content = $table->toHtml();
240
241
        $pageTitle = get_lang('Categories');
242
        if ($parentId > 0) {
243
            $em = Database::getManager();
244
            $parentCategory = $em->find('ChamiloCoreBundle:PortfolioCategory', $parentId);
245
            $pageTitle = $parentCategory->getTitle().' : '.get_lang('SubCategories');
246
        }
247
248
        $this->renderView($content, $pageTitle, $actions);
249
    }
250
251
    /**
252
     * @throws \Doctrine\ORM\ORMException
253
     * @throws \Doctrine\ORM\OptimisticLockException
254
     */
255
    public function addCategory()
256
    {
257
        global $interbreadcrumb;
258
259
        Display::addFlash(
260
            Display::return_message(get_lang('PortfolioCategoryFieldHelp'), 'info')
261
        );
262
263
        $form = new FormValidator('add_category', 'post', "{$this->baseUrl}&action=add_category");
264
265
        if (api_get_configuration_value('save_titles_as_html')) {
266
            $form->addHtmlEditor('title', get_lang('Title'), true, false, ['ToolbarSet' => 'TitleAsHtml']);
267
        } else {
268
            $form->addText('title', get_lang('Title'));
269
            $form->applyFilter('title', 'trim');
270
        }
271
272
        $form->addHtmlEditor('description', get_lang('Description'), false, false, ['ToolbarSet' => 'Minimal']);
273
274
        $parentSelect = $form->addSelect(
275
            'parent_id',
276
            get_lang('ParentCategory')
277
        );
278
        $parentSelect->addOption(get_lang('Level0'), 0);
279
        $currentUserId = api_get_user_id();
280
        $categories = $this->getCategoriesForIndex(null, 0);
281
        foreach ($categories as $category) {
282
            $parentSelect->addOption($category->getTitle(), $category->getId());
283
        }
284
285
        $form->addButtonCreate(get_lang('Create'));
286
287
        if ($form->validate()) {
288
            $values = $form->exportValues();
289
290
            $category = new PortfolioCategory();
291
            $category
292
                ->setTitle($values['title'])
293
                ->setDescription($values['description'])
294
                ->setParentId($values['parent_id'])
295
                ->setUser($this->owner);
296
297
            $this->em->persist($category);
298
            $this->em->flush();
299
300
            Display::addFlash(
301
                Display::return_message(get_lang('CategoryAdded'), 'success')
302
            );
303
304
            header("Location: {$this->baseUrl}action=list_categories");
305
            exit;
306
        }
307
308
        $interbreadcrumb[] = [
309
            'name' => get_lang('Portfolio'),
310
            'url' => $this->baseUrl,
311
        ];
312
        $interbreadcrumb[] = [
313
            'name' => get_lang('Categories'),
314
            'url' => $this->baseUrl.'action=list_categories',
315
        ];
316
317
        $actions = [];
318
        $actions[] = Display::url(
319
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
320
            $this->baseUrl.'action=list_categories'
321
        );
322
323
        $content = $form->returnForm();
324
325
        $this->renderView($content, get_lang('AddCategory'), $actions);
326
    }
327
328
    /**
329
     * @throws \Doctrine\ORM\ORMException
330
     * @throws \Doctrine\ORM\OptimisticLockException
331
     * @throws \Exception
332
     */
333
    public function editCategory(PortfolioCategory $category)
334
    {
335
        global $interbreadcrumb;
336
337
        if (!api_is_platform_admin()) {
338
            api_not_allowed(true);
339
        }
340
341
        Display::addFlash(
342
            Display::return_message(get_lang('PortfolioCategoryFieldHelp'), 'info')
343
        );
344
345
        $form = new FormValidator(
346
            'edit_category',
347
            'post',
348
            $this->baseUrl."action=edit_category&id={$category->getId()}"
349
        );
350
351
        if (api_get_configuration_value('save_titles_as_html')) {
352
            $form->addHtmlEditor('title', get_lang('Title'), true, false, ['ToolbarSet' => 'TitleAsHtml']);
353
        } else {
354
            $translateUrl = $this->baseUrl.'action=translate_category&id='.$category->getId();
355
            $translateButton = Display::toolbarButton(get_lang('TranslateThisTerm'), $translateUrl, 'language', 'link');
356
            $form->addText(
357
                'title',
358
                [get_lang('Title'), $translateButton]
359
            );
360
            $form->applyFilter('title', 'trim');
361
        }
362
363
        $form->addHtmlEditor('description', get_lang('Description'), false, false, ['ToolbarSet' => 'Minimal']);
364
        $form->addButtonUpdate(get_lang('Update'));
365
        $form->setDefaults(
366
            [
367
                'title' => $category->getTitle(),
368
                'description' => $category->getDescription(),
369
            ]
370
        );
371
372
        if ($form->validate()) {
373
            $values = $form->exportValues();
374
375
            $category
376
                ->setTitle($values['title'])
377
                ->setDescription($values['description']);
378
379
            $this->em->persist($category);
380
            $this->em->flush();
381
382
            Display::addFlash(
383
                Display::return_message(get_lang('Updated'), 'success')
384
            );
385
386
            header("Location: {$this->baseUrl}action=list_categories&parent_id=".$category->getParentId());
387
            exit;
388
        }
389
390
        $interbreadcrumb[] = [
391
            'name' => get_lang('Portfolio'),
392
            'url' => $this->baseUrl,
393
        ];
394
        $interbreadcrumb[] = [
395
            'name' => get_lang('Categories'),
396
            'url' => $this->baseUrl.'action=list_categories',
397
        ];
398
        if ($category->getParentId() > 0) {
399
            $em = Database::getManager();
400
            $parentCategory = $em->find('ChamiloCoreBundle:PortfolioCategory', $category->getParentId());
401
            $pageTitle = $parentCategory->getTitle().' : '.get_lang('SubCategories');
402
            $interbreadcrumb[] = [
403
                'name' => Security::remove_XSS($pageTitle),
404
                'url' => $this->baseUrl.'action=list_categories&parent_id='.$category->getParentId(),
405
            ];
406
        }
407
408
        $actions = [];
409
        $actions[] = Display::url(
410
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
411
            $this->baseUrl.'action=list_categories&parent_id='.$category->getParentId()
412
        );
413
414
        $content = $form->returnForm();
415
416
        $this->renderView($content, get_lang('EditCategory'), $actions);
417
    }
418
419
    /**
420
     * @throws \Doctrine\ORM\ORMException
421
     * @throws \Doctrine\ORM\OptimisticLockException
422
     */
423
    public function showHideCategory(PortfolioCategory $category)
424
    {
425
        if (!$this->categoryBelongToOwner($category)) {
426
            api_not_allowed(true);
427
        }
428
429
        $category->setIsVisible(!$category->isVisible());
430
431
        $this->em->persist($category);
432
        $this->em->flush();
433
434
        Display::addFlash(
435
            Display::return_message(get_lang('VisibilityChanged'), 'success')
436
        );
437
438
        header("Location: {$this->baseUrl}action=list_categories");
439
        exit;
440
    }
441
442
    /**
443
     * @throws \Doctrine\ORM\ORMException
444
     * @throws \Doctrine\ORM\OptimisticLockException
445
     */
446
    public function deleteCategory(PortfolioCategory $category)
447
    {
448
        if (!api_is_platform_admin()) {
449
            api_not_allowed(true);
450
        }
451
452
        $this->em->remove($category);
453
        $this->em->flush();
454
455
        Display::addFlash(
456
            Display::return_message(get_lang('CategoryDeleted'), 'success')
457
        );
458
459
        header("Location: {$this->baseUrl}action=list_categories");
460
        exit;
461
    }
462
463
    /**
464
     * @throws \Doctrine\ORM\ORMException
465
     * @throws \Doctrine\ORM\OptimisticLockException
466
     * @throws \Doctrine\ORM\TransactionRequiredException
467
     * @throws \Exception
468
     */
469
    public function addItem()
470
    {
471
        global $interbreadcrumb;
472
473
        $this->blockIsNotAllowed();
474
475
        $templates = $this->em
476
            ->getRepository(Portfolio::class)
477
            ->findBy(
478
                [
479
                    'isTemplate' => true,
480
                    'course' => $this->course,
481
                    'session' => $this->session,
482
                    'user' => $this->owner,
483
                ]
484
            );
485
486
        $form = new FormValidator('add_portfolio', 'post', $this->baseUrl.'action=add_item');
487
        $form->addSelectFromCollection(
488
            'template',
489
            [
490
                get_lang('Template'),
491
                null,
492
                '<span id="portfolio-spinner" class="fa fa-fw fa-spinner fa-spin" style="display: none;"
493
                    aria-hidden="true" aria-label="'.get_lang('Loading').'"></span>',
494
            ],
495
            $templates,
496
            [],
497
            true,
498
            'getTitle'
499
        );
500
501
        if (api_get_configuration_value('save_titles_as_html')) {
502
            $form->addHtmlEditor('title', get_lang('Title'), true, false, ['ToolbarSet' => 'TitleAsHtml']);
503
        } else {
504
            $form->addText('title', get_lang('Title'));
505
            $form->applyFilter('title', 'trim');
506
        }
507
        $editorConfig = [
508
            'ToolbarSet' => 'Documents',
509
            'Width' => '100%',
510
            'Height' => '400',
511
            'cols-size' => [2, 10, 0],
512
        ];
513
        $form->addHtmlEditor('content', get_lang('Content'), true, false, $editorConfig);
514
515
        $categoriesSelect = $form->addSelect(
516
            'category',
517
            [get_lang('Category'), get_lang('PortfolioCategoryFieldHelp')]
518
        );
519
        $categoriesSelect->addOption(get_lang('SelectACategory'), 0);
520
        $parentCategories = $this->getCategoriesForIndex(null, 0);
521
        foreach ($parentCategories as $parentCategory) {
522
            $categoriesSelect->addOption($this->translateDisplayName($parentCategory->getTitle()), $parentCategory->getId());
523
            $subCategories = $this->getCategoriesForIndex(null, $parentCategory->getId());
524
            if (count($subCategories) > 0) {
525
                foreach ($subCategories as $subCategory) {
526
                    $categoriesSelect->addOption(' &mdash; '.$this->translateDisplayName($subCategory->getTitle()), $subCategory->getId());
527
                }
528
            }
529
        }
530
531
        $extraField = new ExtraField('portfolio');
532
        $extra = $extraField->addElements(
533
            $form,
534
            0,
535
            $this->course ? [] : ['tags']
536
        );
537
538
        $this->addAttachmentsFieldToForm($form);
539
540
        $form->addButtonCreate(get_lang('Create'));
541
542
        if ($form->validate()) {
543
            $values = $form->exportValues();
544
            $currentTime = new DateTime(
545
                api_get_utc_datetime(),
546
                new DateTimeZone('UTC')
547
            );
548
549
            $portfolio = new Portfolio();
550
            $portfolio
551
                ->setTitle($values['title'])
552
                ->setContent($values['content'])
553
                ->setUser($this->owner)
554
                ->setCourse($this->course)
555
                ->setSession($this->session)
556
                ->setCategory(
557
                    $this->em->find('ChamiloCoreBundle:PortfolioCategory', $values['category'])
558
                )
559
                ->setCreationDate($currentTime)
560
                ->setUpdateDate($currentTime);
561
562
            $this->em->persist($portfolio);
563
            $this->em->flush();
564
565
            $values['item_id'] = $portfolio->getId();
566
567
            $extraFieldValue = new ExtraFieldValue('portfolio');
568
            $extraFieldValue->saveFieldValues($values);
569
570
            $this->processAttachments(
571
                $form,
572
                $portfolio->getUser(),
573
                $portfolio->getId(),
574
                PortfolioAttachment::TYPE_ITEM
575
            );
576
577
            $hook = HookPortfolioItemAdded::create();
578
            $hook->setEventData(['portfolio' => $portfolio]);
579
            $hook->notifyItemAdded();
580
581
            if (1 == api_get_course_setting('email_alert_teachers_new_post')) {
582
                if ($this->session) {
583
                    $messageCourseTitle = "{$this->course->getTitle()} ({$this->session->getName()})";
584
585
                    $teachers = SessionManager::getCoachesByCourseSession(
586
                        $this->session->getId(),
587
                        $this->course->getId()
588
                    );
589
                    $userIdListToSend = array_values($teachers);
590
                } else {
591
                    $messageCourseTitle = $this->course->getTitle();
592
593
                    $teachers = CourseManager::get_teacher_list_from_course_code($this->course->getCode());
594
595
                    $userIdListToSend = array_keys($teachers);
596
                }
597
598
                $messageSubject = sprintf(get_lang('PortfolioAlertNewPostSubject'), $messageCourseTitle);
599
                $messageContent = sprintf(
600
                    get_lang('PortfolioAlertNewPostContent'),
601
                    $this->owner->getCompleteName(),
602
                    $messageCourseTitle,
603
                    $this->baseUrl.http_build_query(['action' => 'view', 'id' => $portfolio->getId()])
604
                );
605
                $messageContent .= '<br><br><dl>'
606
                    .'<dt>'.Security::remove_XSS($portfolio->getTitle()).'</dt>'
607
                    .'<dd>'.$portfolio->getExcerpt().'</dd>'.'</dl>';
608
609
                foreach ($userIdListToSend as $userIdToSend) {
610
                    MessageManager::send_message_simple(
611
                        $userIdToSend,
612
                        $messageSubject,
613
                        $messageContent,
614
                        0,
615
                        false,
616
                        false,
617
                        [],
618
                        false
619
                    );
620
                }
621
            }
622
623
            Display::addFlash(
624
                Display::return_message(get_lang('PortfolioItemAdded'), 'success')
625
            );
626
627
            header("Location: $this->baseUrl");
628
            exit;
629
        }
630
631
        $interbreadcrumb[] = [
632
            'name' => get_lang('Portfolio'),
633
            'url' => $this->baseUrl,
634
        ];
635
636
        $actions = [];
637
        $actions[] = Display::url(
638
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
639
            $this->baseUrl
640
        );
641
        $actions[] = '<a id="hide_bar_template" href="#" role="button">'.
642
            Display::return_icon('expand.png', get_lang('Expand'), ['id' => 'expand'], ICON_SIZE_MEDIUM).
643
            Display::return_icon('contract.png', get_lang('Collapse'), ['id' => 'contract', 'class' => 'hide'], ICON_SIZE_MEDIUM).'</a>';
644
645
        $js = '<script>
646
            $(function() {
647
                $(".scrollbar-light").scrollbar();
648
                $(".scroll-wrapper").css("height", "550px");
649
                expandColumnToogle("#hide_bar_template", {
650
                    selector: "#template_col",
651
                    width: 3
652
                }, {
653
                    selector: "#doc_form",
654
                    width: 9
655
                });
656
                CKEDITOR.on("instanceReady", function (e) {
657
                    showTemplates();
658
                });
659
                $(window).on("load", function () {
660
                    $("input[name=\'title\']").focus();
661
                });
662
                $(\'#add_portfolio_template\').on(\'change\', function () {
663
                    $(\'#portfolio-spinner\').show();
664
665
                    $.getJSON(_p.web_ajax + \'portfolio.ajax.php?a=find_template&item=\' + this.value)
666
                        .done(function(response) {
667
                            if (CKEDITOR.instances.title) {
668
                                CKEDITOR.instances.title.setData(response.title);
669
                            } else {
670
                                document.getElementById(\'add_portfolio_title\').value = response.title;
671
                            }
672
673
                            CKEDITOR.instances.content.setData(response.content);
674
                        })
675
                        .fail(function () {
676
                            if (CKEDITOR.instances.title) {
677
                                CKEDITOR.instances.title.setData(\'\');
678
                            } else {
679
                                document.getElementById(\'add_portfolio_title\').value = \'\';
680
                            }
681
682
                            CKEDITOR.instances.content.setData(\'\');
683
                        })
684
                        .always(function() {
685
                          $(\'#portfolio-spinner\').hide();
686
                        });
687
                });
688
                '.$extra['jquery_ready_content'].'
689
            });
690
        </script>';
691
        $content = '<div class="page-create">
692
            <div class="row" style="overflow:hidden">
693
            <div id="template_col" class="col-md-3">
694
                <div class="panel panel-default">
695
                <div class="panel-body">
696
                    <div id="frmModel" class="items-templates scrollbar-light"></div>
697
                </div>
698
                </div>
699
            </div>
700
            <div id="doc_form" class="col-md-9">
701
                '.$form->returnForm().'
702
            </div>
703
          </div></div>';
704
705
        $this->renderView(
706
            $content.$js,
707
            get_lang('AddPortfolioItem'),
708
            $actions
709
        );
710
    }
711
712
    /**
713
     * @throws \Doctrine\ORM\ORMException
714
     * @throws \Doctrine\ORM\OptimisticLockException
715
     * @throws \Doctrine\ORM\TransactionRequiredException
716
     * @throws \Exception
717
     */
718
    public function editItem(Portfolio $item)
719
    {
720
        global $interbreadcrumb;
721
722
        if (!api_is_allowed_to_edit() && !$this->itemBelongToOwner($item)) {
723
            api_not_allowed(true);
724
        }
725
726
        $itemCourse = $item->getCourse();
727
        $itemSession = $item->getSession();
728
729
        $form = new FormValidator('edit_portfolio', 'post', $this->baseUrl."action=edit_item&id={$item->getId()}");
730
731
        if (api_get_configuration_value('save_titles_as_html')) {
732
            $form->addHtmlEditor('title', get_lang('Title'), true, false, ['ToolbarSet' => 'TitleAsHtml']);
733
        } else {
734
            $form->addText('title', get_lang('Title'));
735
            $form->applyFilter('title', 'trim');
736
        }
737
738
        if ($item->getOrigin()) {
739
            if (Portfolio::TYPE_ITEM === $item->getOriginType()) {
740
                $origin = $this->em->find(Portfolio::class, $item->getOrigin());
741
742
                $form->addLabel(
743
                    sprintf(get_lang('PortfolioItemFromXUser'), $origin->getUser()->getCompleteName()),
744
                    Display::panel(
745
                        Security::remove_XSS($origin->getContent())
746
                    )
747
                );
748
            } elseif (Portfolio::TYPE_COMMENT === $item->getOriginType()) {
749
                $origin = $this->em->find(PortfolioComment::class, $item->getOrigin());
750
751
                $form->addLabel(
752
                    sprintf(get_lang('PortfolioCommentFromXUser'), $origin->getAuthor()->getCompleteName()),
753
                    Display::panel(
754
                        Security::remove_XSS($origin->getContent())
755
                    )
756
                );
757
            }
758
        }
759
        $editorConfig = [
760
            'ToolbarSet' => 'Documents',
761
            'Width' => '100%',
762
            'Height' => '400',
763
            'cols-size' => [2, 10, 0],
764
        ];
765
        $form->addHtmlEditor('content', get_lang('Content'), true, false, $editorConfig);
766
        $categoriesSelect = $form->addSelect(
767
            'category',
768
            [get_lang('Category'), get_lang('PortfolioCategoryFieldHelp')]
769
        );
770
        $categoriesSelect->addOption(get_lang('SelectACategory'), 0);
771
        $parentCategories = $this->getCategoriesForIndex(null, 0);
772
        foreach ($parentCategories as $parentCategory) {
773
            $categoriesSelect->addOption($this->translateDisplayName($parentCategory->getTitle()), $parentCategory->getId());
774
            $subCategories = $this->getCategoriesForIndex(null, $parentCategory->getId());
775
            if (count($subCategories) > 0) {
776
                foreach ($subCategories as $subCategory) {
777
                    $categoriesSelect->addOption(' &mdash; '.$this->translateDisplayName($subCategory->getTitle()), $subCategory->getId());
778
                }
779
            }
780
        }
781
782
        $extraField = new ExtraField('portfolio');
783
        $extra = $extraField->addElements(
784
            $form,
785
            $item->getId(),
786
            $this->course ? [] : ['tags']
787
        );
788
789
        $attachmentList = $this->generateAttachmentList($item, false);
790
791
        if (!empty($attachmentList)) {
792
            $form->addLabel(get_lang('AttachmentFiles'), $attachmentList);
793
        }
794
795
        $this->addAttachmentsFieldToForm($form);
796
797
        $form->addButtonUpdate(get_lang('Update'));
798
        $form->setDefaults(
799
            [
800
                'title' => $item->getTitle(),
801
                'content' => $item->getContent(),
802
                'category' => $item->getCategory() ? $item->getCategory()->getId() : '',
803
            ]
804
        );
805
806
        if ($form->validate()) {
807
            if ($itemCourse) {
808
                api_item_property_update(
809
                    api_get_course_info($itemCourse->getCode()),
810
                    TOOL_PORTFOLIO,
811
                    $item->getId(),
812
                    'PortfolioUpdated',
813
                    api_get_user_id(),
814
                    [],
815
                    null,
816
                    '',
817
                    '',
818
                    $itemSession ? $itemSession->getId() : 0
819
                );
820
            }
821
822
            $values = $form->exportValues();
823
            $currentTime = new DateTime(api_get_utc_datetime(), new DateTimeZone('UTC'));
824
825
            $item
826
                ->setTitle($values['title'])
827
                ->setContent($values['content'])
828
                ->setUpdateDate($currentTime)
829
                ->setCategory(
830
                    $this->em->find('ChamiloCoreBundle:PortfolioCategory', $values['category'])
831
                );
832
833
            $values['item_id'] = $item->getId();
834
835
            $extraFieldValue = new ExtraFieldValue('portfolio');
836
            $extraFieldValue->saveFieldValues($values);
837
838
            $this->em->persist($item);
839
            $this->em->flush();
840
841
            HookPortfolioItemEdited::create()
842
                ->setEventData(['item' => $item])
843
                ->notifyItemEdited()
844
            ;
845
846
            $this->processAttachments(
847
                $form,
848
                $item->getUser(),
849
                $item->getId(),
850
                PortfolioAttachment::TYPE_ITEM
851
            );
852
853
            Display::addFlash(
854
                Display::return_message(get_lang('ItemUpdated'), 'success')
855
            );
856
857
            header("Location: $this->baseUrl");
858
            exit;
859
        }
860
861
        $interbreadcrumb[] = [
862
            'name' => get_lang('Portfolio'),
863
            'url' => $this->baseUrl,
864
        ];
865
        $actions = [];
866
        $actions[] = Display::url(
867
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
868
            $this->baseUrl
869
        );
870
        $actions[] = '<a id="hide_bar_template" href="#" role="button">'.
871
            Display::return_icon('expand.png', get_lang('Expand'), ['id' => 'expand'], ICON_SIZE_MEDIUM).
872
            Display::return_icon('contract.png', get_lang('Collapse'), ['id' => 'contract', 'class' => 'hide'], ICON_SIZE_MEDIUM).'</a>';
873
874
        $js = '<script>
875
            $(function() {
876
                $(".scrollbar-light").scrollbar();
877
                $(".scroll-wrapper").css("height", "550px");
878
                expandColumnToogle("#hide_bar_template", {
879
                    selector: "#template_col",
880
                    width: 3
881
                }, {
882
                    selector: "#doc_form",
883
                    width: 9
884
                });
885
                CKEDITOR.on("instanceReady", function (e) {
886
                    showTemplates();
887
                });
888
                $(window).on("load", function () {
889
                    $("input[name=\'title\']").focus();
890
                });
891
                '.$extra['jquery_ready_content'].'
892
            });
893
        </script>';
894
        $content = '<div class="page-create">
895
            <div class="row" style="overflow:hidden">
896
            <div id="template_col" class="col-md-3">
897
                <div class="panel panel-default">
898
                <div class="panel-body">
899
                    <div id="frmModel" class="items-templates scrollbar-light"></div>
900
                </div>
901
                </div>
902
            </div>
903
            <div id="doc_form" class="col-md-9">
904
                '.$form->returnForm().'
905
            </div>
906
          </div></div>';
907
908
        $this->renderView(
909
            $content.$js,
910
            get_lang('EditPortfolioItem'),
911
            $actions
912
        );
913
    }
914
915
    /**
916
     * @throws \Doctrine\ORM\ORMException
917
     * @throws \Doctrine\ORM\OptimisticLockException
918
     */
919
    public function showHideItem(Portfolio $item)
920
    {
921
        if (!$this->itemBelongToOwner($item)) {
922
            api_not_allowed(true);
923
        }
924
925
        switch ($item->getVisibility()) {
926
            case Portfolio::VISIBILITY_HIDDEN:
927
                $item->setVisibility(Portfolio::VISIBILITY_VISIBLE);
928
                break;
929
            case Portfolio::VISIBILITY_VISIBLE:
930
                $item->setVisibility(Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER);
931
                break;
932
            case Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER:
933
            default:
934
                $item->setVisibility(Portfolio::VISIBILITY_HIDDEN);
935
                break;
936
        }
937
938
        $this->em->persist($item);
939
        $this->em->flush();
940
941
        Display::addFlash(
942
            Display::return_message(get_lang('VisibilityChanged'), 'success')
943
        );
944
945
        header("Location: $this->baseUrl");
946
        exit;
947
    }
948
949
    /**
950
     * @throws \Doctrine\ORM\ORMException
951
     * @throws \Doctrine\ORM\OptimisticLockException
952
     */
953
    public function deleteItem(Portfolio $item)
954
    {
955
        if (!$this->itemBelongToOwner($item)) {
956
            api_not_allowed(true);
957
        }
958
959
        HookPortfolioItemDeleted::create()
960
            ->setEventData(['item' => $item])
961
            ->notifyItemDeleted()
962
        ;
963
964
        $this->em->remove($item);
965
        $this->em->flush();
966
967
        Display::addFlash(
968
            Display::return_message(get_lang('ItemDeleted'), 'success')
969
        );
970
971
        header("Location: $this->baseUrl");
972
        exit;
973
    }
974
975
    /**
976
     * @throws \Exception
977
     */
978
    public function index(HttpRequest $httpRequest)
979
    {
980
        $listByUser = false;
981
        $listHighlighted = $httpRequest->query->has('list_highlighted');
982
        $listAlphabetical = $httpRequest->query->has('list_alphabetical');
983
984
        if ($httpRequest->query->has('user')) {
985
            $this->owner = api_get_user_entity($httpRequest->query->getInt('user'));
986
987
            if (empty($this->owner)) {
988
                api_not_allowed(true);
989
            }
990
991
            $listByUser = true;
992
        }
993
994
        $currentUserId = api_get_user_id();
995
996
        $actions = [];
997
998
        if (api_is_platform_admin()) {
999
            $actions[] = Display::url(
1000
                Display::return_icon('add.png', get_lang('Add'), [], ICON_SIZE_MEDIUM),
1001
                $this->baseUrl.'action=add_item'
1002
            );
1003
            $actions[] = Display::url(
1004
                Display::return_icon('folder.png', get_lang('AddCategory'), [], ICON_SIZE_MEDIUM),
1005
                $this->baseUrl.'action=list_categories'
1006
            );
1007
            $actions[] = Display::url(
1008
                Display::return_icon('waiting_list.png', get_lang('PortfolioDetails'), [], ICON_SIZE_MEDIUM),
1009
                $this->baseUrl.'action=details'
1010
            );
1011
        } else {
1012
            if ($currentUserId == $this->owner->getId()) {
1013
                if ($this->isAllowed()) {
1014
                    $actions[] = Display::url(
1015
                        Display::return_icon('add.png', get_lang('Add'), [], ICON_SIZE_MEDIUM),
1016
                        $this->baseUrl.'action=add_item'
1017
                    );
1018
                    $actions[] = Display::url(
1019
                        Display::return_icon('waiting_list.png', get_lang('PortfolioDetails'), [], ICON_SIZE_MEDIUM),
1020
                        $this->baseUrl.'action=details'
1021
                    );
1022
                }
1023
            } else {
1024
                $actions[] = Display::url(
1025
                    Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
1026
                    $this->baseUrl
1027
                );
1028
            }
1029
        }
1030
1031
        if (api_is_allowed_to_edit()) {
1032
            $actions[] = Display::url(
1033
                Display::return_icon('tickets.png', get_lang('Tags'), [], ICON_SIZE_MEDIUM),
1034
                $this->baseUrl.'action=tags'
1035
            );
1036
        }
1037
1038
        $frmStudentList = null;
1039
        $frmTagList = null;
1040
1041
        $categories = [];
1042
        $portfolio = [];
1043
        if ($this->course) {
1044
            $frmTagList = $this->createFormTagFilter($listByUser);
1045
            $frmStudentList = $this->createFormStudentFilter($listByUser, $listHighlighted, $listAlphabetical);
1046
            $frmStudentList->setDefaults(['user' => $this->owner->getId()]);
1047
            // it translates the category title with the current user language
1048
            $categories = $this->getCategoriesForIndex(null, 0);
1049
            if (count($categories) > 0) {
1050
                foreach ($categories as &$category) {
1051
                    $translated = $this->translateDisplayName($category->getTitle());
1052
                    $category->setTitle($translated);
1053
                }
1054
            }
1055
        } else {
1056
            // it displays the list in Network Social for the current user
1057
            $portfolio = $this->getCategoriesForIndex();
1058
        }
1059
1060
        $foundComments = [];
1061
1062
        if ($listHighlighted) {
1063
            $items = $this->getHighlightedItems();
1064
        } else {
1065
            $items = $this->getItemsForIndex($listByUser, $frmTagList, $listAlphabetical);
1066
1067
            $foundComments = $this->getCommentsForIndex($frmTagList);
1068
        }
1069
1070
        // it gets and translate the sub-categories
1071
        $categoryId = $httpRequest->query->getInt('categoryId');
1072
        $subCategoryIdsReq = isset($_REQUEST['subCategoryIds']) ? Security::remove_XSS($_REQUEST['subCategoryIds']) : '';
1073
        $subCategoryIds = $subCategoryIdsReq;
1074
        if ('all' !== $subCategoryIdsReq) {
1075
            $subCategoryIds = !empty($subCategoryIdsReq) ? explode(',', $subCategoryIdsReq) : [];
1076
        }
1077
        $subcategories = [];
1078
        if ($categoryId > 0) {
1079
            $subcategories = $this->getCategoriesForIndex(null, $categoryId);
1080
            if (count($subcategories) > 0) {
1081
                foreach ($subcategories as &$subcategory) {
1082
                    $translated = $this->translateDisplayName($subcategory->getTitle());
1083
                    $subcategory->setTitle($translated);
1084
                }
1085
            }
1086
        }
1087
1088
        $template = new Template(null, false, false, false, false, false, false);
1089
        $template->assign('user', $this->owner);
1090
        $template->assign('listByUser', $listByUser);
1091
        $template->assign('course', $this->course);
1092
        $template->assign('session', $this->session);
1093
        $template->assign('portfolio', $portfolio);
1094
        $template->assign('categories', $categories);
1095
        $template->assign('uncategorized_items', $items);
1096
        $template->assign('frm_student_list', $this->course ? $frmStudentList->returnForm() : '');
1097
        $template->assign('frm_tag_list', $this->course ? $frmTagList->returnForm() : '');
1098
        $template->assign('category_id', $categoryId);
1099
        $template->assign('subcategories', $subcategories);
1100
        $template->assign('subcategory_ids', $subCategoryIds);
1101
        $template->assign('found_comments', $foundComments);
1102
1103
        $js = '<script>
1104
            $(function() {
1105
                $(".category-filters").bind("click", function() {
1106
                    var categoryId = parseInt($(this).find("input[type=\'radio\']").val());
1107
                    $("input[name=\'categoryId\']").val(categoryId);
1108
                    $("input[name=\'subCategoryIds\']").val("all");
1109
                    $("#frm_tag_list_submit").trigger("click");
1110
                });
1111
                $(".subcategory-filters").bind("click", function() {
1112
                    var checkedVals = $(".subcategory-filters:checkbox:checked").map(function() {
1113
                        return this.value;
1114
                    }).get();
1115
                    $("input[name=\'subCategoryIds\']").val(checkedVals.join(","));
1116
                    $("#frm_tag_list_submit").trigger("click");
1117
                });
1118
            });
1119
        </script>';
1120
        $template->assign('js_script', $js);
1121
        $layout = $template->get_template('portfolio/list.html.twig');
1122
1123
        Display::addFlash(
1124
            Display::return_message(get_lang('PortfolioPostAddHelp'), 'info', false)
1125
        );
1126
1127
        $content = $template->fetch($layout);
1128
1129
        $this->renderView($content, get_lang('Portfolio'), $actions);
1130
    }
1131
1132
    /**
1133
     * @throws \Doctrine\ORM\ORMException
1134
     * @throws \Doctrine\ORM\OptimisticLockException
1135
     * @throws \Doctrine\ORM\TransactionRequiredException
1136
     */
1137
    public function view(Portfolio $item, $urlUser)
1138
    {
1139
        global $interbreadcrumb;
1140
1141
        if (!$this->itemBelongToOwner($item)) {
1142
            if ($this->advancedSharingEnabled) {
1143
                $courseInfo = api_get_course_info_by_id($this->course->getId());
1144
                $sessionId = $this->session ? $this->session->getId() : 0;
1145
1146
                $itemPropertyVisiblity = api_get_item_visibility(
1147
                    $courseInfo,
1148
                    TOOL_PORTFOLIO,
1149
                    $item->getId(),
1150
                    $sessionId,
1151
                    $this->owner->getId(),
1152
                    'visible'
1153
                );
1154
1155
                if ($item->getVisibility() === Portfolio::VISIBILITY_PER_USER && 1 !== $itemPropertyVisiblity) {
1156
                    api_not_allowed(true);
1157
                }
1158
            } elseif ($item->getVisibility() === Portfolio::VISIBILITY_HIDDEN
1159
                || ($item->getVisibility() === Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER && !api_is_allowed_to_edit())
1160
            ) {
1161
                api_not_allowed(true);
1162
            }
1163
        }
1164
1165
        HookPortfolioItemViewed::create()
1166
            ->setEventData(['portfolio' => $item])
1167
            ->notifyItemViewed()
1168
        ;
1169
1170
        $itemCourse = $item->getCourse();
1171
        $itemSession = $item->getSession();
1172
1173
        $form = $this->createCommentForm($item);
1174
1175
        $commentsRepo = $this->em->getRepository(PortfolioComment::class);
1176
1177
        $commentsQueryBuilder = $commentsRepo->createQueryBuilder('comment');
1178
        $commentsQueryBuilder->where('comment.item = :item');
1179
1180
        if ($this->advancedSharingEnabled) {
1181
            $commentsQueryBuilder
1182
                ->leftJoin(
1183
                    CItemProperty::class,
1184
                    'cip',
1185
                    Join::WITH,
1186
                    "cip.ref = comment.id
1187
                        AND cip.tool = :cip_tool
1188
                        AND cip.course = :course
1189
                        AND cip.lasteditType = 'visible'
1190
                        AND cip.toUser = :current_user"
1191
                )
1192
                ->andWhere(
1193
                    sprintf(
1194
                        'comment.visibility = %d
1195
                            OR (
1196
                                comment.visibility = %d AND cip IS NOT NULL OR comment.author = :current_user
1197
                            )',
1198
                        PortfolioComment::VISIBILITY_VISIBLE,
1199
                        PortfolioComment::VISIBILITY_PER_USER
1200
                    )
1201
                )
1202
                ->setParameter('cip_tool', TOOL_PORTFOLIO_COMMENT)
1203
                ->setParameter('current_user', $this->owner->getId())
1204
                ->setParameter('course', $item->getCourse())
1205
            ;
1206
        }
1207
1208
        if (true === api_get_configuration_value('portfolio_show_base_course_post_in_sessions')
1209
            && $this->session && !$item->getSession() && !$item->isDuplicatedInSession($this->session)
1210
        ) {
1211
            $comments = [];
1212
        } else {
1213
            $comments = $commentsQueryBuilder
1214
                ->orderBy('comment.root, comment.lft', 'ASC')
1215
                ->setParameter('item', $item)
1216
                ->getQuery()
1217
                ->getArrayResult()
1218
            ;
1219
        }
1220
1221
        $clockIcon = Display::returnFontAwesomeIcon('clock-o', '', true);
1222
1223
        $commentsHtml = $commentsRepo->buildTree(
1224
            $comments,
1225
            [
1226
                'decorate' => true,
1227
                'rootOpen' => '<div class="media-list">',
1228
                'rootClose' => '</div>',
1229
                'childOpen' => function ($node) use ($commentsRepo) {
1230
                    /** @var PortfolioComment $comment */
1231
                    $comment = $commentsRepo->find($node['id']);
1232
                    $author = $comment->getAuthor();
1233
1234
                    $userPicture = UserManager::getUserPicture(
1235
                        $comment->getAuthor()->getId(),
1236
                        USER_IMAGE_SIZE_SMALL,
1237
                        null,
1238
                        [
1239
                            'picture_uri' => $author->getPictureUri(),
1240
                            'email' => $author->getEmail(),
1241
                        ]
1242
                    );
1243
1244
                    return '<article class="media" id="comment-'.$node['id'].'">
1245
                        <div class="media-left"><img class="media-object thumbnail" src="'.$userPicture.'" alt="'
1246
                        .$author->getCompleteName().'"></div>
1247
                        <div class="media-body">';
1248
                },
1249
                'childClose' => '</div></article>',
1250
                'nodeDecorator' => function ($node) use ($commentsRepo, $clockIcon, $item) {
1251
                    $commentActions = [];
1252
                    /** @var PortfolioComment $comment */
1253
                    $comment = $commentsRepo->find($node['id']);
1254
1255
                    if ($this->commentBelongsToOwner($comment)) {
1256
                        $commentActions[] = Display::url(
1257
                            Display::return_icon(
1258
                                $comment->isTemplate() ? 'wizard.png' : 'wizard_na.png',
1259
                                $comment->isTemplate() ? get_lang('RemoveAsTemplate') : get_lang('AddAsTemplate')
1260
                            ),
1261
                            $this->baseUrl.http_build_query(['action' => 'template_comment', 'id' => $comment->getId()])
1262
                        );
1263
                    }
1264
1265
                    $commentActions[] = Display::url(
1266
                        Display::return_icon('discuss.png', get_lang('ReplyToThisComment')),
1267
                        '#',
1268
                        [
1269
                            'data-comment' => htmlspecialchars(
1270
                                json_encode(['id' => $comment->getId()])
1271
                            ),
1272
                            'role' => 'button',
1273
                            'class' => 'btn-reply-to',
1274
                        ]
1275
                    );
1276
                    $commentActions[] = Display::url(
1277
                        Display::return_icon('copy.png', get_lang('CopyToMyPortfolio')),
1278
                        $this->baseUrl.http_build_query(
1279
                            [
1280
                                'action' => 'copy',
1281
                                'copy' => 'comment',
1282
                                'id' => $comment->getId(),
1283
                            ]
1284
                        )
1285
                    );
1286
1287
                    $isAllowedToEdit = api_is_allowed_to_edit();
1288
1289
                    if ($isAllowedToEdit) {
1290
                        $commentActions[] = Display::url(
1291
                            Display::return_icon('copy.png', get_lang('CopyToStudentPortfolio')),
1292
                            $this->baseUrl.http_build_query(
1293
                                [
1294
                                    'action' => 'teacher_copy',
1295
                                    'copy' => 'comment',
1296
                                    'id' => $comment->getId(),
1297
                                ]
1298
                            )
1299
                        );
1300
1301
                        if ($comment->isImportant()) {
1302
                            $commentActions[] = Display::url(
1303
                                Display::return_icon('drawing-pin.png', get_lang('UnmarkCommentAsImportant')),
1304
                                $this->baseUrl.http_build_query(
1305
                                    [
1306
                                        'action' => 'mark_important',
1307
                                        'item' => $item->getId(),
1308
                                        'id' => $comment->getId(),
1309
                                    ]
1310
                                )
1311
                            );
1312
                        } else {
1313
                            $commentActions[] = Display::url(
1314
                                Display::return_icon('drawing-pin.png', get_lang('MarkCommentAsImportant')),
1315
                                $this->baseUrl.http_build_query(
1316
                                    [
1317
                                        'action' => 'mark_important',
1318
                                        'item' => $item->getId(),
1319
                                        'id' => $comment->getId(),
1320
                                    ]
1321
                                )
1322
                            );
1323
                        }
1324
1325
                        if ($this->course && '1' === api_get_course_setting('qualify_portfolio_comment')) {
1326
                            $commentActions[] = Display::url(
1327
                                Display::return_icon('quiz.png', get_lang('QualifyThisPortfolioComment')),
1328
                                $this->baseUrl.http_build_query(
1329
                                    [
1330
                                        'action' => 'qualify',
1331
                                        'comment' => $comment->getId(),
1332
                                    ]
1333
                                )
1334
                            );
1335
                        }
1336
                    }
1337
1338
                    if ($this->commentBelongsToOwner($comment)) {
1339
                        if ($this->advancedSharingEnabled) {
1340
                            $commentActions[] = Display::url(
1341
                                Display::return_icon('visible.png', get_lang('ChooseRecipients')),
1342
                                $this->baseUrl.http_build_query(['action' => 'comment_visiblity_choose', 'id' => $comment->getId()])
1343
                            );
1344
                        }
1345
1346
                        $commentActions[] = Display::url(
1347
                            Display::return_icon('edit.png', get_lang('Edit')),
1348
                            $this->baseUrl.http_build_query(['action' => 'edit_comment', 'id' => $comment->getId()])
1349
                        );
1350
                        $commentActions[] = Display::url(
1351
                            Display::return_icon('delete.png', get_lang('Delete')),
1352
                            $this->baseUrl.http_build_query(['action' => 'delete_comment', 'id' => $comment->getId()])
1353
                        );
1354
                    }
1355
1356
                    $nodeHtml = '<div class="pull-right">'.implode(PHP_EOL, $commentActions).'</div>'.PHP_EOL
1357
                        .'<footer class="media-heading h4">'.PHP_EOL
1358
                        .'<p>'.$comment->getAuthor()->getCompleteName().'</p>'.PHP_EOL;
1359
1360
                    if ($comment->isImportant()
1361
                        && ($this->itemBelongToOwner($comment->getItem()) || $isAllowedToEdit)
1362
                    ) {
1363
                        $nodeHtml .= '<span class="pull-right label label-warning origin-style">'
1364
                            .get_lang('CommentMarkedAsImportant')
1365
                            .'</span>'.PHP_EOL;
1366
                    }
1367
1368
                    $nodeHtml .= '<small>'.$clockIcon.PHP_EOL
1369
                        .$this->getLabelForCommentDate($comment).'</small>'.PHP_EOL;
1370
1371
                    $nodeHtml .= '</footer>'.PHP_EOL
1372
                        .Security::remove_XSS($comment->getContent()).PHP_EOL;
1373
1374
                    $nodeHtml .= $this->generateAttachmentList($comment);
1375
1376
                    return $nodeHtml;
1377
                },
1378
            ]
1379
        );
1380
1381
        $template = new Template(null, false, false, false, false, false, false);
1382
        $template->assign('baseurl', $this->baseUrl);
1383
        $template->assign('item', $item);
1384
        $template->assign('item_content', $this->generateItemContent($item));
1385
        $template->assign('count_comments', count($comments));
1386
        $template->assign('comments', $commentsHtml);
1387
        $template->assign('form', $form);
1388
        $template->assign('attachment_list', $this->generateAttachmentList($item));
1389
1390
        if ($itemCourse) {
1391
            $propertyInfo = api_get_item_property_info(
1392
                $itemCourse->getId(),
1393
                TOOL_PORTFOLIO,
1394
                $item->getId(),
1395
                $itemSession ? $itemSession->getId() : 0
1396
            );
1397
1398
            if ($propertyInfo && empty($propertyInfo['to_user_id'])) {
1399
                $template->assign(
1400
                    'last_edit',
1401
                    [
1402
                        'date' => $propertyInfo['lastedit_date'],
1403
                        'user' => api_get_user_entity($propertyInfo['lastedit_user_id'])->getCompleteName(),
1404
                    ]
1405
                );
1406
            }
1407
        }
1408
1409
        $layout = $template->get_template('portfolio/view.html.twig');
1410
        $content = $template->fetch($layout);
1411
1412
        $interbreadcrumb[] = ['name' => get_lang('Portfolio'), 'url' => $this->baseUrl];
1413
1414
        $editLink = Display::url(
1415
            Display::return_icon('edit.png', get_lang('Edit'), [], ICON_SIZE_MEDIUM),
1416
            $this->baseUrl.http_build_query(['action' => 'edit_item', 'id' => $item->getId()])
1417
        );
1418
1419
        $urlUserString = "";
1420
        if (!empty($urlUser)) {
1421
            $urlUserString = "user=".$urlUser;
1422
        }
1423
1424
        $actions = [];
1425
        $actions[] = Display::url(
1426
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
1427
            $this->baseUrl.$urlUserString
1428
        );
1429
1430
        if ($this->itemBelongToOwner($item)) {
1431
            $actions[] = $editLink;
1432
1433
            $actions[] = Display::url(
1434
                Display::return_icon(
1435
                    $item->isTemplate() ? 'wizard.png' : 'wizard_na.png',
1436
                    $item->isTemplate() ? get_lang('RemoveAsTemplate') : get_lang('AddAsTemplate'),
1437
                    [],
1438
                    ICON_SIZE_MEDIUM
1439
                ),
1440
                $this->baseUrl.http_build_query(['action' => 'template', 'id' => $item->getId()])
1441
            );
1442
1443
            if ($this->advancedSharingEnabled) {
1444
                $actions[] = Display::url(
1445
                    Display::return_icon('visible.png', get_lang('ChooseRecipients'), [], ICON_SIZE_MEDIUM),
1446
                    $this->baseUrl.http_build_query(['action' => 'item_visiblity_choose', 'id' => $item->getId()])
1447
                );
1448
            } else {
1449
                $visibilityUrl = $this->baseUrl.http_build_query(['action' => 'visibility', 'id' => $item->getId()]);
1450
1451
                if ($item->getVisibility() === Portfolio::VISIBILITY_HIDDEN) {
1452
                    $actions[] = Display::url(
1453
                        Display::return_icon('invisible.png', get_lang('MakeVisible'), [], ICON_SIZE_MEDIUM),
1454
                        $visibilityUrl
1455
                    );
1456
                } elseif ($item->getVisibility() === Portfolio::VISIBILITY_VISIBLE) {
1457
                    $actions[] = Display::url(
1458
                        Display::return_icon('visible.png', get_lang('MakeVisibleForTeachers'), [], ICON_SIZE_MEDIUM),
1459
                        $visibilityUrl
1460
                    );
1461
                } elseif ($item->getVisibility() === Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER) {
1462
                    $actions[] = Display::url(
1463
                        Display::return_icon('eye-slash.png', get_lang('MakeInvisible'), [], ICON_SIZE_MEDIUM),
1464
                        $visibilityUrl
1465
                    );
1466
                }
1467
            }
1468
1469
            $actions[] = Display::url(
1470
                Display::return_icon('delete.png', get_lang('Delete'), [], ICON_SIZE_MEDIUM),
1471
                $this->baseUrl.http_build_query(['action' => 'delete_item', 'id' => $item->getId()])
1472
            );
1473
        } else {
1474
            $actions[] = Display::url(
1475
                Display::return_icon('copy.png', get_lang('CopyToMyPortfolio'), [], ICON_SIZE_MEDIUM),
1476
                $this->baseUrl.http_build_query(['action' => 'copy', 'copy' => 'item', 'id' => $item->getId()])
1477
            );
1478
        }
1479
1480
        if (api_is_allowed_to_edit()) {
1481
            $actions[] = Display::url(
1482
                Display::return_icon('copy.png', get_lang('CopyToStudentPortfolio'), [], ICON_SIZE_MEDIUM),
1483
                $this->baseUrl.http_build_query(['action' => 'teacher_copy', 'copy' => 'item', 'id' => $item->getId()])
1484
            );
1485
            $actions[] = $editLink;
1486
1487
            $highlightedUrl = $this->baseUrl.http_build_query(['action' => 'highlighted', 'id' => $item->getId()]);
1488
1489
            if ($item->isHighlighted()) {
1490
                $actions[] = Display::url(
1491
                    Display::return_icon('award_red.png', get_lang('UnmarkAsHighlighted'), [], ICON_SIZE_MEDIUM),
1492
                    $highlightedUrl
1493
                );
1494
            } else {
1495
                $actions[] = Display::url(
1496
                    Display::return_icon('award_red_na.png', get_lang('MarkAsHighlighted'), [], ICON_SIZE_MEDIUM),
1497
                    $highlightedUrl
1498
                );
1499
            }
1500
1501
            if ($itemCourse && '1' === api_get_course_setting('qualify_portfolio_item')) {
1502
                $actions[] = Display::url(
1503
                    Display::return_icon('quiz.png', get_lang('QualifyThisPortfolioItem'), [], ICON_SIZE_MEDIUM),
1504
                    $this->baseUrl.http_build_query(['action' => 'qualify', 'item' => $item->getId()])
1505
                );
1506
            }
1507
        }
1508
1509
        $this->renderView($content, $item->getTitle(true), $actions, false);
1510
    }
1511
1512
    /**
1513
     * @throws \Doctrine\ORM\ORMException
1514
     * @throws \Doctrine\ORM\OptimisticLockException
1515
     */
1516
    public function copyItem(Portfolio $originItem)
1517
    {
1518
        $this->blockIsNotAllowed();
1519
1520
        $currentTime = api_get_utc_datetime(null, false, true);
1521
1522
        $portfolio = new Portfolio();
1523
        $portfolio
1524
            ->setVisibility(Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER)
1525
            ->setTitle(
1526
                sprintf(get_lang('PortfolioItemFromXUser'), $originItem->getUser()->getCompleteName())
1527
            )
1528
            ->setContent('')
1529
            ->setUser($this->owner)
1530
            ->setOrigin($originItem->getId())
1531
            ->setOriginType(Portfolio::TYPE_ITEM)
1532
            ->setCourse($this->course)
1533
            ->setSession($this->session)
1534
            ->setCreationDate($currentTime)
1535
            ->setUpdateDate($currentTime);
1536
1537
        $this->em->persist($portfolio);
1538
        $this->em->flush();
1539
1540
        Display::addFlash(
1541
            Display::return_message(get_lang('PortfolioItemAdded'), 'success')
1542
        );
1543
1544
        header("Location: $this->baseUrl".http_build_query(['action' => 'edit_item', 'id' => $portfolio->getId()]));
1545
        exit;
1546
    }
1547
1548
    /**
1549
     * @throws \Doctrine\ORM\ORMException
1550
     * @throws \Doctrine\ORM\OptimisticLockException
1551
     */
1552
    public function copyComment(PortfolioComment $originComment)
1553
    {
1554
        $currentTime = api_get_utc_datetime(null, false, true);
1555
1556
        $portfolio = new Portfolio();
1557
        $portfolio
1558
            ->setVisibility(Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER)
1559
            ->setTitle(
1560
                sprintf(get_lang('PortfolioCommentFromXUser'), $originComment->getAuthor()->getCompleteName())
1561
            )
1562
            ->setContent('')
1563
            ->setUser($this->owner)
1564
            ->setOrigin($originComment->getId())
1565
            ->setOriginType(Portfolio::TYPE_COMMENT)
1566
            ->setCourse($this->course)
1567
            ->setSession($this->session)
1568
            ->setCreationDate($currentTime)
1569
            ->setUpdateDate($currentTime);
1570
1571
        $this->em->persist($portfolio);
1572
        $this->em->flush();
1573
1574
        Display::addFlash(
1575
            Display::return_message(get_lang('PortfolioItemAdded'), 'success')
1576
        );
1577
1578
        header("Location: $this->baseUrl".http_build_query(['action' => 'edit_item', 'id' => $portfolio->getId()]));
1579
        exit;
1580
    }
1581
1582
    /**
1583
     * @throws \Doctrine\ORM\ORMException
1584
     * @throws \Doctrine\ORM\OptimisticLockException
1585
     * @throws \Exception
1586
     */
1587
    public function teacherCopyItem(Portfolio $originItem)
1588
    {
1589
        api_protect_teacher_script();
1590
1591
        $actionParams = http_build_query(['action' => 'teacher_copy', 'copy' => 'item', 'id' => $originItem->getId()]);
1592
1593
        $form = new FormValidator('teacher_copy_portfolio', 'post', $this->baseUrl.$actionParams);
1594
1595
        if (api_get_configuration_value('save_titles_as_html')) {
1596
            $form->addHtmlEditor('title', get_lang('Title'), true, false, ['ToolbarSet' => 'TitleAsHtml']);
1597
        } else {
1598
            $form->addText('title', get_lang('Title'));
1599
            $form->applyFilter('title', 'trim');
1600
        }
1601
1602
        $form->addLabel(
1603
            sprintf(get_lang('PortfolioItemFromXUser'), $originItem->getUser()->getCompleteName()),
1604
            Display::panel(
1605
                Security::remove_XSS($originItem->getContent())
1606
            )
1607
        );
1608
        $form->addHtmlEditor('content', get_lang('Content'), true, false, ['ToolbarSet' => 'NotebookStudent']);
1609
1610
        $urlParams = http_build_query(
1611
            [
1612
                'a' => 'search_user_by_course',
1613
                'course_id' => $this->course->getId(),
1614
                'session_id' => $this->session ? $this->session->getId() : 0,
1615
            ]
1616
        );
1617
        $form->addSelectAjax(
1618
            'students',
1619
            get_lang('Students'),
1620
            [],
1621
            [
1622
                'url' => api_get_path(WEB_AJAX_PATH)."course.ajax.php?$urlParams",
1623
                'multiple' => true,
1624
            ]
1625
        );
1626
        $form->addRule('students', get_lang('ThisFieldIsRequired'), 'required');
1627
        $form->addButtonCreate(get_lang('Save'));
1628
1629
        $toolName = get_lang('CopyToStudentPortfolio');
1630
        $content = $form->returnForm();
1631
1632
        if ($form->validate()) {
1633
            $values = $form->exportValues();
1634
1635
            $currentTime = api_get_utc_datetime(null, false, true);
1636
1637
            foreach ($values['students'] as $studentId) {
1638
                $owner = api_get_user_entity($studentId);
1639
1640
                $portfolio = new Portfolio();
1641
                $portfolio
1642
                    ->setVisibility(Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER)
1643
                    ->setTitle($values['title'])
1644
                    ->setContent($values['content'])
1645
                    ->setUser($owner)
1646
                    ->setOrigin($originItem->getId())
1647
                    ->setOriginType(Portfolio::TYPE_ITEM)
1648
                    ->setCourse($this->course)
1649
                    ->setSession($this->session)
1650
                    ->setCreationDate($currentTime)
1651
                    ->setUpdateDate($currentTime);
1652
1653
                $this->em->persist($portfolio);
1654
            }
1655
1656
            $this->em->flush();
1657
1658
            Display::addFlash(
1659
                Display::return_message(get_lang('PortfolioItemAddedToStudents'), 'success')
1660
            );
1661
1662
            header("Location: $this->baseUrl");
1663
            exit;
1664
        }
1665
1666
        $this->renderView($content, $toolName);
1667
    }
1668
1669
    /**
1670
     * @throws \Doctrine\ORM\ORMException
1671
     * @throws \Doctrine\ORM\OptimisticLockException
1672
     * @throws \Exception
1673
     */
1674
    public function teacherCopyComment(PortfolioComment $originComment)
1675
    {
1676
        $actionParams = http_build_query(
1677
            [
1678
                'action' => 'teacher_copy',
1679
                'copy' => 'comment',
1680
                'id' => $originComment->getId(),
1681
            ]
1682
        );
1683
1684
        $form = new FormValidator('teacher_copy_portfolio', 'post', $this->baseUrl.$actionParams);
1685
1686
        if (api_get_configuration_value('save_titles_as_html')) {
1687
            $form->addHtmlEditor('title', get_lang('Title'), true, false, ['ToolbarSet' => 'TitleAsHtml']);
1688
        } else {
1689
            $form->addText('title', get_lang('Title'));
1690
            $form->applyFilter('title', 'trim');
1691
        }
1692
1693
        $form->addLabel(
1694
            sprintf(get_lang('PortfolioCommentFromXUser'), $originComment->getAuthor()->getCompleteName()),
1695
            Display::panel(
1696
                Security::remove_XSS($originComment->getContent())
1697
            )
1698
        );
1699
        $form->addHtmlEditor('content', get_lang('Content'), true, false, ['ToolbarSet' => 'NotebookStudent']);
1700
1701
        $urlParams = http_build_query(
1702
            [
1703
                'a' => 'search_user_by_course',
1704
                'course_id' => $this->course->getId(),
1705
                'session_id' => $this->session ? $this->session->getId() : 0,
1706
            ]
1707
        );
1708
        $form->addSelectAjax(
1709
            'students',
1710
            get_lang('Students'),
1711
            [],
1712
            [
1713
                'url' => api_get_path(WEB_AJAX_PATH)."course.ajax.php?$urlParams",
1714
                'multiple' => true,
1715
            ]
1716
        );
1717
        $form->addRule('students', get_lang('ThisFieldIsRequired'), 'required');
1718
        $form->addButtonCreate(get_lang('Save'));
1719
1720
        $toolName = get_lang('CopyToStudentPortfolio');
1721
        $content = $form->returnForm();
1722
1723
        if ($form->validate()) {
1724
            $values = $form->exportValues();
1725
1726
            $currentTime = api_get_utc_datetime(null, false, true);
1727
1728
            foreach ($values['students'] as $studentId) {
1729
                $owner = api_get_user_entity($studentId);
1730
1731
                $portfolio = new Portfolio();
1732
                $portfolio
1733
                    ->setVisibility(Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER)
1734
                    ->setTitle($values['title'])
1735
                    ->setContent($values['content'])
1736
                    ->setUser($owner)
1737
                    ->setOrigin($originComment->getId())
1738
                    ->setOriginType(Portfolio::TYPE_COMMENT)
1739
                    ->setCourse($this->course)
1740
                    ->setSession($this->session)
1741
                    ->setCreationDate($currentTime)
1742
                    ->setUpdateDate($currentTime);
1743
1744
                $this->em->persist($portfolio);
1745
            }
1746
1747
            $this->em->flush();
1748
1749
            Display::addFlash(
1750
                Display::return_message(get_lang('PortfolioItemAddedToStudents'), 'success')
1751
            );
1752
1753
            header("Location: $this->baseUrl");
1754
            exit;
1755
        }
1756
1757
        $this->renderView($content, $toolName);
1758
    }
1759
1760
    /**
1761
     * @throws \Doctrine\ORM\ORMException
1762
     * @throws \Doctrine\ORM\OptimisticLockException
1763
     */
1764
    public function markImportantCommentInItem(Portfolio $item, PortfolioComment $comment)
1765
    {
1766
        if ($comment->getItem()->getId() !== $item->getId()) {
1767
            api_not_allowed(true);
1768
        }
1769
1770
        $comment->setIsImportant(
1771
            !$comment->isImportant()
1772
        );
1773
1774
        $this->em->persist($comment);
1775
        $this->em->flush();
1776
1777
        Display::addFlash(
1778
            Display::return_message(get_lang('CommentMarkedAsImportant'), 'success')
1779
        );
1780
1781
        header("Location: $this->baseUrl".http_build_query(['action' => 'view', 'id' => $item->getId()]));
1782
        exit;
1783
    }
1784
1785
    /**
1786
     * @throws \Exception
1787
     */
1788
    public function details(HttpRequest $httpRequest)
1789
    {
1790
        $this->blockIsNotAllowed();
1791
1792
        $currentUserId = api_get_user_id();
1793
        $isAllowedToFilterStudent = $this->course && api_is_allowed_to_edit();
1794
1795
        $actions = [];
1796
        $actions[] = Display::url(
1797
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
1798
            $this->baseUrl
1799
        );
1800
        $actions[] = Display::url(
1801
            Display::return_icon('pdf.png', get_lang('ExportMyPortfolioDataPdf'), [], ICON_SIZE_MEDIUM),
1802
            $this->baseUrl.http_build_query(['action' => 'export_pdf'])
1803
        );
1804
        $actions[] = Display::url(
1805
            Display::return_icon('save_pack.png', get_lang('ExportMyPortfolioDataZip'), [], ICON_SIZE_MEDIUM),
1806
            $this->baseUrl.http_build_query(['action' => 'export_zip'])
1807
        );
1808
1809
        $frmStudent = null;
1810
1811
        if ($isAllowedToFilterStudent) {
1812
            if ($httpRequest->query->has('user')) {
1813
                $this->owner = api_get_user_entity($httpRequest->query->getInt('user'));
1814
1815
                if (empty($this->owner)) {
1816
                    api_not_allowed(true);
1817
                }
1818
1819
                $actions[1] = Display::url(
1820
                    Display::return_icon('pdf.png', get_lang('ExportMyPortfolioDataPdf'), [], ICON_SIZE_MEDIUM),
1821
                    $this->baseUrl.http_build_query(['action' => 'export_pdf', 'user' => $this->owner->getId()])
1822
                );
1823
                $actions[2] = Display::url(
1824
                    Display::return_icon('save_pack.png', get_lang('ExportMyPortfolioDataZip'), [], ICON_SIZE_MEDIUM),
1825
                    $this->baseUrl.http_build_query(['action' => 'export_zip', 'user' => $this->owner->getId()])
1826
                );
1827
            }
1828
1829
            $frmStudent = new FormValidator('frm_student_list', 'get');
1830
1831
            $urlParams = http_build_query(
1832
                [
1833
                    'a' => 'search_user_by_course',
1834
                    'course_id' => $this->course->getId(),
1835
                    'session_id' => $this->session ? $this->session->getId() : 0,
1836
                ]
1837
            );
1838
1839
            $frmStudent
1840
                ->addSelectAjax(
1841
                    'user',
1842
                    get_lang('SelectLearnerPortfolio'),
1843
                    [],
1844
                    [
1845
                        'url' => api_get_path(WEB_AJAX_PATH)."course.ajax.php?$urlParams",
1846
                        'placeholder' => get_lang('SearchStudent'),
1847
                        'formatResult' => SelectAjax::templateResultForUsersInCourse(),
1848
                        'formatSelection' => SelectAjax::templateSelectionForUsersInCourse(),
1849
                    ]
1850
                )
1851
                ->addOption(
1852
                    $this->owner->getCompleteName(),
1853
                    $this->owner->getId(),
1854
                    [
1855
                        'data-avatarurl' => UserManager::getUserPicture($this->owner->getId()),
1856
                        'data-username' => $this->owner->getUsername(),
1857
                    ]
1858
                )
1859
            ;
1860
            $frmStudent->setDefaults(['user' => $this->owner->getId()]);
1861
            $frmStudent->addHidden('action', 'details');
1862
            $frmStudent->addHidden('cidReq', $this->course->getCode());
1863
            $frmStudent->addHidden('id_session', $this->session ? $this->session->getId() : 0);
1864
            $frmStudent->addButtonFilter(get_lang('Filter'));
1865
        }
1866
1867
        $itemsRepo = $this->em->getRepository(Portfolio::class);
1868
        $commentsRepo = $this->em->getRepository(PortfolioComment::class);
1869
1870
        $getItemsTotalNumber = function () use ($itemsRepo, $isAllowedToFilterStudent, $currentUserId) {
1871
            $qb = $itemsRepo->createQueryBuilder('i');
1872
            $qb
1873
                ->select('COUNT(i)')
1874
                ->where('i.user = :user')
1875
                ->setParameter('user', $this->owner);
1876
1877
            if ($this->course) {
1878
                $qb
1879
                    ->andWhere('i.course = :course')
1880
                    ->setParameter('course', $this->course);
1881
1882
                if ($this->session) {
1883
                    $qb
1884
                        ->andWhere('i.session = :session')
1885
                        ->setParameter('session', $this->session);
1886
                } else {
1887
                    $qb->andWhere('i.session IS NULL');
1888
                }
1889
            }
1890
1891
            if ($isAllowedToFilterStudent && $currentUserId !== $this->owner->getId()) {
1892
                $visibilityCriteria = [
1893
                    Portfolio::VISIBILITY_VISIBLE,
1894
                    Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER,
1895
                ];
1896
1897
                $qb->andWhere(
1898
                    $qb->expr()->in('i.visibility', $visibilityCriteria)
1899
                );
1900
            }
1901
1902
            return $qb->getQuery()->getSingleScalarResult();
1903
        };
1904
        $getItemsData = function ($from, $limit, $columnNo, $orderDirection) use ($itemsRepo, $isAllowedToFilterStudent, $currentUserId) {
1905
            $qb = $itemsRepo->createQueryBuilder('item')
1906
                ->where('item.user = :user')
1907
                ->leftJoin('item.category', 'category')
1908
                ->leftJoin('item.course', 'course')
1909
                ->leftJoin('item.session', 'session')
1910
                ->setParameter('user', $this->owner);
1911
1912
            if ($this->course) {
1913
                $qb
1914
                    ->andWhere('item.course = :course_id')
1915
                    ->setParameter('course_id', $this->course);
1916
1917
                if ($this->session) {
1918
                    $qb
1919
                        ->andWhere('item.session = :session')
1920
                        ->setParameter('session', $this->session);
1921
                } else {
1922
                    $qb->andWhere('item.session IS NULL');
1923
                }
1924
            }
1925
1926
            if ($isAllowedToFilterStudent && $currentUserId !== $this->owner->getId()) {
1927
                $visibilityCriteria = [
1928
                    Portfolio::VISIBILITY_VISIBLE,
1929
                    Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER,
1930
                ];
1931
1932
                $qb->andWhere(
1933
                    $qb->expr()->in('item.visibility', $visibilityCriteria)
1934
                );
1935
            }
1936
1937
            if (0 == $columnNo) {
1938
                $qb->orderBy('item.title', $orderDirection);
1939
            } elseif (1 == $columnNo) {
1940
                $qb->orderBy('item.creationDate', $orderDirection);
1941
            } elseif (2 == $columnNo) {
1942
                $qb->orderBy('item.updateDate', $orderDirection);
1943
            } elseif (3 == $columnNo) {
1944
                $qb->orderBy('category.title', $orderDirection);
1945
            } elseif (5 == $columnNo) {
1946
                $qb->orderBy('item.score', $orderDirection);
1947
            } elseif (6 == $columnNo) {
1948
                $qb->orderBy('course.title', $orderDirection);
1949
            } elseif (7 == $columnNo) {
1950
                $qb->orderBy('session.name', $orderDirection);
1951
            }
1952
1953
            $qb->setFirstResult($from)->setMaxResults($limit);
1954
1955
            return array_map(
1956
                function (Portfolio $item) {
1957
                    $category = $item->getCategory();
1958
1959
                    $row = [];
1960
                    $row[] = $item;
1961
                    $row[] = $item->getCreationDate();
1962
                    $row[] = $item->getUpdateDate();
1963
                    $row[] = $category ? $item->getCategory()->getTitle() : null;
1964
                    $row[] = $item->getComments()->count();
1965
                    $row[] = $item->getScore();
1966
1967
                    if (!$this->course) {
1968
                        $row[] = $item->getCourse();
1969
                        $row[] = $item->getSession();
1970
                    }
1971
1972
                    return $row;
1973
                },
1974
                $qb->getQuery()->getResult()
1975
            );
1976
        };
1977
1978
        $portfolioItemColumnFilter = function (Portfolio $item) {
1979
            return Display::url(
1980
                $item->getTitle(true),
1981
                $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()])
1982
            );
1983
        };
1984
        $convertFormatDateColumnFilter = function (DateTime $date) {
1985
            return api_convert_and_format_date($date);
1986
        };
1987
1988
        $tblItems = new SortableTable('tbl_items', $getItemsTotalNumber, $getItemsData, 1, 20, 'DESC');
1989
        $tblItems->set_additional_parameters(['action' => 'details', 'user' => $this->owner->getId()]);
1990
        $tblItems->set_header(0, get_lang('Title'));
1991
        $tblItems->set_column_filter(0, $portfolioItemColumnFilter);
1992
        $tblItems->set_header(1, get_lang('CreationDate'), true, [], ['class' => 'text-center']);
1993
        $tblItems->set_column_filter(1, $convertFormatDateColumnFilter);
1994
        $tblItems->set_header(2, get_lang('LastUpdate'), true, [], ['class' => 'text-center']);
1995
        $tblItems->set_column_filter(2, $convertFormatDateColumnFilter);
1996
        $tblItems->set_header(3, get_lang('Category'));
1997
        $tblItems->set_header(4, get_lang('Comments'), false, [], ['class' => 'text-right']);
1998
        $tblItems->set_header(5, get_lang('Score'), true, [], ['class' => 'text-right']);
1999
2000
        if (!$this->course) {
2001
            $tblItems->set_header(6, get_lang('Course'));
2002
            $tblItems->set_header(7, get_lang('Session'));
2003
        }
2004
2005
        $getCommentsTotalNumber = function () use ($commentsRepo) {
2006
            $qb = $commentsRepo->createQueryBuilder('c');
2007
            $qb
2008
                ->select('COUNT(c)')
2009
                ->where('c.author = :author')
2010
                ->setParameter('author', $this->owner);
2011
2012
            if ($this->course) {
2013
                $qb
2014
                    ->innerJoin('c.item', 'i')
2015
                    ->andWhere('i.course = :course')
2016
                    ->setParameter('course', $this->course);
2017
2018
                if ($this->session) {
2019
                    $qb
2020
                        ->andWhere('i.session = :session')
2021
                        ->setParameter('session', $this->session);
2022
                } else {
2023
                    $qb->andWhere('i.session IS NULL');
2024
                }
2025
            }
2026
2027
            return $qb->getQuery()->getSingleScalarResult();
2028
        };
2029
        $getCommentsData = function ($from, $limit, $columnNo, $orderDirection) use ($commentsRepo) {
2030
            $qb = $commentsRepo->createQueryBuilder('comment');
2031
            $qb
2032
                ->where('comment.author = :user')
2033
                ->innerJoin('comment.item', 'item')
2034
                ->setParameter('user', $this->owner);
2035
2036
            if ($this->course) {
2037
                $qb
2038
                    ->innerJoin('comment.item', 'i')
2039
                    ->andWhere('item.course = :course')
2040
                    ->setParameter('course', $this->course);
2041
2042
                if ($this->session) {
2043
                    $qb
2044
                        ->andWhere('item.session = :session')
2045
                        ->setParameter('session', $this->session);
2046
                } else {
2047
                    $qb->andWhere('item.session IS NULL');
2048
                }
2049
            }
2050
2051
            if (0 == $columnNo) {
2052
                $qb->orderBy('comment.content', $orderDirection);
2053
            } elseif (1 == $columnNo) {
2054
                $qb->orderBy('comment.date', $orderDirection);
2055
            } elseif (2 == $columnNo) {
2056
                $qb->orderBy('item.title', $orderDirection);
2057
            } elseif (3 == $columnNo) {
2058
                $qb->orderBy('comment.score', $orderDirection);
2059
            }
2060
2061
            $qb->setFirstResult($from)->setMaxResults($limit);
2062
2063
            return array_map(
2064
                function (PortfolioComment $comment) {
2065
                    return [
2066
                        $comment,
2067
                        $comment->getDate(),
2068
                        $comment->getItem(),
2069
                        $comment->getScore(),
2070
                    ];
2071
                },
2072
                $qb->getQuery()->getResult()
2073
            );
2074
        };
2075
2076
        $tblComments = new SortableTable('tbl_comments', $getCommentsTotalNumber, $getCommentsData, 1, 20, 'DESC');
2077
        $tblComments->set_additional_parameters(['action' => 'details', 'user' => $this->owner->getId()]);
2078
        $tblComments->set_header(0, get_lang('Resume'));
2079
        $tblComments->set_column_filter(
2080
            0,
2081
            function (PortfolioComment $comment) {
2082
                return Display::url(
2083
                    $comment->getExcerpt(),
2084
                    $this->baseUrl.http_build_query(['action' => 'view', 'id' => $comment->getItem()->getId()])
2085
                    .'#comment-'.$comment->getId()
2086
                );
2087
            }
2088
        );
2089
        $tblComments->set_header(1, get_lang('Date'), true, [], ['class' => 'text-center']);
2090
        $tblComments->set_column_filter(1, $convertFormatDateColumnFilter);
2091
        $tblComments->set_header(2, get_lang('PortfolioItemTitle'));
2092
        $tblComments->set_column_filter(2, $portfolioItemColumnFilter);
2093
        $tblComments->set_header(3, get_lang('Score'), true, [], ['class' => 'text-right']);
2094
2095
        $content = '';
2096
2097
        if ($frmStudent) {
2098
            $content .= $frmStudent->returnForm();
2099
        }
2100
2101
        $totalNumberOfItems = $tblItems->get_total_number_of_items();
2102
        $totalNumberOfComments = $tblComments->get_total_number_of_items();
2103
        $requiredNumberOfItems = (int) api_get_course_setting('portfolio_number_items');
2104
        $requiredNumberOfComments = (int) api_get_course_setting('portfolio_number_comments');
2105
2106
        $itemsSubtitle = '';
2107
2108
        if ($requiredNumberOfItems > 0) {
2109
            $itemsSubtitle = sprintf(
2110
                get_lang('XAddedYRequired'),
2111
                $totalNumberOfItems,
2112
                $requiredNumberOfItems
2113
            );
2114
        }
2115
2116
        $content .= Display::page_subheader2(
2117
            get_lang('PortfolioItems'),
2118
            $itemsSubtitle
2119
        ).PHP_EOL;
2120
2121
        if ($totalNumberOfItems > 0) {
2122
            $content .= $tblItems->return_table().PHP_EOL;
2123
        } else {
2124
            $content .= Display::return_message(get_lang('NoItemsInYourPortfolio'), 'warning');
2125
        }
2126
2127
        $commentsSubtitle = '';
2128
2129
        if ($requiredNumberOfComments > 0) {
2130
            $commentsSubtitle = sprintf(
2131
                get_lang('XAddedYRequired'),
2132
                $totalNumberOfComments,
2133
                $requiredNumberOfComments
2134
            );
2135
        }
2136
2137
        $content .= Display::page_subheader2(
2138
            get_lang('PortfolioCommentsMade'),
2139
            $commentsSubtitle
2140
        ).PHP_EOL;
2141
2142
        if ($totalNumberOfComments > 0) {
2143
            $content .= $tblComments->return_table().PHP_EOL;
2144
        } else {
2145
            $content .= Display::return_message(get_lang('YouHaveNotCommented'), 'warning');
2146
        }
2147
2148
        $this->renderView($content, get_lang('PortfolioDetails'), $actions);
2149
    }
2150
2151
    /**
2152
     * @throws MpdfException
2153
     */
2154
    public function exportPdf(HttpRequest $httpRequest)
2155
    {
2156
        $currentUserId = api_get_user_id();
2157
        $isAllowedToFilterStudent = $this->course && api_is_allowed_to_edit();
2158
2159
        if ($isAllowedToFilterStudent) {
2160
            if ($httpRequest->query->has('user')) {
2161
                $this->owner = api_get_user_entity($httpRequest->query->getInt('user'));
2162
2163
                if (empty($this->owner)) {
2164
                    api_not_allowed(true);
2165
                }
2166
            }
2167
        }
2168
2169
        $pdfContent = Display::page_header($this->owner->getCompleteName());
2170
2171
        if ($this->course) {
2172
            $pdfContent .= '<p>'.get_lang('Course').': ';
2173
2174
            if ($this->session) {
2175
                $pdfContent .= $this->session->getName().' ('.$this->course->getTitle().')';
2176
            } else {
2177
                $pdfContent .= $this->course->getTitle();
2178
            }
2179
2180
            $pdfContent .= '</p>';
2181
        }
2182
2183
        $visibility = [];
2184
2185
        if ($isAllowedToFilterStudent && $currentUserId !== $this->owner->getId()) {
2186
            $visibility[] = Portfolio::VISIBILITY_VISIBLE;
2187
            $visibility[] = Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER;
2188
        }
2189
2190
        $items = $this->em
2191
            ->getRepository(Portfolio::class)
2192
            ->findItemsByUser(
2193
                $this->owner,
2194
                $this->course,
2195
                $this->session,
2196
                null,
2197
                $visibility
2198
            );
2199
        $comments = $this->em
2200
            ->getRepository(PortfolioComment::class)
2201
            ->findCommentsByUser($this->owner, $this->course, $this->session);
2202
2203
        $itemsHtml = $this->getItemsInHtmlFormatted($items);
2204
        $commentsHtml = $this->getCommentsInHtmlFormatted($comments);
2205
2206
        $totalNumberOfItems = count($itemsHtml);
2207
        $totalNumberOfComments = count($commentsHtml);
2208
        $requiredNumberOfItems = (int) api_get_course_setting('portfolio_number_items');
2209
        $requiredNumberOfComments = (int) api_get_course_setting('portfolio_number_comments');
2210
2211
        $itemsSubtitle = '';
2212
        $commentsSubtitle = '';
2213
2214
        if ($requiredNumberOfItems > 0) {
2215
            $itemsSubtitle = sprintf(
2216
                get_lang('XAddedYRequired'),
2217
                $totalNumberOfItems,
2218
                $requiredNumberOfItems
2219
            );
2220
        }
2221
2222
        if ($requiredNumberOfComments > 0) {
2223
            $commentsSubtitle = sprintf(
2224
                get_lang('XAddedYRequired'),
2225
                $totalNumberOfComments,
2226
                $requiredNumberOfComments
2227
            );
2228
        }
2229
2230
        $pdfContent .= Display::page_subheader2(
2231
            get_lang('PortfolioItems'),
2232
            $itemsSubtitle
2233
        );
2234
2235
        if ($totalNumberOfItems > 0) {
2236
            $pdfContent .= implode(PHP_EOL, $itemsHtml);
2237
        } else {
2238
            $pdfContent .= Display::return_message(get_lang('NoItemsInYourPortfolio'), 'warning');
2239
        }
2240
2241
        $pdfContent .= Display::page_subheader2(
2242
            get_lang('PortfolioCommentsMade'),
2243
            $commentsSubtitle
2244
        );
2245
2246
        if ($totalNumberOfComments > 0) {
2247
            $pdfContent .= implode(PHP_EOL, $commentsHtml);
2248
        } else {
2249
            $pdfContent .= Display::return_message(get_lang('YouHaveNotCommented'), 'warning');
2250
        }
2251
2252
        $pdfName = $this->owner->getCompleteName()
2253
            .($this->course ? '_'.$this->course->getCode() : '')
2254
            .'_'.get_lang('Portfolio');
2255
2256
        HookPortfolioDownloaded::create()
2257
            ->setEventData(['owner' => $this->owner])
2258
            ->notifyPortfolioDownloaded()
2259
        ;
2260
2261
        $pdf = new PDF();
2262
        $pdf->content_to_pdf(
2263
            $pdfContent,
2264
            null,
2265
            $pdfName,
2266
            $this->course ? $this->course->getCode() : null,
2267
            'D',
2268
            false,
2269
            null,
2270
            false,
2271
            true
2272
        );
2273
    }
2274
2275
    public function exportZip(HttpRequest $httpRequest)
2276
    {
2277
        $currentUserId = api_get_user_id();
2278
        $isAllowedToFilterStudent = $this->course && api_is_allowed_to_edit();
2279
2280
        if ($isAllowedToFilterStudent) {
2281
            if ($httpRequest->query->has('user')) {
2282
                $this->owner = api_get_user_entity($httpRequest->query->getInt('user'));
2283
2284
                if (empty($this->owner)) {
2285
                    api_not_allowed(true);
2286
                }
2287
            }
2288
        }
2289
2290
        $itemsRepo = $this->em->getRepository(Portfolio::class);
2291
        $commentsRepo = $this->em->getRepository(PortfolioComment::class);
2292
        $attachmentsRepo = $this->em->getRepository(PortfolioAttachment::class);
2293
2294
        $visibility = [];
2295
2296
        if ($isAllowedToFilterStudent && $currentUserId !== $this->owner->getId()) {
2297
            $visibility[] = Portfolio::VISIBILITY_VISIBLE;
2298
            $visibility[] = Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER;
2299
        }
2300
2301
        $items = $itemsRepo->findItemsByUser(
2302
            $this->owner,
2303
            $this->course,
2304
            $this->session,
2305
            null,
2306
            $visibility
2307
        );
2308
        $comments = $commentsRepo->findCommentsByUser($this->owner, $this->course, $this->session);
2309
2310
        $itemsHtml = $this->getItemsInHtmlFormatted($items);
2311
        $commentsHtml = $this->getCommentsInHtmlFormatted($comments);
2312
2313
        $sysArchivePath = api_get_path(SYS_ARCHIVE_PATH);
2314
        $tempPortfolioDirectory = $sysArchivePath."portfolio/{$this->owner->getId()}";
2315
2316
        $userDirectory = UserManager::getUserPathById($this->owner->getId(), 'system');
2317
        $attachmentsDirectory = $userDirectory.'portfolio_attachments/';
2318
2319
        $tblItemsHeaders = [];
2320
        $tblItemsHeaders[] = get_lang('Title');
2321
        $tblItemsHeaders[] = get_lang('CreationDate');
2322
        $tblItemsHeaders[] = get_lang('LastUpdate');
2323
        $tblItemsHeaders[] = get_lang('Category');
2324
        $tblItemsHeaders[] = get_lang('Category');
2325
        $tblItemsHeaders[] = get_lang('Score');
2326
        $tblItemsHeaders[] = get_lang('Course');
2327
        $tblItemsHeaders[] = get_lang('Session');
2328
        $tblItemsData = [];
2329
2330
        $tblCommentsHeaders = [];
2331
        $tblCommentsHeaders[] = get_lang('Resume');
2332
        $tblCommentsHeaders[] = get_lang('Date');
2333
        $tblCommentsHeaders[] = get_lang('PortfolioItemTitle');
2334
        $tblCommentsHeaders[] = get_lang('Score');
2335
        $tblCommentsData = [];
2336
2337
        $filenames = [];
2338
2339
        $fs = new Filesystem();
2340
2341
        /**
2342
         * @var int       $i
2343
         * @var Portfolio $item
2344
         */
2345
        foreach ($items as $i => $item) {
2346
            $itemCategory = $item->getCategory();
2347
            $itemCourse = $item->getCourse();
2348
            $itemSession = $item->getSession();
2349
2350
            $itemDirectory = $item->getCreationDate()->format('Y-m-d-H-i-s');
2351
2352
            $itemFilename = sprintf('%s/items/%s/item.html', $tempPortfolioDirectory, $itemDirectory);
2353
            $imagePaths = [];
2354
            $itemFileContent = $this->fixMediaSourcesToHtml($itemsHtml[$i], $imagePaths);
2355
2356
            $fs->dumpFile($itemFilename, $itemFileContent);
2357
2358
            $filenames[] = $itemFilename;
2359
2360
            foreach ($imagePaths as $imagePath) {
2361
                $inlineFile = dirname($itemFilename).'/'.basename($imagePath);
2362
                $filenames[] = $inlineFile;
2363
                $fs->copy($imagePath, $inlineFile);
2364
            }
2365
2366
2367
            $attachments = $attachmentsRepo->findFromItem($item);
2368
2369
            /** @var PortfolioAttachment $attachment */
2370
            foreach ($attachments as $attachment) {
2371
                $attachmentFilename = sprintf(
2372
                    '%s/items/%s/attachments/%s',
2373
                    $tempPortfolioDirectory,
2374
                    $itemDirectory,
2375
                    $attachment->getFilename()
2376
                );
2377
2378
                $fs->copy(
2379
                    $attachmentsDirectory.$attachment->getPath(),
2380
                    $attachmentFilename
2381
                );
2382
2383
                $filenames[] = $attachmentFilename;
2384
            }
2385
2386
            $tblItemsData[] = [
2387
                Display::url(
2388
                    Security::remove_XSS($item->getTitle()),
2389
                    sprintf('items/%s/item.html', $itemDirectory)
2390
                ),
2391
                api_convert_and_format_date($item->getCreationDate()),
2392
                api_convert_and_format_date($item->getUpdateDate()),
2393
                $itemCategory ? $itemCategory->getTitle() : null,
2394
                $item->getComments()->count(),
2395
                $item->getScore(),
2396
                $itemCourse->getTitle(),
2397
                $itemSession ? $itemSession->getName() : null,
2398
            ];
2399
        }
2400
2401
        /**
2402
         * @var int              $i
2403
         * @var PortfolioComment $comment
2404
         */
2405
        foreach ($comments as $i => $comment) {
2406
            $commentDirectory = $comment->getDate()->format('Y-m-d-H-i-s');
2407
2408
            $imagePaths = [];
2409
            $commentFileContent = $this->fixMediaSourcesToHtml($commentsHtml[$i], $imagePaths);
2410
            $commentFilename = sprintf('%s/comments/%s/comment.html', $tempPortfolioDirectory, $commentDirectory);
2411
2412
            $fs->dumpFile($commentFilename, $commentFileContent);
2413
2414
            $filenames[] = $commentFilename;
2415
2416
            foreach ($imagePaths as $imagePath) {
2417
                $inlineFile = dirname($commentFilename).'/'.basename($imagePath);
2418
                $filenames[] = $inlineFile;
2419
                $fs->copy($imagePath, $inlineFile);
2420
            }
2421
2422
            $attachments = $attachmentsRepo->findFromComment($comment);
2423
2424
            /** @var PortfolioAttachment $attachment */
2425
            foreach ($attachments as $attachment) {
2426
                $attachmentFilename = sprintf(
2427
                    '%s/comments/%s/attachments/%s',
2428
                    $tempPortfolioDirectory,
2429
                    $commentDirectory,
2430
                    $attachment->getFilename()
2431
                );
2432
2433
                $fs->copy(
2434
                    $attachmentsDirectory.$attachment->getPath(),
2435
                    $attachmentFilename
2436
                );
2437
2438
                $filenames[] = $attachmentFilename;
2439
            }
2440
2441
            $tblCommentsData[] = [
2442
                Display::url(
2443
                    $comment->getExcerpt(),
2444
                    sprintf('comments/%s/comment.html', $commentDirectory)
2445
                ),
2446
                api_convert_and_format_date($comment->getDate()),
2447
                Security::remove_XSS($comment->getItem()->getTitle()),
2448
                $comment->getScore(),
2449
            ];
2450
        }
2451
2452
        $tblItems = new HTML_Table(['class' => 'table table-hover table-striped table-bordered data_table']);
2453
        $tblItems->setHeaders($tblItemsHeaders);
2454
        $tblItems->setData($tblItemsData);
2455
2456
        $tblComments = new HTML_Table(['class' => 'table table-hover table-striped table-bordered data_table']);
2457
        $tblComments->setHeaders($tblCommentsHeaders);
2458
        $tblComments->setData($tblCommentsData);
2459
2460
        $itemFilename = sprintf('%s/index.html', $tempPortfolioDirectory);
2461
2462
        $filenames[] = $itemFilename;
2463
2464
        $fs->dumpFile(
2465
            $itemFilename,
2466
            $this->formatZipIndexFile($tblItems, $tblComments)
2467
        );
2468
2469
        $zipName = $this->owner->getCompleteName()
2470
            .($this->course ? '_'.$this->course->getCode() : '')
2471
            .'_'.get_lang('Portfolio');
2472
        $tempZipFile = $sysArchivePath."portfolio/$zipName.zip";
2473
        $zip = new PclZip($tempZipFile);
2474
2475
        foreach ($filenames as $filename) {
2476
            $zip->add($filename, PCLZIP_OPT_REMOVE_PATH, $tempPortfolioDirectory);
2477
        }
2478
2479
        HookPortfolioDownloaded::create()
2480
            ->setEventData(['owner' => $this->owner])
2481
            ->notifyPortfolioDownloaded()
2482
        ;
2483
2484
        DocumentManager::file_send_for_download($tempZipFile, true, "$zipName.zip");
2485
2486
        $fs->remove($tempPortfolioDirectory);
2487
        $fs->remove($tempZipFile);
2488
    }
2489
2490
    public function qualifyItem(Portfolio $item)
2491
    {
2492
        global $interbreadcrumb;
2493
2494
        $em = Database::getManager();
2495
2496
        $formAction = $this->baseUrl.http_build_query(['action' => 'qualify', 'item' => $item->getId()]);
2497
2498
        $form = new FormValidator('frm_qualify', 'post', $formAction);
2499
        $form->addUserAvatar('user', get_lang('Author'));
2500
        $form->addLabel(get_lang('Title'), $item->getTitle());
2501
2502
        $itemContent = $this->generateItemContent($item);
2503
2504
        $form->addLabel(get_lang('Content'), $itemContent);
2505
        $form->addNumeric(
2506
            'score',
2507
            [get_lang('QualifyNumeric'), null, ' / '.api_get_course_setting('portfolio_max_score')]
2508
        );
2509
        $form->addButtonSave(get_lang('QualifyThisPortfolioItem'));
2510
2511
        if ($form->validate()) {
2512
            $values = $form->exportValues();
2513
2514
            $item->setScore($values['score']);
2515
2516
            $em->persist($item);
2517
            $em->flush();
2518
2519
            HookPortfolioItemScored::create()
2520
                ->setEventData(['item' => $item])
2521
                ->notifyItemScored()
2522
            ;
2523
2524
            Display::addFlash(
2525
                Display::return_message(get_lang('PortfolioItemGraded'), 'success')
2526
            );
2527
2528
            header("Location: $formAction");
2529
            exit();
2530
        }
2531
2532
        $form->setDefaults(
2533
            [
2534
                'user' => $item->getUser(),
2535
                'score' => (float) $item->getScore(),
2536
            ]
2537
        );
2538
2539
        $interbreadcrumb[] = [
2540
            'name' => get_lang('Portfolio'),
2541
            'url' => $this->baseUrl,
2542
        ];
2543
        $interbreadcrumb[] = [
2544
            'name' => $item->getTitle(true),
2545
            'url' => $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()]),
2546
        ];
2547
2548
        $actions = [];
2549
        $actions[] = Display::url(
2550
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
2551
            $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()])
2552
        );
2553
2554
        $this->renderView($form->returnForm(), get_lang('Qualify'), $actions);
2555
    }
2556
2557
    public function qualifyComment(PortfolioComment $comment)
2558
    {
2559
        global $interbreadcrumb;
2560
2561
        $em = Database::getManager();
2562
2563
        $item = $comment->getItem();
2564
        $commentPath = $em->getRepository(PortfolioComment::class)->getPath($comment);
2565
2566
        $template = new Template('', false, false, false, true, false, false);
2567
        $template->assign('item', $item);
2568
        $template->assign('comments_path', $commentPath);
2569
        $commentContext = $template->fetch(
2570
            $template->get_template('portfolio/comment_context.html.twig')
2571
        );
2572
2573
        $formAction = $this->baseUrl.http_build_query(['action' => 'qualify', 'comment' => $comment->getId()]);
2574
2575
        $form = new FormValidator('frm_qualify', 'post', $formAction);
2576
        $form->addHtml($commentContext);
2577
        $form->addUserAvatar('user', get_lang('Author'));
2578
        $form->addLabel(get_lang('Comment'), $comment->getContent());
2579
        $form->addNumeric(
2580
            'score',
2581
            [get_lang('QualifyNumeric'), null, '/ '.api_get_course_setting('portfolio_max_score')]
2582
        );
2583
        $form->addButtonSave(get_lang('QualifyThisPortfolioComment'));
2584
2585
        if ($form->validate()) {
2586
            $values = $form->exportValues();
2587
2588
            $comment->setScore($values['score']);
2589
2590
            $em->persist($comment);
2591
            $em->flush();
2592
2593
            HookPortfolioCommentScored::create()
2594
                ->setEventData(['comment' => $comment])
2595
                ->notifyCommentScored()
2596
            ;
2597
2598
            Display::addFlash(
2599
                Display::return_message(get_lang('PortfolioCommentGraded'), 'success')
2600
            );
2601
2602
            header("Location: $formAction");
2603
            exit();
2604
        }
2605
2606
        $form->setDefaults(
2607
            [
2608
                'user' => $comment->getAuthor(),
2609
                'score' => (float) $comment->getScore(),
2610
            ]
2611
        );
2612
2613
        $interbreadcrumb[] = [
2614
            'name' => get_lang('Portfolio'),
2615
            'url' => $this->baseUrl,
2616
        ];
2617
        $interbreadcrumb[] = [
2618
            'name' => $item->getTitle(true),
2619
            'url' => $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()]),
2620
        ];
2621
2622
        $actions = [];
2623
        $actions[] = Display::url(
2624
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
2625
            $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()])
2626
        );
2627
2628
        $this->renderView($form->returnForm(), get_lang('Qualify'), $actions);
2629
    }
2630
2631
    public function downloadAttachment(HttpRequest $httpRequest)
2632
    {
2633
        $path = $httpRequest->query->get('file');
2634
2635
        if (empty($path)) {
2636
            api_not_allowed(true);
2637
        }
2638
2639
        $em = Database::getManager();
2640
        $attachmentRepo = $em->getRepository(PortfolioAttachment::class);
2641
2642
        $attachment = $attachmentRepo->findOneByPath($path);
2643
2644
        if (empty($attachment)) {
2645
            api_not_allowed(true);
2646
        }
2647
2648
        $originOwnerId = 0;
2649
2650
        if (PortfolioAttachment::TYPE_ITEM === $attachment->getOriginType()) {
2651
            $item = $em->find(Portfolio::class, $attachment->getOrigin());
2652
2653
            $originOwnerId = $item->getUser()->getId();
2654
        } elseif (PortfolioAttachment::TYPE_COMMENT === $attachment->getOriginType()) {
2655
            $comment = $em->find(PortfolioComment::class, $attachment->getOrigin());
2656
2657
            $originOwnerId = $comment->getAuthor()->getId();
2658
        } else {
2659
            api_not_allowed(true);
2660
        }
2661
2662
        $userDirectory = UserManager::getUserPathById($originOwnerId, 'system');
2663
        $attachmentsDirectory = $userDirectory.'portfolio_attachments/';
2664
        $attachmentFilename = $attachmentsDirectory.$attachment->getPath();
2665
2666
        if (!Security::check_abs_path($attachmentFilename, $attachmentsDirectory)) {
2667
            api_not_allowed(true);
2668
        }
2669
2670
        $downloaded = DocumentManager::file_send_for_download(
2671
            $attachmentFilename,
2672
            true,
2673
            $attachment->getFilename()
2674
        );
2675
2676
        if (!$downloaded) {
2677
            api_not_allowed(true);
2678
        }
2679
    }
2680
2681
    public function deleteAttachment(HttpRequest $httpRequest)
2682
    {
2683
        $currentUserId = api_get_user_id();
2684
2685
        $path = $httpRequest->query->get('file');
2686
2687
        if (empty($path)) {
2688
            api_not_allowed(true);
2689
        }
2690
2691
        $em = Database::getManager();
2692
        $fs = new Filesystem();
2693
2694
        $attachmentRepo = $em->getRepository(PortfolioAttachment::class);
2695
        $attachment = $attachmentRepo->findOneByPath($path);
2696
2697
        if (empty($attachment)) {
2698
            api_not_allowed(true);
2699
        }
2700
2701
        $originOwnerId = 0;
2702
        $itemId = 0;
2703
2704
        if (PortfolioAttachment::TYPE_ITEM === $attachment->getOriginType()) {
2705
            $item = $em->find(Portfolio::class, $attachment->getOrigin());
2706
            $originOwnerId = $item->getUser()->getId();
2707
            $itemId = $item->getId();
2708
        } elseif (PortfolioAttachment::TYPE_COMMENT === $attachment->getOriginType()) {
2709
            $comment = $em->find(PortfolioComment::class, $attachment->getOrigin());
2710
            $originOwnerId = $comment->getAuthor()->getId();
2711
            $itemId = $comment->getItem()->getId();
2712
        }
2713
2714
        if ($currentUserId !== $originOwnerId) {
2715
            api_not_allowed(true);
2716
        }
2717
2718
        $em->remove($attachment);
2719
        $em->flush();
2720
2721
        $userDirectory = UserManager::getUserPathById($originOwnerId, 'system');
2722
        $attachmentsDirectory = $userDirectory.'portfolio_attachments/';
2723
        $attachmentFilename = $attachmentsDirectory.$attachment->getPath();
2724
2725
        $fs->remove($attachmentFilename);
2726
2727
        if ($httpRequest->isXmlHttpRequest()) {
2728
            echo Display::return_message(get_lang('AttachmentFileDeleteSuccess'), 'success');
2729
        } else {
2730
            Display::addFlash(
2731
                Display::return_message(get_lang('AttachmentFileDeleteSuccess'), 'success')
2732
            );
2733
2734
            $url = $this->baseUrl.http_build_query(['action' => 'view', 'id' => $itemId]);
2735
2736
            if (PortfolioAttachment::TYPE_COMMENT === $attachment->getOriginType() && isset($comment)) {
2737
                $url .= '#comment-'.$comment->getId();
2738
            }
2739
2740
            header("Location: $url");
2741
        }
2742
2743
        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...
2744
    }
2745
2746
    /**
2747
     * @throws \Doctrine\ORM\OptimisticLockException
2748
     * @throws \Doctrine\ORM\ORMException
2749
     */
2750
    public function markAsHighlighted(Portfolio $item)
2751
    {
2752
        if ($item->getCourse()->getId() !== (int) api_get_course_int_id()) {
2753
            api_not_allowed(true);
2754
        }
2755
2756
        $item->setIsHighlighted(
2757
            !$item->isHighlighted()
2758
        );
2759
2760
        Database::getManager()->flush();
2761
2762
        if ($item->isHighlighted()) {
2763
            HookPortfolioItemHighlighted::create()
2764
                ->setEventData(['item' => $item])
2765
                ->notifyItemHighlighted()
2766
            ;
2767
        }
2768
2769
        Display::addFlash(
2770
            Display::return_message(
2771
                $item->isHighlighted() ? get_lang('MarkedAsHighlighted') : get_lang('UnmarkedAsHighlighted'),
2772
                'success'
2773
            )
2774
        );
2775
2776
        header("Location: $this->baseUrl".http_build_query(['action' => 'view', 'id' => $item->getId()]));
2777
        exit;
2778
    }
2779
2780
    public function markAsTemplate(Portfolio $item)
2781
    {
2782
        if (!$this->itemBelongToOwner($item)) {
2783
            api_not_allowed(true);
2784
        }
2785
2786
        $item->setIsTemplate(
2787
            !$item->isTemplate()
2788
        );
2789
2790
        Database::getManager()->flush($item);
2791
2792
        Display::addFlash(
2793
            Display::return_message(
2794
                $item->isTemplate() ? get_lang('PortfolioItemSetAsTemplate') : get_lang('PortfolioItemUnsetAsTemplate'),
2795
                'success'
2796
            )
2797
        );
2798
2799
        header("Location: $this->baseUrl".http_build_query(['action' => 'view', 'id' => $item->getId()]));
2800
        exit;
2801
    }
2802
2803
    public function markAsTemplateComment(PortfolioComment $comment)
2804
    {
2805
        if (!$this->commentBelongsToOwner($comment)) {
2806
            api_not_allowed(true);
2807
        }
2808
2809
        $comment->setIsTemplate(
2810
            !$comment->isTemplate()
2811
        );
2812
2813
        Database::getManager()->flush();
2814
2815
        Display::addFlash(
2816
            Display::return_message(
2817
                $comment->isTemplate() ? get_lang('PortfolioCommentSetAsTemplate') : get_lang('PortfolioCommentUnsetAsTemplate'),
2818
                'success'
2819
            )
2820
        );
2821
2822
        header("Location: $this->baseUrl".http_build_query(['action' => 'view', 'id' => $comment->getItem()->getId()]));
2823
        exit;
2824
    }
2825
2826
    public function listTags(HttpRequest $request)
2827
    {
2828
        global $interbreadcrumb;
2829
2830
        api_protect_course_script();
2831
        api_protect_teacher_script();
2832
2833
        $em = Database::getManager();
2834
        $tagRepo = $em->getRepository(Tag::class);
2835
2836
        $tagsQuery = $tagRepo->findForPortfolioInCourseQuery($this->course, $this->session);
2837
2838
        $tag = $request->query->has('id')
2839
            ? $tagRepo->find($request->query->getInt('id'))
2840
            : null;
2841
2842
        $formAction = ['action' => $request->query->get('action')];
2843
2844
        if ($tag) {
2845
            $formAction['id'] = $tag->getId();
2846
        }
2847
2848
        $form = new FormValidator('frm_add_tag', 'post', $this->baseUrl.http_build_query($formAction));
2849
        $form->addText('name', get_lang('Tag'));
2850
2851
        if ($tag) {
2852
            $form->addButtonUpdate(get_lang('Edit'));
2853
        } else {
2854
            $form->addButtonCreate(get_lang('Add'));
2855
        }
2856
2857
        if ($form->validate()) {
2858
            $values = $form->exportValues();
2859
2860
            $extraFieldInfo = (new ExtraField('portfolio'))->get_handler_field_info_by_field_variable('tags');
2861
2862
            if (!$tag) {
2863
                $tag = (new Tag())->setCount(0);
2864
2865
                $portfolioRelTag = (new PortfolioRelTag())
2866
                    ->setTag($tag)
2867
                    ->setCourse($this->course)
2868
                    ->setSession($this->session)
2869
                ;
2870
2871
                $em->persist($tag);
2872
                $em->persist($portfolioRelTag);
2873
            }
2874
2875
            $tag
2876
                ->setTag($values['name'])
2877
                ->setFieldId((int) $extraFieldInfo['id'])
2878
            ;
2879
2880
            $em->flush();
2881
2882
            Display::addFlash(
2883
                Display::return_message(get_lang('TagSaved'), 'success')
2884
            );
2885
2886
            header('Location: '.$this->baseUrl.http_build_query($formAction));
2887
            exit();
2888
        } else {
2889
            $form->protect();
2890
2891
            if ($tag) {
2892
                $form->setDefaults(['name' => $tag->getTag()]);
2893
            }
2894
        }
2895
2896
        $langTags = get_lang('Tags');
2897
        $langEdit = get_lang('Edit');
2898
2899
        $deleteIcon = Display::return_icon('delete.png', get_lang('Delete'));
2900
        $editIcon = Display::return_icon('edit.png', $langEdit);
2901
2902
        $table = new SortableTable(
2903
            'portfolio_tags',
2904
            function () use ($tagsQuery) {
2905
                return (int) $tagsQuery
2906
                    ->select('COUNT(t)')
2907
                    ->getQuery()
2908
                    ->getSingleScalarResult()
2909
                ;
2910
            },
2911
            function ($from, $limit, $column, $direction) use ($tagsQuery) {
2912
                $data = [];
2913
2914
                /** @var array<int, Tag> $tags */
2915
                $tags = $tagsQuery
2916
                    ->select('t')
2917
                    ->orderBy('t.tag', $direction)
2918
                    ->setFirstResult($from)
2919
                    ->setMaxResults($limit)
2920
                    ->getQuery()
2921
                    ->getResult();
2922
2923
                foreach ($tags as $tag) {
2924
                    $data[] = [
2925
                        $tag->getTag(),
2926
                        $tag->getId(),
2927
                    ];
2928
                }
2929
2930
                return $data;
2931
            },
2932
            0,
2933
            40
2934
        );
2935
        $table->set_header(0, get_lang('Name'));
2936
        $table->set_header(1, get_lang('Actions'), false, ['class' => 'text-right'], ['class' => 'text-right']);
2937
        $table->set_column_filter(
2938
            1,
2939
            function ($id) use ($editIcon, $deleteIcon) {
2940
                $editParams = http_build_query(['action' => 'edit_tag', 'id' => $id]);
2941
                $deleteParams = http_build_query(['action' => 'delete_tag', 'id' => $id]);
2942
2943
                return Display::url($editIcon, $this->baseUrl.$editParams).PHP_EOL
2944
                    .Display::url($deleteIcon, $this->baseUrl.$deleteParams).PHP_EOL;
2945
            }
2946
        );
2947
        $table->set_additional_parameters(
2948
            [
2949
                'action' => 'tags',
2950
                'cidReq' => $this->course->getCode(),
2951
                'id_session' => $this->session ? $this->session->getId() : 0,
2952
                'gidReq' => 0,
2953
            ]
2954
        );
2955
2956
        $content = $form->returnForm().PHP_EOL
2957
            .$table->return_table();
2958
2959
        $interbreadcrumb[] = [
2960
            'name' => get_lang('Portfolio'),
2961
            'url' => $this->baseUrl,
2962
        ];
2963
2964
        $pageTitle = $langTags;
2965
2966
        if ($tag) {
2967
            $pageTitle = $langEdit;
2968
2969
            $interbreadcrumb[] = [
2970
                'name' => $langTags,
2971
                'url' => $this->baseUrl.'action=tags',
2972
            ];
2973
        }
2974
2975
        $this->renderView($content, $pageTitle);
2976
    }
2977
2978
    public function deleteTag(Tag $tag)
2979
    {
2980
        api_protect_course_script();
2981
        api_protect_teacher_script();
2982
2983
        $em = Database::getManager();
2984
        $portfolioTagRepo = $em->getRepository(PortfolioRelTag::class);
2985
2986
        $portfolioTag = $portfolioTagRepo
2987
            ->findOneBy(['tag' => $tag, 'course' => $this->course, 'session' => $this->session]);
2988
2989
        if ($portfolioTag) {
2990
            $em->remove($portfolioTag);
2991
            $em->flush();
2992
2993
            Display::addFlash(
2994
                Display::return_message(get_lang('TagDeleted'), 'success')
2995
            );
2996
        }
2997
2998
        header('Location: '.$this->baseUrl.http_build_query(['action' => 'tags']));
2999
        exit();
3000
    }
3001
3002
    /**
3003
     * @throws \Doctrine\ORM\OptimisticLockException
3004
     * @throws \Doctrine\ORM\ORMException
3005
     */
3006
    public function editComment(PortfolioComment $comment)
3007
    {
3008
        global $interbreadcrumb;
3009
3010
        if (!$this->commentBelongsToOwner($comment)) {
3011
            api_not_allowed(true);
3012
        }
3013
3014
        $item = $comment->getItem();
3015
        $commmentCourse = $item->getCourse();
3016
        $commmentSession = $item->getSession();
3017
3018
        $formAction = $this->baseUrl.http_build_query(['action' => 'edit_comment', 'id' => $comment->getId()]);
3019
3020
        $form = new FormValidator('frm_comment', 'post', $formAction);
3021
        $form->addLabel(
3022
            get_lang('Date'),
3023
            $this->getLabelForCommentDate($comment)
3024
        );
3025
        $form->addHtmlEditor('content', get_lang('Comments'), true, false, ['ToolbarSet' => 'Minimal']);
3026
        $form->applyFilter('content', 'trim');
3027
3028
        $this->addAttachmentsFieldToForm($form);
3029
3030
        $form->addButtonUpdate(get_lang('Update'));
3031
3032
        if ($form->validate()) {
3033
            if ($commmentCourse) {
3034
                api_item_property_update(
3035
                    api_get_course_info($commmentCourse->getCode()),
3036
                    TOOL_PORTFOLIO_COMMENT,
3037
                    $comment->getId(),
3038
                    'PortfolioCommentUpdated',
3039
                    api_get_user_id(),
3040
                    [],
3041
                    null,
3042
                    '',
3043
                    '',
3044
                    $commmentSession ? $commmentSession->getId() : 0
3045
                );
3046
            }
3047
3048
            $values = $form->exportValues();
3049
3050
            $comment->setContent($values['content']);
3051
3052
            $this->em->flush();
3053
3054
            $this->processAttachments(
3055
                $form,
3056
                $comment->getAuthor(),
3057
                $comment->getId(),
3058
                PortfolioAttachment::TYPE_COMMENT
3059
            );
3060
3061
            HookPortfolioCommentEdited::create()
3062
                ->setEventData(['comment' => $comment])
3063
                ->notifyCommentEdited()
3064
            ;
3065
3066
            Display::addFlash(
3067
                Display::return_message(get_lang('ItemUpdated'), 'success')
3068
            );
3069
3070
            header("Location: $this->baseUrl"
3071
                .http_build_query(['action' => 'view', 'id' => $item->getId()])
3072
                .'#comment-'.$comment->getId()
3073
            );
3074
            exit;
3075
        }
3076
3077
        $form->setDefaults([
3078
            'content' => $comment->getContent(),
3079
        ]);
3080
3081
        $interbreadcrumb[] = [
3082
            'name' => get_lang('Portfolio'),
3083
            'url' => $this->baseUrl,
3084
        ];
3085
        $interbreadcrumb[] = [
3086
            'name' => $item->getTitle(true),
3087
            'url' => $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()]),
3088
        ];
3089
3090
        $actions = [];
3091
        $actions[] = Display::url(
3092
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
3093
            $this->baseUrl
3094
        );
3095
3096
        $content = $form->returnForm()
3097
            .PHP_EOL
3098
            .'<div class="row"> <div class="col-sm-8 col-sm-offset-2">'
3099
            .$this->generateAttachmentList($comment)
3100
            .'</div></div>';
3101
3102
        $this->renderView(
3103
            $content,
3104
            get_lang('EditPortfolioComment'),
3105
            $actions
3106
        );
3107
    }
3108
3109
    /**
3110
     * @throws \Doctrine\ORM\OptimisticLockException
3111
     * @throws \Doctrine\ORM\ORMException
3112
     */
3113
    public function deleteComment(PortfolioComment $comment)
3114
    {
3115
        if (!$this->commentBelongsToOwner($comment)) {
3116
            api_not_allowed(true);
3117
        }
3118
3119
        $this->em->remove($comment);
3120
3121
        $this->em
3122
            ->getRepository(PortfolioAttachment::class)
3123
            ->removeFromComment($comment);
3124
3125
        $this->em->flush();
3126
3127
        Display::addFlash(
3128
            Display::return_message(get_lang('CommentDeleted'), 'success')
3129
        );
3130
3131
        header("Location: $this->baseUrl");
3132
        exit;
3133
    }
3134
3135
    public function itemVisibilityChooser(Portfolio $item)
3136
    {
3137
        global $interbreadcrumb;
3138
3139
        if (!$this->itemBelongToOwner($item)) {
3140
            api_not_allowed(true);
3141
        }
3142
3143
        $em = Database::getManager();
3144
        $tblItemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
3145
3146
        $courseId = $this->course->getId();
3147
        $sessionId = $this->session ? $this->session->getId() : 0;
3148
3149
        $formAction = $this->baseUrl.http_build_query(['action' => 'item_visiblity_choose', 'id' => $item->getId()]);
3150
3151
        $form = new FormValidator('visibility', 'post', $formAction);
3152
        CourseManager::addUserGroupMultiSelect($form, ['USER:'.$this->owner->getId()]);
3153
        $form->addLabel(
3154
            '',
3155
            Display::return_message(
3156
                get_lang('OnlySelectedUsersWillSeeTheContent')
3157
                    .'<br>'.get_lang('LeaveEmptyToEnableTheContentForEveryone'),
3158
                'info',
3159
                false
3160
            )
3161
        );
3162
        $form->addCheckBox('hidden', '', get_lang('HiddenButVisibleForMe'));
3163
        $form->addButtonSave(get_lang('Save'));
3164
3165
        if ($form->validate()) {
3166
            $values = $form->exportValues();
3167
            $recipients = CourseManager::separateUsersGroups($values['users'])['users'];
3168
            $courseInfo = api_get_course_info_by_id($courseId);
3169
3170
            Database::delete(
3171
                $tblItemProperty,
3172
                [
3173
                    'c_id = ? ' => [$courseId],
3174
                    'AND tool = ? AND ref = ? ' => [TOOL_PORTFOLIO, $item->getId()],
3175
                    'AND lastedit_type = ? ' => ['visible'],
3176
                ]
3177
            );
3178
3179
            if (empty($recipients) && empty($values['hidden'])) {
3180
                $item->setVisibility(Portfolio::VISIBILITY_VISIBLE);
3181
            } else {
3182
                if (empty($values['hidden'])) {
3183
                    foreach ($recipients as $userId) {
3184
                        api_item_property_update(
3185
                            $courseInfo,
3186
                            TOOL_PORTFOLIO,
3187
                            $item->getId(),
3188
                            'visible',
3189
                            api_get_user_id(),
3190
                            [],
3191
                            $userId,
3192
                            '',
3193
                            '',
3194
                            $sessionId
3195
                        );
3196
                    }
3197
                }
3198
3199
                $item->setVisibility(Portfolio::VISIBILITY_PER_USER);
3200
            }
3201
3202
            $em->flush();
3203
3204
            HookPortfolioItemVisibility::create()
3205
                ->setEventData([
3206
                    'item' => $item,
3207
                    'recipients' => array_values($recipients),
3208
                ])
3209
                ->notifyItemVisibility()
3210
            ;
3211
3212
            Display::addFlash(
3213
                Display::return_message(get_lang('VisibilityChanged'), 'success')
3214
            );
3215
3216
            header("Location: $formAction");
3217
            exit;
3218
        }
3219
3220
        $result = Database::select(
3221
            'to_user_id',
3222
            $tblItemProperty,
3223
            [
3224
                'where' => [
3225
                    'c_id = ? ' => [$courseId],
3226
                    'AND tool = ? AND ref = ? ' => [TOOL_PORTFOLIO, $item->getId()],
3227
                    'AND to_user_id IS NOT NULL ' => [],
3228
                ],
3229
            ]
3230
        );
3231
3232
        $recipients = array_map(
3233
            function (array $item): string {
3234
                return 'USER:'.$item['to_user_id'];
3235
            },
3236
            $result
3237
        );
3238
3239
        $defaults = ['users' => $recipients];
3240
3241
        if (empty($recipients) && Portfolio::VISIBILITY_PER_USER === $item->getVisibility()) {
3242
            $defaults['hidden'] = true;
3243
        }
3244
3245
        $form->setDefaults($defaults);
3246
        $form->protect();
3247
3248
        $interbreadcrumb[] = [
3249
            'name' => get_lang('Portfolio'),
3250
            'url' => $this->baseUrl,
3251
        ];
3252
        $interbreadcrumb[] = [
3253
            'name' => $item->getTitle(true),
3254
            'url' => $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()]),
3255
        ];
3256
3257
        $actions = [];
3258
        $actions[] = Display::url(
3259
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
3260
            $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()])
3261
        );
3262
3263
        $this->renderView(
3264
            $form->returnForm(),
3265
            get_lang('ChooseRecipients'),
3266
            $actions
3267
        );
3268
    }
3269
3270
    public function commentVisibilityChooser(PortfolioComment $comment)
3271
    {
3272
        global $interbreadcrumb;
3273
3274
        if (!$this->commentBelongsToOwner($comment)) {
3275
            api_not_allowed(true);
3276
        }
3277
3278
        $em = Database::getManager();
3279
        $tblItemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
3280
3281
        $courseId = $this->course->getId();
3282
        $sessionId = $this->session ? $this->session->getId() : 0;
3283
        $item = $comment->getItem();
3284
3285
        $formAction = $this->baseUrl.http_build_query(['action' => 'comment_visiblity_choose', 'id' => $comment->getId()]);
3286
3287
        $form = new FormValidator('visibility', 'post', $formAction);
3288
        CourseManager::addUserGroupMultiSelect($form, ['USER:'.$this->owner->getId()]);
3289
        $form->addLabel(
3290
            '',
3291
            Display::return_message(
3292
                get_lang('OnlySelectedUsersWillSeeTheContent')
3293
                    .'<br>'.get_lang('LeaveEmptyToEnableTheContentForEveryone'),
3294
                'info',
3295
                false
3296
            )
3297
        );
3298
        $form->addCheckBox('hidden', '', get_lang('HiddenButVisibleForMe'));
3299
        $form->addButtonSave(get_lang('Save'));
3300
3301
        if ($form->validate()) {
3302
            $values = $form->exportValues();
3303
            $recipients = CourseManager::separateUsersGroups($values['users'])['users'];
3304
            $courseInfo = api_get_course_info_by_id($courseId);
3305
3306
            Database::delete(
3307
                $tblItemProperty,
3308
                [
3309
                    'c_id = ? ' => [$courseId],
3310
                    'AND tool = ? AND ref = ? ' => [TOOL_PORTFOLIO_COMMENT, $comment->getId()],
3311
                    'AND lastedit_type = ? ' => ['visible'],
3312
                ]
3313
            );
3314
3315
            if (empty($recipients) && empty($values['hidden'])) {
3316
                $comment->setVisibility(PortfolioComment::VISIBILITY_VISIBLE);
3317
            } else {
3318
                if (empty($values['hidden'])) {
3319
                    foreach ($recipients as $userId) {
3320
                        api_item_property_update(
3321
                            $courseInfo,
3322
                            TOOL_PORTFOLIO_COMMENT,
3323
                            $comment->getId(),
3324
                            'visible',
3325
                            api_get_user_id(),
3326
                            [],
3327
                            $userId,
3328
                            '',
3329
                            '',
3330
                            $sessionId
3331
                        );
3332
                    }
3333
                }
3334
3335
                $comment->setVisibility(PortfolioComment::VISIBILITY_PER_USER);
3336
            }
3337
3338
            $em->flush();
3339
3340
            Display::addFlash(
3341
                Display::return_message(get_lang('VisibilityChanged'), 'success')
3342
            );
3343
3344
            header("Location: $formAction");
3345
            exit;
3346
        }
3347
3348
        $result = Database::select(
3349
            'to_user_id',
3350
            $tblItemProperty,
3351
            [
3352
                'where' => [
3353
                    'c_id = ? ' => [$courseId],
3354
                    'AND tool = ? AND ref = ? ' => [TOOL_PORTFOLIO_COMMENT, $comment->getId()],
3355
                    'AND to_user_id IS NOT NULL ' => [],
3356
                ],
3357
            ]
3358
        );
3359
3360
        $recipients = array_map(
3361
            function (array $itemProperty): string {
3362
                return 'USER:'.$itemProperty['to_user_id'];
3363
            },
3364
            $result
3365
        );
3366
3367
        $defaults = ['users' => $recipients];
3368
3369
        if (empty($recipients) && PortfolioComment::VISIBILITY_PER_USER === $comment->getVisibility()) {
3370
            $defaults['hidden'] = true;
3371
        }
3372
3373
        $form->setDefaults($defaults);
3374
        $form->protect();
3375
3376
        $interbreadcrumb[] = [
3377
            'name' => get_lang('Portfolio'),
3378
            'url' => $this->baseUrl,
3379
        ];
3380
        $interbreadcrumb[] = [
3381
            'name' => $item->getTitle(true),
3382
            'url' => $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()]),
3383
        ];
3384
        $interbreadcrumb[] = [
3385
            'name' => $comment->getExcerpt(40),
3386
            'url' => $this->baseUrl
3387
                .http_build_query(['action' => 'view', 'id' => $item->getId()])
3388
                .'#comment-'.$comment->getId(),
3389
        ];
3390
3391
        $actions = [];
3392
        $actions[] = Display::url(
3393
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
3394
            $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()])
3395
        );
3396
3397
        $this->renderView(
3398
            $form->returnForm(),
3399
            get_lang('ChooseRecipients'),
3400
            $actions
3401
        );
3402
    }
3403
3404
    private function isAllowed(): bool
3405
    {
3406
        $isSubscribedInCourse = false;
3407
3408
        if ($this->course) {
3409
            $isSubscribedInCourse = CourseManager::is_user_subscribed_in_course(
3410
                api_get_user_id(),
3411
                $this->course->getCode(),
3412
                (bool) $this->session,
3413
                $this->session ? $this->session->getId() : 0
3414
            );
3415
        }
3416
3417
        if (!$this->course || $isSubscribedInCourse) {
3418
            return true;
3419
        }
3420
3421
        return false;
3422
    }
3423
3424
    private function blockIsNotAllowed()
3425
    {
3426
        if (!$this->isAllowed()) {
3427
            api_not_allowed(true);
3428
        }
3429
    }
3430
3431
    /**
3432
     * @param bool $showHeader
3433
     */
3434
    private function renderView(string $content, string $toolName, array $actions = [], $showHeader = true)
3435
    {
3436
        global $this_section;
3437
3438
        $this_section = $this->course ? SECTION_COURSES : SECTION_SOCIAL;
3439
3440
        $view = new Template($toolName);
3441
3442
        if ($showHeader) {
3443
            $view->assign('header', $toolName);
3444
        }
3445
3446
        $actionsStr = '';
3447
3448
        if ($this->course) {
3449
            $actionsStr .= Display::return_introduction_section(TOOL_PORTFOLIO);
3450
        }
3451
3452
        if ($actions) {
3453
            $actions = implode('', $actions);
3454
3455
            $actionsStr .= Display::toolbarAction('portfolio-toolbar', [$actions]);
3456
        }
3457
3458
        $view->assign('baseurl', $this->baseUrl);
3459
        $view->assign('actions', $actionsStr);
3460
3461
        $view->assign('content', $content);
3462
        $view->display_one_col_template();
3463
    }
3464
3465
    private function categoryBelongToOwner(PortfolioCategory $category): bool
3466
    {
3467
        if ($category->getUser()->getId() != $this->owner->getId()) {
3468
            return false;
3469
        }
3470
3471
        return true;
3472
    }
3473
3474
    private function addAttachmentsFieldToForm(FormValidator $form)
3475
    {
3476
        $form->addButton('add_attachment', get_lang('AddAttachment'), 'plus');
3477
        $form->addHtml('<div id="container-attachments" style="display: none;">');
3478
        $form->addFile('attachment_file[]', get_lang('FilesAttachment'));
3479
        $form->addText('attachment_comment[]', get_lang('Description'), false);
3480
        $form->addHtml('</div>');
3481
3482
        $script = "$(function () {
3483
            var attachmentsTemplate = $('#container-attachments').html();
3484
            var \$btnAdd = $('[name=\"add_attachment\"]');
3485
            var \$reference = \$btnAdd.parents('.form-group');
3486
3487
            \$btnAdd.on('click', function (e) {
3488
                e.preventDefault();
3489
3490
                $(attachmentsTemplate).insertBefore(\$reference);
3491
            });
3492
        })";
3493
3494
        $form->addHtml("<script>$script</script>");
3495
    }
3496
3497
    private function processAttachments(
3498
        FormValidator $form,
3499
        User $user,
3500
        int $originId,
3501
        int $originType
3502
    ) {
3503
        $em = Database::getManager();
3504
        $fs = new Filesystem();
3505
3506
        $comments = $form->getSubmitValue('attachment_comment');
3507
3508
        foreach ($_FILES['attachment_file']['error'] as $i => $attachmentFileError) {
3509
            if ($attachmentFileError != UPLOAD_ERR_OK) {
3510
                continue;
3511
            }
3512
3513
            $_file = [
3514
                'name' => $_FILES['attachment_file']['name'][$i],
3515
                'type' => $_FILES['attachment_file']['type'][$i],
3516
                'tmp_name' => $_FILES['attachment_file']['tmp_name'][$i],
3517
                'size' => $_FILES['attachment_file']['size'][$i],
3518
            ];
3519
3520
            if (empty($_file['type'])) {
3521
                $_file['type'] = DocumentManager::file_get_mime_type($_file['name']);
3522
            }
3523
3524
            $newFileName = add_ext_on_mime(stripslashes($_file['name']), $_file['type']);
3525
3526
            if (!filter_extension($newFileName)) {
3527
                Display::addFlash(Display::return_message(get_lang('UplUnableToSaveFileFilteredExtension'), 'error'));
3528
                continue;
3529
            }
3530
3531
            $newFileName = uniqid();
3532
            $attachmentsDirectory = UserManager::getUserPathById($user->getId(), 'system').'portfolio_attachments/';
3533
3534
            if (!$fs->exists($attachmentsDirectory)) {
3535
                $fs->mkdir($attachmentsDirectory, api_get_permissions_for_new_directories());
3536
            }
3537
3538
            $attachmentFilename = $attachmentsDirectory.$newFileName;
3539
3540
            if (is_uploaded_file($_file['tmp_name'])) {
3541
                $moved = move_uploaded_file($_file['tmp_name'], $attachmentFilename);
3542
3543
                if (!$moved) {
3544
                    Display::addFlash(Display::return_message(get_lang('UplUnableToSaveFile'), 'error'));
3545
                    continue;
3546
                }
3547
            }
3548
3549
            $attachment = new PortfolioAttachment();
3550
            $attachment
3551
                ->setFilename($_file['name'])
3552
                ->setComment($comments[$i])
3553
                ->setPath($newFileName)
3554
                ->setOrigin($originId)
3555
                ->setOriginType($originType)
3556
                ->setSize($_file['size']);
3557
3558
            $em->persist($attachment);
3559
            $em->flush();
3560
        }
3561
    }
3562
3563
    private function itemBelongToOwner(Portfolio $item): bool
3564
    {
3565
        if ($item->getUser()->getId() != $this->owner->getId()) {
3566
            return false;
3567
        }
3568
3569
        return true;
3570
    }
3571
3572
    private function commentBelongsToOwner(PortfolioComment $comment): bool
3573
    {
3574
        return $comment->getAuthor() === $this->owner;
3575
    }
3576
3577
    private function createFormTagFilter(bool $listByUser = false): FormValidator
3578
    {
3579
        $tags = Database::getManager()
3580
            ->getRepository(Tag::class)
3581
            ->findForPortfolioInCourseQuery($this->course, $this->session)
3582
            ->getQuery()
3583
            ->getResult()
3584
        ;
3585
3586
        $frmTagList = new FormValidator(
3587
            'frm_tag_list',
3588
            'get',
3589
            $this->baseUrl.($listByUser ? 'user='.$this->owner->getId() : ''),
3590
            '',
3591
            [],
3592
            FormValidator::LAYOUT_BOX
3593
        );
3594
3595
        $frmTagList->addDatePicker('date', get_lang('CreationDate'));
3596
3597
        $frmTagList->addSelectFromCollection(
3598
            'tags',
3599
            get_lang('Tags'),
3600
            $tags,
3601
            ['multiple' => 'multiple'],
3602
            false,
3603
            'getTag'
3604
        );
3605
3606
        $frmTagList->addText('text', get_lang('Search'), false)->setIcon('search');
3607
        $frmTagList->applyFilter('text', 'trim');
3608
        $frmTagList->addHtml('<br>');
3609
        $frmTagList->addButtonFilter(get_lang('Filter'));
3610
3611
        if ($this->course) {
3612
            $frmTagList->addHidden('cidReq', $this->course->getCode());
3613
            $frmTagList->addHidden('id_session', $this->session ? $this->session->getId() : 0);
3614
            $frmTagList->addHidden('gidReq', 0);
3615
            $frmTagList->addHidden('gradebook', 0);
3616
            $frmTagList->addHidden('origin', '');
3617
            $frmTagList->addHidden('categoryId', 0);
3618
            $frmTagList->addHidden('subCategoryIds', '');
3619
3620
            if ($listByUser) {
3621
                $frmTagList->addHidden('user', $this->owner->getId());
3622
            }
3623
        }
3624
3625
        return $frmTagList;
3626
    }
3627
3628
    /**
3629
     * @throws Exception
3630
     */
3631
    private function createFormStudentFilter(bool $listByUser = false, bool $listHighlighted = false, bool $listAlphabeticalOrder = false): FormValidator
3632
    {
3633
        $frmStudentList = new FormValidator(
3634
            'frm_student_list',
3635
            'get',
3636
            $this->baseUrl,
3637
            '',
3638
            [],
3639
            FormValidator::LAYOUT_BOX
3640
        );
3641
3642
        $urlParams = http_build_query(
3643
            [
3644
                'a' => 'search_user_by_course',
3645
                'course_id' => $this->course->getId(),
3646
                'session_id' => $this->session ? $this->session->getId() : 0,
3647
            ]
3648
        );
3649
3650
        /** @var SelectAjax $slctUser */
3651
        $slctUser = $frmStudentList->addSelectAjax(
3652
            'user',
3653
            get_lang('SelectLearnerPortfolio'),
3654
            [],
3655
            [
3656
                'url' => api_get_path(WEB_AJAX_PATH)."course.ajax.php?$urlParams",
3657
                'placeholder' => get_lang('SearchStudent'),
3658
                'formatResult' => SelectAjax::templateResultForUsersInCourse(),
3659
                'formatSelection' => SelectAjax::templateSelectionForUsersInCourse(),
3660
            ]
3661
        );
3662
3663
        if ($listByUser) {
3664
            $slctUser->addOption(
3665
                $this->owner->getCompleteName(),
3666
                $this->owner->getId(),
3667
                [
3668
                    'data-avatarurl' => UserManager::getUserPicture($this->owner->getId()),
3669
                    'data-username' => $this->owner->getUsername(),
3670
                ]
3671
            );
3672
3673
            $link = Display::url(
3674
                get_lang('BackToMainPortfolio'),
3675
                $this->baseUrl
3676
            );
3677
        } else {
3678
            $link = Display::url(
3679
                get_lang('SeeMyPortfolio'),
3680
                $this->baseUrl.http_build_query(['user' => api_get_user_id()])
3681
            );
3682
        }
3683
3684
        $frmStudentList->addHtml("<p>$link</p>");
3685
3686
        if ($listHighlighted) {
3687
            $link = Display::url(
3688
                get_lang('BackToMainPortfolio'),
3689
                $this->baseUrl
3690
            );
3691
        } else {
3692
            $link = Display::url(
3693
                get_lang('SeeHighlights'),
3694
                $this->baseUrl.http_build_query(['list_highlighted' => true])
3695
            );
3696
        }
3697
3698
        $frmStudentList->addHtml("<p>$link</p>");
3699
3700
        if (true !== api_get_configuration_value('portfolio_order_post_by_alphabetical_order')) {
3701
            if ($listAlphabeticalOrder) {
3702
                $link = Display::url(
3703
                    get_lang('BackToDateOrder'),
3704
                    $this->baseUrl
3705
                );
3706
            } else {
3707
                $link = Display::url(
3708
                    get_lang('SeeAlphabeticalOrder'),
3709
                    $this->baseUrl.http_build_query(['list_alphabetical' => true])
3710
                );
3711
            }
3712
3713
            $frmStudentList->addHtml("<p>$link</p>");
3714
        }
3715
3716
        return $frmStudentList;
3717
    }
3718
3719
    private function getCategoriesForIndex(?int $currentUserId = null, ?int $parentId = null): array
3720
    {
3721
        $categoriesCriteria = [];
3722
        if (isset($currentUserId)) {
3723
            $categoriesCriteria['user'] = $this->owner;
3724
        }
3725
        if (!api_is_platform_admin() && $currentUserId !== $this->owner->getId()) {
3726
            $categoriesCriteria['isVisible'] = true;
3727
        }
3728
        if (isset($parentId)) {
3729
            $categoriesCriteria['parentId'] = $parentId;
3730
        }
3731
3732
        return $this->em
3733
            ->getRepository(PortfolioCategory::class)
3734
            ->findBy($categoriesCriteria);
3735
    }
3736
3737
    private function getHighlightedItems()
3738
    {
3739
        $queryBuilder = $this->em->createQueryBuilder();
3740
        $queryBuilder
3741
            ->select('pi')
3742
            ->from(Portfolio::class, 'pi')
3743
            ->where('pi.course = :course')
3744
            ->andWhere('pi.isHighlighted = TRUE')
3745
            ->setParameter('course', $this->course);
3746
3747
        if ($this->session) {
3748
            $queryBuilder->andWhere('pi.session = :session');
3749
            $queryBuilder->setParameter('session', $this->session);
3750
        } else {
3751
            $queryBuilder->andWhere('pi.session IS NULL');
3752
        }
3753
3754
        if ($this->advancedSharingEnabled) {
3755
            $queryBuilder
3756
                ->leftJoin(
3757
                    CItemProperty::class,
3758
                    'cip',
3759
                    Join::WITH,
3760
                    "cip.ref = pi.id
3761
                        AND cip.tool = :cip_tool
3762
                        AND cip.course = pi.course
3763
                        AND cip.lasteditType = 'visible'
3764
                        AND cip.toUser = :current_user"
3765
                )
3766
                ->andWhere(
3767
                    sprintf(
3768
                        'pi.visibility = %d
3769
                            OR (
3770
                                pi.visibility = %d AND cip IS NOT NULL OR pi.user = :current_user
3771
                            )',
3772
                        Portfolio::VISIBILITY_VISIBLE,
3773
                        Portfolio::VISIBILITY_PER_USER
3774
                    )
3775
                )
3776
                ->setParameter('cip_tool', TOOL_PORTFOLIO)
3777
            ;
3778
        } else {
3779
            $visibilityCriteria = [Portfolio::VISIBILITY_VISIBLE];
3780
3781
            if (api_is_allowed_to_edit()) {
3782
                $visibilityCriteria[] = Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER;
3783
            }
3784
3785
            $queryBuilder->andWhere(
3786
                $queryBuilder->expr()->orX(
3787
                    'pi.user = :current_user',
3788
                    $queryBuilder->expr()->andX(
3789
                        'pi.user != :current_user',
3790
                        $queryBuilder->expr()->in('pi.visibility', $visibilityCriteria)
3791
                    )
3792
                )
3793
            );
3794
        }
3795
3796
        $queryBuilder->setParameter('current_user', api_get_user_id());
3797
        $queryBuilder->orderBy('pi.creationDate', 'DESC');
3798
3799
        return $queryBuilder->getQuery()->getResult();
3800
    }
3801
3802
    private function getItemsForIndex(
3803
        bool $listByUser = false,
3804
        FormValidator $frmFilterList = null,
3805
        bool $alphabeticalOrder = false
3806
    ) {
3807
        $currentUserId = api_get_user_id();
3808
3809
        if ($this->course) {
3810
            $showBaseContentInSession = $this->session
3811
                && true === api_get_configuration_value('portfolio_show_base_course_post_in_sessions');
3812
3813
            $queryBuilder = $this->em->createQueryBuilder();
3814
            $queryBuilder
3815
                ->select('pi')
3816
                ->from(Portfolio::class, 'pi')
3817
                ->where('pi.course = :course');
3818
3819
            $queryBuilder->setParameter('course', $this->course);
3820
3821
            if ($this->session) {
3822
                $queryBuilder->andWhere(
3823
                    $showBaseContentInSession ? 'pi.session = :session OR pi.session IS NULL' : 'pi.session = :session'
3824
                );
3825
                $queryBuilder->setParameter('session', $this->session);
3826
            } else {
3827
                $queryBuilder->andWhere('pi.session IS NULL');
3828
            }
3829
3830
            if ($frmFilterList && $frmFilterList->validate()) {
3831
                $values = $frmFilterList->exportValues();
3832
3833
                if (!empty($values['date'])) {
3834
                    $queryBuilder
3835
                        ->andWhere('pi.creationDate >= :date')
3836
                        ->setParameter(':date', api_get_utc_datetime($values['date'], false, true))
3837
                    ;
3838
                }
3839
3840
                if (!empty($values['tags'])) {
3841
                    $queryBuilder
3842
                        ->innerJoin(ExtraFieldRelTag::class, 'efrt', Join::WITH, 'efrt.itemId = pi.id')
3843
                        ->innerJoin(ExtraFieldEntity::class, 'ef', Join::WITH, 'ef.id = efrt.fieldId')
3844
                        ->andWhere('ef.extraFieldType = :efType')
3845
                        ->andWhere('ef.variable = :variable')
3846
                        ->andWhere('efrt.tagId IN (:tags)');
3847
3848
                    $queryBuilder->setParameter('efType', ExtraFieldEntity::PORTFOLIO_TYPE);
3849
                    $queryBuilder->setParameter('variable', 'tags');
3850
                    $queryBuilder->setParameter('tags', $values['tags']);
3851
                }
3852
3853
                if (!empty($values['text'])) {
3854
                    $queryBuilder->andWhere(
3855
                        $queryBuilder->expr()->orX(
3856
                            $queryBuilder->expr()->like('pi.title', ':text'),
3857
                            $queryBuilder->expr()->like('pi.content', ':text')
3858
                        )
3859
                    );
3860
3861
                    $queryBuilder->setParameter('text', '%'.$values['text'].'%');
3862
                }
3863
3864
                // Filters by category level 0
3865
                $searchCategories = [];
3866
                if (!empty($values['categoryId'])) {
3867
                    $searchCategories[] = $values['categoryId'];
3868
                    $subCategories = $this->getCategoriesForIndex(null, $values['categoryId']);
3869
                    if (count($subCategories) > 0) {
3870
                        foreach ($subCategories as $subCategory) {
3871
                            $searchCategories[] = $subCategory->getId();
3872
                        }
3873
                    }
3874
                    $queryBuilder->andWhere('pi.category IN('.implode(',', $searchCategories).')');
3875
                }
3876
3877
                // Filters by sub-category, don't show the selected values
3878
                $diff = [];
3879
                if (!empty($values['subCategoryIds']) && !('all' === $values['subCategoryIds'])) {
3880
                    $subCategoryIds = explode(',', $values['subCategoryIds']);
3881
                    $diff = array_diff($searchCategories, $subCategoryIds);
3882
                } else {
3883
                    if (trim($values['subCategoryIds']) === '') {
3884
                        $diff = $searchCategories;
3885
                    }
3886
                }
3887
                if (!empty($diff)) {
3888
                    unset($diff[0]);
3889
                    if (!empty($diff)) {
3890
                        $queryBuilder->andWhere('pi.category NOT IN('.implode(',', $diff).')');
3891
                    }
3892
                }
3893
            }
3894
3895
            if ($listByUser) {
3896
                $queryBuilder
3897
                    ->andWhere('pi.user = :user')
3898
                    ->setParameter('user', $this->owner);
3899
            }
3900
3901
            if ($this->advancedSharingEnabled) {
3902
                $queryBuilder
3903
                    ->leftJoin(
3904
                        CItemProperty::class,
3905
                        'cip',
3906
                        Join::WITH,
3907
                        "cip.ref = pi.id
3908
                            AND cip.tool = :cip_tool
3909
                            AND cip.course = pi.course
3910
                            AND cip.lasteditType = 'visible'
3911
                            AND cip.toUser = :current_user"
3912
                    )
3913
                    ->andWhere(
3914
                        sprintf(
3915
                            'pi.visibility = %d
3916
                            OR (
3917
                                pi.visibility = %d AND cip IS NOT NULL OR pi.user = :current_user
3918
                            )',
3919
                            Portfolio::VISIBILITY_VISIBLE,
3920
                            Portfolio::VISIBILITY_PER_USER
3921
                        )
3922
                    )
3923
                    ->setParameter('cip_tool', TOOL_PORTFOLIO)
3924
                ;
3925
            } else {
3926
                $visibilityCriteria = [Portfolio::VISIBILITY_VISIBLE];
3927
3928
                if (api_is_allowed_to_edit()) {
3929
                    $visibilityCriteria[] = Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER;
3930
                }
3931
3932
                $queryBuilder->andWhere(
3933
                    $queryBuilder->expr()->orX(
3934
                        'pi.user = :current_user',
3935
                        $queryBuilder->expr()->andX(
3936
                            'pi.user != :current_user',
3937
                            $queryBuilder->expr()->in('pi.visibility', $visibilityCriteria)
3938
                        )
3939
                    )
3940
                );
3941
            }
3942
3943
            $queryBuilder->setParameter('current_user', $currentUserId);
3944
            if ($alphabeticalOrder || true === api_get_configuration_value('portfolio_order_post_by_alphabetical_order')) {
3945
                $queryBuilder->orderBy('pi.title', 'ASC');
3946
            } else {
3947
                $queryBuilder->orderBy('pi.creationDate', 'DESC');
3948
            }
3949
3950
            $items = $queryBuilder->getQuery()->getResult();
3951
3952
            if ($showBaseContentInSession) {
3953
                $items = array_filter(
3954
                    $items,
3955
                    fn (Portfolio $item) => !($this->session && !$item->getSession() && $item->isDuplicatedInSession($this->session))
3956
                );
3957
            }
3958
3959
            return $items;
3960
        } else {
3961
            $itemsCriteria = [];
3962
            $itemsCriteria['category'] = null;
3963
            $itemsCriteria['user'] = $this->owner;
3964
3965
            if ($currentUserId !== $this->owner->getId()) {
3966
                $itemsCriteria['visibility'] = Portfolio::VISIBILITY_VISIBLE;
3967
            }
3968
3969
            $items = $this->em
3970
                ->getRepository(Portfolio::class)
3971
                ->findBy($itemsCriteria, ['creationDate' => 'DESC']);
3972
        }
3973
3974
        return $items;
3975
    }
3976
3977
    /**
3978
     * @throws \Doctrine\ORM\ORMException
3979
     * @throws \Doctrine\ORM\OptimisticLockException
3980
     * @throws \Doctrine\ORM\TransactionRequiredException
3981
     */
3982
    private function createCommentForm(Portfolio $item): string
3983
    {
3984
        $formAction = $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()]);
3985
3986
        $templates = $this->em
3987
            ->getRepository(PortfolioComment::class)
3988
            ->findBy(
3989
                [
3990
                    'isTemplate' => true,
3991
                    'author' => $this->owner,
3992
                ]
3993
            );
3994
3995
        $form = new FormValidator('frm_comment', 'post', $formAction);
3996
        $form->addHeader(get_lang('AddNewComment'));
3997
        $form->addSelectFromCollection(
3998
            'template',
3999
            [
4000
                get_lang('Template'),
4001
                null,
4002
                '<span id="portfolio-spinner" class="fa fa-fw fa-spinner fa-spin" style="display: none;"
4003
                    aria-hidden="true" aria-label="'.get_lang('Loading').'"></span>',
4004
            ],
4005
            $templates,
4006
            [],
4007
            true,
4008
            'getExcerpt'
4009
        );
4010
        $form->addHtmlEditor('content', get_lang('Comments'), true, false, ['ToolbarSet' => 'Minimal']);
4011
        $form->addHidden('item', $item->getId());
4012
        $form->addHidden('parent', 0);
4013
        $form->applyFilter('content', 'trim');
4014
4015
        $this->addAttachmentsFieldToForm($form);
4016
4017
        $form->addButtonSave(get_lang('Save'));
4018
4019
        if ($form->validate()) {
4020
            if ($this->session
4021
                && true === api_get_configuration_value('portfolio_show_base_course_post_in_sessions')
4022
                && !$item->getSession()
4023
            ) {
4024
                $duplicate = $item->duplicateInSession($this->session);
4025
4026
                $this->em->persist($duplicate);
4027
                $this->em->flush();
4028
4029
                $item = $duplicate;
4030
4031
                $formAction = $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()]);
4032
            }
4033
4034
            $values = $form->exportValues();
4035
4036
            $parentComment = $this->em->find(PortfolioComment::class, $values['parent']);
4037
4038
            $comment = new PortfolioComment();
4039
            $comment
4040
                ->setAuthor($this->owner)
4041
                ->setParent($parentComment)
4042
                ->setContent($values['content'])
4043
                ->setDate(api_get_utc_datetime(null, false, true))
4044
                ->setItem($item);
4045
4046
            $this->em->persist($comment);
4047
            $this->em->flush();
4048
4049
            $this->processAttachments(
4050
                $form,
4051
                $comment->getAuthor(),
4052
                $comment->getId(),
4053
                PortfolioAttachment::TYPE_COMMENT
4054
            );
4055
4056
            $hook = HookPortfolioItemCommented::create();
4057
            $hook->setEventData(['comment' => $comment]);
4058
            $hook->notifyItemCommented();
4059
4060
            PortfolioNotifier::notifyTeachersAndAuthor($comment);
4061
4062
            Display::addFlash(
4063
                Display::return_message(get_lang('CommentAdded'), 'success')
4064
            );
4065
4066
            header("Location: $formAction");
4067
            exit;
4068
        }
4069
4070
        $js = '<script>
4071
            $(function() {
4072
                $(\'#frm_comment_template\').on(\'change\', function () {
4073
                    $(\'#portfolio-spinner\').show();
4074
4075
                    $.getJSON(_p.web_ajax + \'portfolio.ajax.php?a=find_template_comment&comment=\' + this.value)
4076
                        .done(function(response) {
4077
                            CKEDITOR.instances.content.setData(response.content);
4078
                        })
4079
                        .fail(function () {
4080
                            CKEDITOR.instances.content.setData(\'\');
4081
                        })
4082
                        .always(function() {
4083
                          $(\'#portfolio-spinner\').hide();
4084
                        });
4085
                });
4086
            });
4087
        </script>';
4088
4089
        return $form->returnForm().$js;
4090
    }
4091
4092
    private function generateAttachmentList($post, bool $includeHeader = true): string
4093
    {
4094
        $attachmentsRepo = $this->em->getRepository(PortfolioAttachment::class);
4095
4096
        $postOwnerId = 0;
4097
4098
        if ($post instanceof Portfolio) {
4099
            $attachments = $attachmentsRepo->findFromItem($post);
4100
4101
            $postOwnerId = $post->getUser()->getId();
4102
        } elseif ($post instanceof PortfolioComment) {
4103
            $attachments = $attachmentsRepo->findFromComment($post);
4104
4105
            $postOwnerId = $post->getAuthor()->getId();
4106
        }
4107
4108
        if (empty($attachments)) {
4109
            return '';
4110
        }
4111
4112
        $currentUserId = api_get_user_id();
4113
4114
        $listItems = '<ul class="fa-ul">';
4115
4116
        $deleteIcon = Display::return_icon(
4117
            'delete.png',
4118
            get_lang('DeleteAttachment'),
4119
            ['style' => 'display: inline-block'],
4120
            ICON_SIZE_TINY
4121
        );
4122
        $deleteAttrs = ['class' => 'btn-portfolio-delete'];
4123
4124
        /** @var PortfolioAttachment $attachment */
4125
        foreach ($attachments as $attachment) {
4126
            $downloadParams = http_build_query(['action' => 'download_attachment', 'file' => $attachment->getPath()]);
4127
            $deleteParams = http_build_query(['action' => 'delete_attachment', 'file' => $attachment->getPath()]);
4128
4129
            $listItems .= '<li>'
4130
                .'<span class="fa-li fa fa-paperclip" aria-hidden="true"></span>'
4131
                .Display::url(
4132
                    Security::remove_XSS($attachment->getFilename()),
4133
                    $this->baseUrl.$downloadParams
4134
                );
4135
4136
            if ($currentUserId === $postOwnerId) {
4137
                $listItems .= PHP_EOL.Display::url($deleteIcon, $this->baseUrl.$deleteParams, $deleteAttrs);
4138
            }
4139
4140
            if ($attachment->getComment()) {
4141
                $listItems .= '<p class="text-muted">'.Security::remove_XSS($attachment->getComment()).'</p>';
4142
            }
4143
4144
            $listItems .= '</li>';
4145
        }
4146
4147
        $listItems .= '</ul>';
4148
4149
        if ($includeHeader) {
4150
            $listItems = '<h1 class="h4">'.get_lang('FilesAttachment').'</h1>'
4151
                .$listItems;
4152
        }
4153
4154
        return $listItems;
4155
    }
4156
4157
    private function generateItemContent(Portfolio $item): string
4158
    {
4159
        $originId = $item->getOrigin();
4160
4161
        if (empty($originId)) {
4162
            return $item->getContent();
4163
        }
4164
4165
        $em = Database::getManager();
4166
4167
        $originContent = '';
4168
        $originContentFooter = '';
4169
4170
        if (Portfolio::TYPE_ITEM === $item->getOriginType()) {
4171
            $origin = $em->find(Portfolio::class, $item->getOrigin());
4172
4173
            if ($origin) {
4174
                $originContent = Security::remove_XSS($origin->getContent());
4175
                $originContentFooter = vsprintf(
4176
                    get_lang('OriginallyPublishedAsXTitleByYUser'),
4177
                    [
4178
                        "<cite>{$origin->getTitle(true)}</cite>",
4179
                        $origin->getUser()->getCompleteName(),
4180
                    ]
4181
                );
4182
            }
4183
        } elseif (Portfolio::TYPE_COMMENT === $item->getOriginType()) {
4184
            $origin = $em->find(PortfolioComment::class, $item->getOrigin());
4185
4186
            if ($origin) {
4187
                $originContent = Security::remove_XSS($origin->getContent());
4188
                $originContentFooter = vsprintf(
4189
                    get_lang('OriginallyCommentedByXUserInYItem'),
4190
                    [
4191
                        $origin->getAuthor()->getCompleteName(),
4192
                        "<cite>{$origin->getItem()->getTitle(true)}</cite>",
4193
                    ]
4194
                );
4195
            }
4196
        }
4197
4198
        if ($originContent) {
4199
            return "<figure>
4200
                    <blockquote>$originContent</blockquote>
4201
                    <figcaption style=\"margin-bottom: 10px;\">$originContentFooter</figcaption>
4202
                </figure>
4203
                <div class=\"clearfix\">".Security::remove_XSS($item->getContent()).'</div>'
4204
            ;
4205
        }
4206
4207
        return Security::remove_XSS($item->getContent());
4208
    }
4209
4210
    private function getItemsInHtmlFormatted(array $items): array
4211
    {
4212
        $itemsHtml = [];
4213
4214
        /** @var Portfolio $item */
4215
        foreach ($items as $item) {
4216
            $itemCourse = $item->getCourse();
4217
            $itemSession = $item->getSession();
4218
4219
            $creationDate = api_convert_and_format_date($item->getCreationDate());
4220
            $updateDate = api_convert_and_format_date($item->getUpdateDate());
4221
4222
            $metadata = '<ul class="list-unstyled text-muted">';
4223
4224
            if ($itemSession) {
4225
                $metadata .= '<li>'.get_lang('Course').': '.$itemSession->getName().' ('
4226
                    .$itemCourse->getTitle().') </li>';
4227
            } elseif ($itemCourse) {
4228
                $metadata .= '<li>'.get_lang('Course').': '.$itemCourse->getTitle().'</li>';
4229
            }
4230
4231
            $metadata .= '<li>'.sprintf(get_lang('CreationDateXDate'), $creationDate).'</li>';
4232
4233
            if ($itemCourse) {
4234
                $propertyInfo = api_get_item_property_info(
4235
                    $itemCourse->getId(),
4236
                    TOOL_PORTFOLIO,
4237
                    $item->getId(),
4238
                    $itemSession ? $itemSession->getId() : 0
4239
                );
4240
4241
                if ($propertyInfo) {
4242
                    $metadata .= '<li>'
4243
                        .sprintf(
4244
                            get_lang('UpdatedOnDateXByUserY'),
4245
                            api_convert_and_format_date($propertyInfo['lastedit_date'], DATE_TIME_FORMAT_LONG),
4246
                            api_get_user_entity($propertyInfo['lastedit_user_id'])->getCompleteName()
4247
                        )
4248
                        .'</li>';
4249
                }
4250
            } else {
4251
                $metadata .= '<li>'.sprintf(get_lang('UpdateDateXDate'), $updateDate).'</li>';
4252
            }
4253
4254
            if ($item->getCategory()) {
4255
                $metadata .= '<li>'.sprintf(get_lang('CategoryXName'), $item->getCategory()->getTitle()).'</li>';
4256
            }
4257
4258
            $metadata .= '</ul>';
4259
4260
            $itemContent = $this->generateItemContent($item);
4261
4262
            $itemsHtml[] = Display::panel($itemContent, Security::remove_XSS($item->getTitle()), '', 'info', $metadata);
4263
        }
4264
4265
        return $itemsHtml;
4266
    }
4267
4268
    private function getCommentsInHtmlFormatted(array $comments): array
4269
    {
4270
        $commentsHtml = [];
4271
4272
        /** @var PortfolioComment $comment */
4273
        foreach ($comments as $comment) {
4274
            $item = $comment->getItem();
4275
            $date = api_convert_and_format_date($comment->getDate());
4276
4277
            $metadata = '<ul class="list-unstyled text-muted">';
4278
            $metadata .= '<li>'.sprintf(get_lang('DateXDate'), $date).'</li>';
4279
            $metadata .= '<li>'.sprintf(get_lang('PortfolioItemTitleXName'), Security::remove_XSS($item->getTitle()))
4280
                .'</li>';
4281
            $metadata .= '</ul>';
4282
4283
            $commentsHtml[] = Display::panel(
4284
                Security::remove_XSS($comment->getContent()),
4285
                '',
4286
                '',
4287
                'default',
4288
                $metadata
4289
            );
4290
        }
4291
4292
        return $commentsHtml;
4293
    }
4294
4295
    /**
4296
     * @param string $htmlContent
4297
     * @param array $imagePaths Relative paths found in $htmlContent
4298
     *
4299
     * @return string
4300
     */
4301
    private function fixMediaSourcesToHtml(string $htmlContent, array &$imagePaths): string
4302
    {
4303
        $doc = new DOMDocument();
4304
        @$doc->loadHTML($htmlContent);
4305
4306
        $tagsWithSrc = ['img', 'video', 'audio', 'source'];
4307
        /** @var array<int, \DOMElement> $elements */
4308
        $elements = [];
4309
4310
        foreach ($tagsWithSrc as $tag) {
4311
            foreach ($doc->getElementsByTagName($tag) as $element) {
4312
                if ($element->hasAttribute('src')) {
4313
                    $elements[] = $element;
4314
                }
4315
            }
4316
        }
4317
4318
        if (empty($elements)) {
4319
            return $htmlContent;
4320
        }
4321
4322
        /** @var array<int, \DOMElement> $anchorElements */
4323
        $anchorElements = $doc->getElementsByTagName('a');
4324
4325
        $webPath = api_get_path(WEB_PATH);
4326
        $sysPath = rtrim(api_get_path(SYS_PATH), '/');
4327
4328
        $paths = [
4329
            '/app/upload/' => $sysPath,
4330
            '/courses/' => $sysPath.'/app'
4331
        ];
4332
4333
        foreach ($elements as $element) {
4334
            $src = trim($element->getAttribute('src'));
4335
4336
            if (!str_starts_with($src, '/')
4337
                && !str_starts_with($src, $webPath)
4338
            ) {
4339
                continue;
4340
            }
4341
4342
            if ($anchorElements->length > 0) {
4343
                foreach ($anchorElements as $anchorElement) {
4344
                    if (!$anchorElement->hasAttribute('href')) {
4345
                        continue;
4346
                    }
4347
4348
                    if ($src === $anchorElement->getAttribute('href')) {
4349
                        $anchorElement->setAttribute('href', basename($src));
4350
                    }
4351
                }
4352
            }
4353
4354
            $src = str_replace($webPath, '/', $src);
4355
4356
            foreach ($paths as $prefix => $basePath) {
4357
                if (str_starts_with($src, $prefix)) {
4358
                    $imagePaths[] = $basePath.urldecode($src);
4359
                    $element->setAttribute('src', basename($src));
4360
                }
4361
            }
4362
        }
4363
4364
        return $doc->saveHTML();
4365
    }
4366
4367
    private function formatZipIndexFile(HTML_Table $tblItems, HTML_Table $tblComments): string
4368
    {
4369
        $htmlContent = Display::page_header($this->owner->getCompleteNameWithUsername());
4370
        $htmlContent .= Display::page_subheader2(get_lang('PortfolioItems'));
4371
4372
        $htmlContent .= $tblItems->getRowCount() > 0
4373
            ? $tblItems->toHtml()
4374
            : Display::return_message(get_lang('NoItemsInYourPortfolio'), 'warning');
4375
4376
        $htmlContent .= Display::page_subheader2(get_lang('PortfolioCommentsMade'));
4377
4378
        $htmlContent .= $tblComments->getRowCount() > 0
4379
            ? $tblComments->toHtml()
4380
            : Display::return_message(get_lang('YouHaveNotCommented'), 'warning');
4381
4382
        $webAssetsPath = api_get_path(WEB_PUBLIC_PATH).'assets/';
4383
4384
        $doc = new DOMDocument();
4385
        @$doc->loadHTML($htmlContent);
4386
4387
        $stylesheet1 = $doc->createElement('link');
4388
        $stylesheet1->setAttribute('rel', 'stylesheet');
4389
        $stylesheet1->setAttribute('href', $webAssetsPath.'bootstrap/dist/css/bootstrap.min.css');
4390
        $stylesheet2 = $doc->createElement('link');
4391
        $stylesheet2->setAttribute('rel', 'stylesheet');
4392
        $stylesheet2->setAttribute('href', $webAssetsPath.'fontawesome/css/font-awesome.min.css');
4393
        $stylesheet3 = $doc->createElement('link');
4394
        $stylesheet3->setAttribute('rel', 'stylesheet');
4395
        $stylesheet3->setAttribute('href', ChamiloApi::getEditorDocStylePath());
4396
4397
        $head = $doc->createElement('head');
4398
        $head->appendChild($stylesheet1);
4399
        $head->appendChild($stylesheet2);
4400
        $head->appendChild($stylesheet3);
4401
4402
        $doc->documentElement->insertBefore(
4403
            $head,
4404
            $doc->getElementsByTagName('body')->item(0)
4405
        );
4406
4407
        return $doc->saveHTML();
4408
    }
4409
4410
    /**
4411
     * It parsers a title for a variable in lang.
4412
     *
4413
     * @param $defaultDisplayText
4414
     *
4415
     * @return string
4416
     */
4417
    private function getLanguageVariable($defaultDisplayText)
4418
    {
4419
        $variableLanguage = api_replace_dangerous_char(strtolower($defaultDisplayText));
4420
        $variableLanguage = preg_replace('/[^A-Za-z0-9\_]/', '', $variableLanguage); // Removes special chars except underscore.
4421
        if (is_numeric($variableLanguage[0])) {
4422
            $variableLanguage = '_'.$variableLanguage;
4423
        }
4424
        $variableLanguage = api_underscore_to_camel_case($variableLanguage);
4425
4426
        return $variableLanguage;
4427
    }
4428
4429
    /**
4430
     * It translates the text as parameter.
4431
     *
4432
     * @param $defaultDisplayText
4433
     *
4434
     * @return mixed
4435
     */
4436
    private function translateDisplayName($defaultDisplayText)
4437
    {
4438
        $variableLanguage = $this->getLanguageVariable($defaultDisplayText);
4439
4440
        return isset($GLOBALS[$variableLanguage]) ? $GLOBALS[$variableLanguage] : $defaultDisplayText;
4441
    }
4442
4443
    private function getCommentsForIndex(FormValidator $frmFilterList = null): array
4444
    {
4445
        if (null === $frmFilterList) {
4446
            return [];
4447
        }
4448
4449
        if (!$frmFilterList->validate()) {
4450
            return [];
4451
        }
4452
4453
        $values = $frmFilterList->exportValues();
4454
4455
        if (empty($values['date']) && empty($values['text'])) {
4456
            return [];
4457
        }
4458
4459
        $queryBuilder = $this->em->createQueryBuilder()
4460
            ->select('c')
4461
            ->from(PortfolioComment::class, 'c')
4462
        ;
4463
4464
        if (!empty($values['date'])) {
4465
            $queryBuilder
4466
                ->andWhere('c.date >= :date')
4467
                ->setParameter(':date', api_get_utc_datetime($values['date'], false, true))
4468
            ;
4469
        }
4470
4471
        if (!empty($values['text'])) {
4472
            $queryBuilder
4473
                ->andWhere('c.content LIKE :text')
4474
                ->setParameter('text', '%'.$values['text'].'%')
4475
            ;
4476
        }
4477
4478
        if ($this->advancedSharingEnabled) {
4479
            $queryBuilder
4480
                ->leftJoin(
4481
                    CItemProperty::class,
4482
                    'cip',
4483
                    Join::WITH,
4484
                    "cip.ref = c.id
4485
                        AND cip.tool = :cip_tool
4486
                        AND cip.course = :course
4487
                        AND cip.lasteditType = 'visible'
4488
                        AND cip.toUser = :current_user"
4489
                )
4490
                ->andWhere(
4491
                    sprintf(
4492
                        'c.visibility = %d
4493
                            OR (
4494
                                c.visibility = %d AND cip IS NOT NULL OR c.author = :current_user
4495
                            )',
4496
                        PortfolioComment::VISIBILITY_VISIBLE,
4497
                        PortfolioComment::VISIBILITY_PER_USER
4498
                    )
4499
                )
4500
                ->setParameter('cip_tool', TOOL_PORTFOLIO_COMMENT)
4501
                ->setParameter('current_user', $this->owner->getId())
4502
                ->setParameter('course', $this->course)
4503
            ;
4504
        }
4505
4506
        $queryBuilder->orderBy('c.date', 'DESC');
4507
4508
        return $queryBuilder->getQuery()->getResult();
4509
    }
4510
4511
    private function getLabelForCommentDate(PortfolioComment $comment): string
4512
    {
4513
        $item = $comment->getItem();
4514
        $commmentCourse = $item->getCourse();
4515
        $commmentSession = $item->getSession();
4516
4517
        $dateLabel = Display::dateToStringAgoAndLongDate($comment->getDate()).PHP_EOL;
4518
4519
        if ($commmentCourse) {
4520
            $propertyInfo = api_get_item_property_info(
4521
                $commmentCourse->getId(),
4522
                TOOL_PORTFOLIO_COMMENT,
4523
                $comment->getId(),
4524
                $commmentSession ? $commmentSession->getId() : 0
4525
            );
4526
4527
            if ($propertyInfo) {
4528
                $dateLabel .= '|'.PHP_EOL
4529
                    .sprintf(
4530
                        get_lang('UpdatedDateX'),
4531
                        Display::dateToStringAgoAndLongDate($propertyInfo['lastedit_date'])
4532
                    );
4533
            }
4534
        }
4535
4536
        return $dateLabel;
4537
    }
4538
}
4539