Passed
Pull Request — 1.11.x (#5989)
by Angel Fernando Quiroz
30:27 queued 13:31
created

PortfolioController::applySessionCondition()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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