PortfolioController::listCategories()   F
last analyzed

Complexity

Conditions 13
Paths 576

Size

Total Lines 93
Code Lines 63

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 13
eloc 63
c 0
b 0
f 0
nc 576
nop 0
dl 0
loc 93
rs 3.2383

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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