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

PortfolioController::itemBelongToOwner()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 1
dl 0
loc 7
rs 10
c 0
b 0
f 0
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\Tag;
14
use Chamilo\UserBundle\Entity\User;
15
use Doctrine\ORM\Query\Expr\Join;
16
use Symfony\Component\Filesystem\Filesystem;
17
use Symfony\Component\HttpFoundation\Request as HttpRequest;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, HttpRequest. Consider defining an alias.

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

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

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

// Bar.php
namespace OtherDir;

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

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

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

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

// Bar.php
namespace OtherDir;

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

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

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

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

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

Loading history...
568
569
                    $teachers = SessionManager::getCoachesByCourseSession(
570
                        $this->session->getId(),
571
                        $this->course->getId()
572
                    );
573
                    $userIdListToSend = array_values($teachers);
574
                } else {
575
                    $messageCourseTitle = $this->course->getTitle();
576
577
                    $teachers = CourseManager::get_teacher_list_from_course_code($this->course->getCode());
578
579
                    $userIdListToSend = array_keys($teachers);
580
                }
581
582
                $messageSubject = sprintf(get_lang('PortfolioAlertNewPostSubject'), $messageCourseTitle);
583
                $messageContent = sprintf(
584
                    get_lang('PortfolioAlertNewPostContent'),
585
                    $this->owner->getCompleteName(),
586
                    $messageCourseTitle,
587
                    $this->baseUrl.http_build_query(['action' => 'view', 'id' => $portfolio->getId()])
588
                );
589
                $messageContent .= '<br><br><dl>'
590
                    .'<dt>'.Security::remove_XSS($portfolio->getTitle()).'</dt>'
591
                    .'<dd>'.$portfolio->getExcerpt().'</dd>'.'</dl>';
592
593
                foreach ($userIdListToSend as $userIdToSend) {
594
                    MessageManager::send_message_simple(
595
                        $userIdToSend,
596
                        $messageSubject,
597
                        $messageContent,
598
                        0,
599
                        false,
600
                        false,
601
                        [],
602
                        false
603
                    );
604
                }
605
            }
606
607
            Display::addFlash(
608
                Display::return_message(get_lang('PortfolioItemAdded'), 'success')
609
            );
610
611
            header("Location: $this->baseUrl");
612
            exit;
613
        }
614
615
        $interbreadcrumb[] = [
616
            'name' => get_lang('Portfolio'),
617
            'url' => $this->baseUrl,
618
        ];
619
620
        $actions = [];
621
        $actions[] = Display::url(
622
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
623
            $this->baseUrl
624
        );
625
        $actions[] = '<a id="hide_bar_template" href="#" role="button">'.
626
            Display::return_icon('expand.png', get_lang('Expand'), ['id' => 'expand'], ICON_SIZE_MEDIUM).
627
            Display::return_icon('contract.png', get_lang('Collapse'), ['id' => 'contract', 'class' => 'hide'], ICON_SIZE_MEDIUM).'</a>';
628
629
        $js = '<script>
630
            $(function() {
631
                $(".scrollbar-light").scrollbar();
632
                $(".scroll-wrapper").css("height", "550px");
633
                expandColumnToogle("#hide_bar_template", {
634
                    selector: "#template_col",
635
                    width: 3
636
                }, {
637
                    selector: "#doc_form",
638
                    width: 9
639
                });
640
                CKEDITOR.on("instanceReady", function (e) {
641
                    showTemplates();
642
                });
643
                $(window).on("load", function () {
644
                    $("input[name=\'title\']").focus();
645
                });
646
                $(\'#add_portfolio_template\').on(\'change\', function () {
647
                    $(\'#portfolio-spinner\').show();
648
                
649
                    $.getJSON(_p.web_ajax + \'portfolio.ajax.php?a=find_template&item=\' + this.value).done(function(response) {
650
                        if (CKEDITOR.instances.title) {
651
                            CKEDITOR.instances.title.setData(response.title);
652
                        } else {
653
                            document.getElementById(\'add_portfolio_title\').value = response.title;
654
                        }
655
                    
656
                        CKEDITOR.instances.content.setData(response.content);
657
                        
658
                        $(\'#portfolio-spinner\').hide();
659
                    });
660
                });
661
                '.$extra['jquery_ready_content'].'
662
            });
663
        </script>';
664
        $content = '<div class="page-create">
665
            <div class="row" style="overflow:hidden">
666
            <div id="template_col" class="col-md-3">
667
                <div class="panel panel-default">
668
                <div class="panel-body">
669
                    <div id="frmModel" class="items-templates scrollbar-light"></div>
670
                </div>
671
                </div>
672
            </div>
673
            <div id="doc_form" class="col-md-9">
674
                '.$form->returnForm().'
675
            </div>
676
          </div></div>';
677
678
        $this->renderView(
679
            $content.$js,
680
            get_lang('AddPortfolioItem'),
681
            $actions
682
        );
683
    }
684
685
    /**
686
     * @throws \Doctrine\ORM\ORMException
687
     * @throws \Doctrine\ORM\OptimisticLockException
688
     * @throws \Doctrine\ORM\TransactionRequiredException
689
     * @throws \Exception
690
     */
691
    public function editItem(Portfolio $item)
692
    {
693
        global $interbreadcrumb;
694
695
        if (!api_is_allowed_to_edit() && !$this->itemBelongToOwner($item)) {
696
            api_not_allowed(true);
697
        }
698
699
        $itemCourse = $item->getCourse();
700
        $itemSession = $item->getSession();
701
702
        $form = new FormValidator('edit_portfolio', 'post', $this->baseUrl."action=edit_item&id={$item->getId()}");
703
704
        if (api_get_configuration_value('save_titles_as_html')) {
705
            $form->addHtmlEditor('title', get_lang('Title'), true, false, ['ToolbarSet' => 'TitleAsHtml']);
706
        } else {
707
            $form->addText('title', get_lang('Title'));
708
            $form->applyFilter('title', 'trim');
709
        }
710
711
        if ($item->getOrigin()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $item->getOrigin() of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

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

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

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

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

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

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

}

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

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

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

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

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

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

Loading history...
2618
            $actions = implode('', $actions);
2619
2620
            $actionsStr .= Display::toolbarAction('portfolio-toolbar', [$actions]);
2621
        }
2622
2623
        $view->assign('baseurl', $this->baseUrl);
2624
        $view->assign('actions', $actionsStr);
2625
2626
        $view->assign('content', $content);
2627
        $view->display_one_col_template();
2628
    }
2629
2630
    private function categoryBelongToOwner(PortfolioCategory $category): bool
2631
    {
2632
        if ($category->getUser()->getId() != $this->owner->getId()) {
2633
            return false;
2634
        }
2635
2636
        return true;
2637
    }
2638
2639
    private function addAttachmentsFieldToForm(FormValidator $form)
2640
    {
2641
        $form->addButton('add_attachment', get_lang('AddAttachment'), 'plus');
2642
        $form->addHtml('<div id="container-attachments" style="display: none;">');
2643
        $form->addFile('attachment_file[]', get_lang('FilesAttachment'));
2644
        $form->addText('attachment_comment[]', get_lang('Description'), false);
2645
        $form->addHtml('</div>');
2646
2647
        $script = "$(function () {
2648
            var attachmentsTemplate = $('#container-attachments').html();
2649
            var \$btnAdd = $('[name=\"add_attachment\"]');
2650
            var \$reference = \$btnAdd.parents('.form-group');
2651
2652
            \$btnAdd.on('click', function (e) {
2653
                e.preventDefault();
2654
2655
                $(attachmentsTemplate).insertBefore(\$reference);
2656
            });
2657
        })";
2658
2659
        $form->addHtml("<script>$script</script>");
2660
    }
2661
2662
    private function processAttachments(
2663
        FormValidator $form,
2664
        User $user,
2665
        int $originId,
2666
        int $originType
2667
    ) {
2668
        $em = Database::getManager();
2669
        $fs = new Filesystem();
2670
2671
        $comments = $form->getSubmitValue('attachment_comment');
2672
2673
        foreach ($_FILES['attachment_file']['error'] as $i => $attachmentFileError) {
2674
            if ($attachmentFileError != UPLOAD_ERR_OK) {
2675
                continue;
2676
            }
2677
2678
            $_file = [
2679
                'name' => $_FILES['attachment_file']['name'][$i],
2680
                'type' => $_FILES['attachment_file']['type'][$i],
2681
                'tmp_name' => $_FILES['attachment_file']['tmp_name'][$i],
2682
                'size' => $_FILES['attachment_file']['size'][$i],
2683
            ];
2684
2685
            if (empty($_file['type'])) {
2686
                $_file['type'] = DocumentManager::file_get_mime_type($_file['name']);
2687
            }
2688
2689
            $newFileName = add_ext_on_mime(stripslashes($_file['name']), $_file['type']);
2690
2691
            if (!filter_extension($newFileName)) {
2692
                Display::addFlash(Display::return_message(get_lang('UplUnableToSaveFileFilteredExtension'), 'error'));
2693
                continue;
2694
            }
2695
2696
            $newFileName = uniqid();
2697
            $attachmentsDirectory = UserManager::getUserPathById($user->getId(), 'system').'portfolio_attachments/';
2698
2699
            if (!$fs->exists($attachmentsDirectory)) {
2700
                $fs->mkdir($attachmentsDirectory, api_get_permissions_for_new_directories());
2701
            }
2702
2703
            $attachmentFilename = $attachmentsDirectory.$newFileName;
2704
2705
            if (is_uploaded_file($_file['tmp_name'])) {
2706
                $moved = move_uploaded_file($_file['tmp_name'], $attachmentFilename);
2707
2708
                if (!$moved) {
2709
                    Display::addFlash(Display::return_message(get_lang('UplUnableToSaveFile'), 'error'));
2710
                    continue;
2711
                }
2712
            }
2713
2714
            $attachment = new PortfolioAttachment();
2715
            $attachment
2716
                ->setFilename($_file['name'])
2717
                ->setComment($comments[$i])
2718
                ->setPath($newFileName)
2719
                ->setOrigin($originId)
2720
                ->setOriginType($originType)
2721
                ->setSize($_file['size']);
2722
2723
            $em->persist($attachment);
2724
            $em->flush();
2725
        }
2726
    }
2727
2728
    private function itemBelongToOwner(Portfolio $item): bool
2729
    {
2730
        if ($item->getUser()->getId() != $this->owner->getId()) {
2731
            return false;
2732
        }
2733
2734
        return true;
2735
    }
2736
2737
    private function createFormTagFilter(bool $listByUser = false): FormValidator
2738
    {
2739
        $em = Database::getManager();
2740
        $tagTepo = $em->getRepository(Tag::class);
2741
2742
        $extraField = new ExtraField('portfolio');
2743
        $tagFieldInfo = $extraField->get_handler_field_info_by_tags('tags');
2744
2745
        $selectTagOptions = array_map(
2746
            function (array $tagOption) {
2747
                return $tagOption['tag'];
2748
            },
2749
            $tagFieldInfo['options'] ?? []
2750
        );
2751
2752
        $frmTagList = new FormValidator(
2753
            'frm_tag_list',
2754
            'get',
2755
            $this->baseUrl.($listByUser ? 'user='.$this->owner->getId() : ''),
2756
            '',
2757
            [],
2758
            FormValidator::LAYOUT_BOX
2759
        );
2760
2761
        $frmTagList->addDatePicker('date', get_lang('CreationDate'));
2762
2763
        $frmTagList->addSelect(
2764
            'tags',
2765
            get_lang('Tags'),
2766
            $selectTagOptions,
2767
            ['multiple' => 'multiple']
2768
        );
2769
2770
        $frmTagList->addText('text', get_lang('Search'), false)->setIcon('search');
2771
        $frmTagList->applyFilter('text', 'trim');
2772
        $frmTagList->addHtml('<br>');
2773
        $frmTagList->addButtonFilter(get_lang('Filter'));
2774
2775
        if ($this->course) {
2776
            $frmTagList->addHidden('cidReq', $this->course->getCode());
2777
            $frmTagList->addHidden('id_session', $this->session ? $this->session->getId() : 0);
2778
            $frmTagList->addHidden('gidReq', 0);
2779
            $frmTagList->addHidden('gradebook', 0);
2780
            $frmTagList->addHidden('origin', '');
2781
            $frmTagList->addHidden('categoryId', 0);
2782
            $frmTagList->addHidden('subCategoryIds', '');
2783
2784
            if ($listByUser) {
2785
                $frmTagList->addHidden('user', $this->owner->getId());
2786
            }
2787
        }
2788
2789
        return $frmTagList;
2790
    }
2791
2792
    /**
2793
     * @throws Exception
2794
     */
2795
    private function createFormStudentFilter(bool $listByUser = false, bool $listHighlighted = false): FormValidator
2796
    {
2797
        $frmStudentList = new FormValidator(
2798
            'frm_student_list',
2799
            'get',
2800
            $this->baseUrl,
2801
            '',
2802
            [],
2803
            FormValidator::LAYOUT_BOX
2804
        );
2805
2806
        $urlParams = http_build_query(
2807
            [
2808
                'a' => 'search_user_by_course',
2809
                'course_id' => $this->course->getId(),
2810
                'session_id' => $this->session ? $this->session->getId() : 0,
2811
            ]
2812
        );
2813
2814
        /** @var SelectAjax $slctUser */
2815
        $slctUser = $frmStudentList->addSelectAjax(
2816
            'user',
2817
            get_lang('SelectLearnerPortfolio'),
2818
            [],
2819
            [
2820
                'url' => api_get_path(WEB_AJAX_PATH)."course.ajax.php?$urlParams",
2821
                'placeholder' => get_lang('SearchStudent'),
2822
                'formatResult' => SelectAjax::templateResultForUsersInCourse(),
2823
                'formatSelection' => SelectAjax::templateSelectionForUsersInCourse(),
2824
            ]
2825
        );
2826
2827
        if ($listByUser) {
2828
            $slctUser->addOption(
2829
                $this->owner->getCompleteName(),
2830
                $this->owner->getId(),
2831
                [
2832
                    'data-avatarurl' => UserManager::getUserPicture($this->owner->getId()),
2833
                    'data-username' => $this->owner->getUsername(),
2834
                ]
2835
            );
2836
2837
            $link = Display::url(
2838
                get_lang('BackToMainPortfolio'),
2839
                $this->baseUrl
2840
            );
2841
        } else {
2842
            $link = Display::url(
2843
                get_lang('SeeMyPortfolio'),
2844
                $this->baseUrl.http_build_query(['user' => api_get_user_id()])
2845
            );
2846
        }
2847
2848
        $frmStudentList->addHtml("<p>$link</p>");
2849
2850
        if ($listHighlighted) {
2851
            $link = Display::url(
2852
                get_lang('BackToMainPortfolio'),
2853
                $this->baseUrl
2854
            );
2855
        } else {
2856
            $link = Display::url(
2857
                get_lang('SeeHighlights'),
2858
                $this->baseUrl.http_build_query(['list_highlighted' => true])
2859
            );
2860
        }
2861
2862
        $frmStudentList->addHtml("<p>$link</p>");
2863
2864
        return $frmStudentList;
2865
    }
2866
2867
    private function getCategoriesForIndex(?int $currentUserId = null, ?int $parentId = null): array
2868
    {
2869
        $categoriesCriteria = [];
2870
        if (isset($currentUserId)) {
2871
            $categoriesCriteria['user'] = $this->owner;
2872
        }
2873
        if (!api_is_platform_admin() && $currentUserId !== $this->owner->getId()) {
2874
            $categoriesCriteria['isVisible'] = true;
2875
        }
2876
        if (isset($parentId)) {
2877
            $categoriesCriteria['parentId'] = $parentId;
2878
        }
2879
2880
        return $this->em
2881
            ->getRepository(PortfolioCategory::class)
2882
            ->findBy($categoriesCriteria);
2883
    }
2884
2885
    private function getHighlightedItems()
2886
    {
2887
        $queryBuilder = $this->em->createQueryBuilder();
2888
        $queryBuilder
2889
            ->select('pi')
2890
            ->from(Portfolio::class, 'pi')
2891
            ->where('pi.course = :course')
2892
            ->andWhere('pi.isHighlighted = TRUE')
2893
            ->setParameter('course', $this->course);
2894
2895
        if ($this->session) {
2896
            $queryBuilder->andWhere('pi.session = :session');
2897
            $queryBuilder->setParameter('session', $this->session);
2898
        } else {
2899
            $queryBuilder->andWhere('pi.session IS NULL');
2900
        }
2901
2902
        $visibilityCriteria = [Portfolio::VISIBILITY_VISIBLE];
2903
2904
        if (api_is_allowed_to_edit()) {
2905
            $visibilityCriteria[] = Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER;
2906
        }
2907
2908
        $queryBuilder
2909
            ->andWhere(
2910
                $queryBuilder->expr()->orX(
2911
                    'pi.user = :current_user',
2912
                    $queryBuilder->expr()->andX(
2913
                        'pi.user != :current_user',
2914
                        $queryBuilder->expr()->in('pi.visibility', $visibilityCriteria)
2915
                    )
2916
                )
2917
            )
2918
            ->setParameter('current_user', api_get_user_id());
2919
2920
        $queryBuilder->orderBy('pi.creationDate', 'DESC');
2921
2922
        return $queryBuilder->getQuery()->getResult();
2923
    }
2924
2925
    private function getItemsForIndex(
2926
        bool $listByUser = false,
2927
        FormValidator $frmFilterList = null
2928
    ) {
2929
        $currentUserId = api_get_user_id();
2930
2931
        if ($this->course) {
2932
            $queryBuilder = $this->em->createQueryBuilder();
2933
            $queryBuilder
2934
                ->select('pi')
2935
                ->from(Portfolio::class, 'pi')
2936
                ->where('pi.course = :course');
2937
2938
            $queryBuilder->setParameter('course', $this->course);
2939
2940
            if ($this->session) {
2941
                $queryBuilder->andWhere('pi.session = :session');
2942
                $queryBuilder->setParameter('session', $this->session);
2943
            } else {
2944
                $queryBuilder->andWhere('pi.session IS NULL');
2945
            }
2946
2947
            if ($frmFilterList && $frmFilterList->validate()) {
2948
                $values = $frmFilterList->exportValues();
2949
2950
                if (!empty($values['date'])) {
2951
                    $queryBuilder
2952
                        ->andWhere('pi.creationDate >= :date')
2953
                        ->setParameter(':date', api_get_utc_datetime($values['date'], false, true))
2954
                    ;
2955
                }
2956
2957
                if (!empty($values['tags'])) {
2958
                    $queryBuilder
2959
                        ->innerJoin(ExtraFieldRelTag::class, 'efrt', Join::WITH, 'efrt.itemId = pi.id')
2960
                        ->innerJoin(ExtraFieldEntity::class, 'ef', Join::WITH, 'ef.id = efrt.fieldId')
2961
                        ->andWhere('ef.extraFieldType = :efType')
2962
                        ->andWhere('ef.variable = :variable')
2963
                        ->andWhere('efrt.tagId IN (:tags)');
2964
2965
                    $queryBuilder->setParameter('efType', ExtraFieldEntity::PORTFOLIO_TYPE);
2966
                    $queryBuilder->setParameter('variable', 'tags');
2967
                    $queryBuilder->setParameter('tags', $values['tags']);
2968
                }
2969
2970
                if (!empty($values['text'])) {
2971
                    $queryBuilder->andWhere(
2972
                        $queryBuilder->expr()->orX(
2973
                            $queryBuilder->expr()->like('pi.title', ':text'),
2974
                            $queryBuilder->expr()->like('pi.content', ':text')
2975
                        )
2976
                    );
2977
2978
                    $queryBuilder->setParameter('text', '%'.$values['text'].'%');
2979
                }
2980
2981
                // Filters by category level 0
2982
                $searchCategories = [];
2983
                if (!empty($values['categoryId'])) {
2984
                    $searchCategories[] = $values['categoryId'];
2985
                    $subCategories = $this->getCategoriesForIndex(null, $values['categoryId']);
2986
                    if (count($subCategories) > 0) {
2987
                        foreach ($subCategories as $subCategory) {
2988
                            $searchCategories[] = $subCategory->getId();
2989
                        }
2990
                    }
2991
                    $queryBuilder->andWhere('pi.category IN('.implode(',', $searchCategories).')');
2992
                }
2993
2994
                // Filters by sub-category, don't show the selected values
2995
                $diff = [];
2996
                if (!empty($values['subCategoryIds']) && !('all' === $values['subCategoryIds'])) {
2997
                    $subCategoryIds = explode(',', $values['subCategoryIds']);
2998
                    $diff = array_diff($searchCategories, $subCategoryIds);
2999
                } else {
3000
                    if (trim($values['subCategoryIds']) === '') {
3001
                        $diff = $searchCategories;
3002
                    }
3003
                }
3004
                if (!empty($diff)) {
3005
                    unset($diff[0]);
3006
                    if (!empty($diff)) {
3007
                        $queryBuilder->andWhere('pi.category NOT IN('.implode(',', $diff).')');
3008
                    }
3009
                }
3010
            }
3011
3012
            if ($listByUser) {
3013
                $queryBuilder
3014
                    ->andWhere('pi.user = :user')
3015
                    ->setParameter('user', $this->owner);
3016
            }
3017
3018
            $visibilityCriteria = [Portfolio::VISIBILITY_VISIBLE];
3019
3020
            if (api_is_allowed_to_edit()) {
3021
                $visibilityCriteria[] = Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER;
3022
            }
3023
3024
            $queryBuilder
3025
                ->andWhere(
3026
                    $queryBuilder->expr()->orX(
3027
                        'pi.user = :current_user',
3028
                        $queryBuilder->expr()->andX(
3029
                            'pi.user != :current_user',
3030
                            $queryBuilder->expr()->in('pi.visibility', $visibilityCriteria)
3031
                        )
3032
                    )
3033
                )
3034
                ->setParameter('current_user', $currentUserId);
3035
3036
            $queryBuilder->orderBy('pi.creationDate', 'DESC');
3037
3038
            $items = $queryBuilder->getQuery()->getResult();
3039
        } else {
3040
            $itemsCriteria = [];
3041
            $itemsCriteria['category'] = null;
3042
            $itemsCriteria['user'] = $this->owner;
3043
3044
            if ($currentUserId !== $this->owner->getId()) {
3045
                $itemsCriteria['visibility'] = Portfolio::VISIBILITY_VISIBLE;
3046
            }
3047
3048
            $items = $this->em
3049
                ->getRepository(Portfolio::class)
3050
                ->findBy($itemsCriteria, ['creationDate' => 'DESC']);
3051
        }
3052
3053
        return $items;
3054
    }
3055
3056
    /**
3057
     * @throws \Doctrine\ORM\ORMException
3058
     * @throws \Doctrine\ORM\OptimisticLockException
3059
     * @throws \Doctrine\ORM\TransactionRequiredException
3060
     */
3061
    private function createCommentForm(Portfolio $item): string
3062
    {
3063
        $formAction = $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()]);
3064
3065
        $form = new FormValidator('frm_comment', 'post', $formAction);
3066
        $form->addHeader(get_lang('AddNewComment'));
3067
        $form->addHtmlEditor('content', get_lang('Comments'), true, false, ['ToolbarSet' => 'Minimal']);
3068
        $form->addHidden('item', $item->getId());
3069
        $form->addHidden('parent', 0);
3070
        $form->applyFilter('content', 'trim');
3071
3072
        $this->addAttachmentsFieldToForm($form);
3073
3074
        $form->addButtonSave(get_lang('Save'));
3075
3076
        if ($form->validate()) {
3077
            $values = $form->exportValues();
3078
3079
            $parentComment = $this->em->find(PortfolioComment::class, $values['parent']);
3080
3081
            $comment = new PortfolioComment();
3082
            $comment
3083
                ->setAuthor($this->owner)
3084
                ->setParent($parentComment)
3085
                ->setContent($values['content'])
3086
                ->setDate(api_get_utc_datetime(null, false, true))
3087
                ->setItem($item);
3088
3089
            $this->em->persist($comment);
3090
            $this->em->flush();
3091
3092
            $this->processAttachments(
3093
                $form,
3094
                $comment->getAuthor(),
3095
                $comment->getId(),
3096
                PortfolioAttachment::TYPE_COMMENT
3097
            );
3098
3099
            $hook = HookPortfolioItemCommented::create();
3100
            $hook->setEventData(['comment' => $comment]);
3101
            $hook->notifyItemCommented();
3102
3103
            PortfolioNotifier::notifyTeachersAndAuthor($comment);
3104
3105
            Display::addFlash(
3106
                Display::return_message(get_lang('CommentAdded'), 'success')
3107
            );
3108
3109
            header("Location: $formAction");
3110
            exit;
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return string. Consider adding a return statement or allowing null as return value.

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

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

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
3111
        }
3112
3113
        return $form->returnForm();
3114
    }
3115
3116
    private function generateAttachmentList($post, bool $includeHeader = true): string
3117
    {
3118
        $attachmentsRepo = $this->em->getRepository(PortfolioAttachment::class);
3119
3120
        $postOwnerId = 0;
3121
3122
        if ($post instanceof Portfolio) {
3123
            $attachments = $attachmentsRepo->findFromItem($post);
3124
3125
            $postOwnerId = $post->getUser()->getId();
3126
        } elseif ($post instanceof PortfolioComment) {
3127
            $attachments = $attachmentsRepo->findFromComment($post);
3128
3129
            $postOwnerId = $post->getAuthor()->getId();
3130
        }
3131
3132
        if (empty($attachments)) {
3133
            return '';
3134
        }
3135
3136
        $currentUserId = api_get_user_id();
3137
3138
        $listItems = '<ul class="fa-ul">';
3139
3140
        $deleteIcon = Display::return_icon(
3141
            'delete.png',
3142
            get_lang('DeleteAttachment'),
3143
            ['style' => 'display: inline-block'],
3144
            ICON_SIZE_TINY
3145
        );
3146
        $deleteAttrs = ['class' => 'btn-portfolio-delete'];
3147
3148
        /** @var PortfolioAttachment $attachment */
3149
        foreach ($attachments as $attachment) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $attachments does not seem to be defined for all execution paths leading up to this point.
Loading history...
3150
            $downloadParams = http_build_query(['action' => 'download_attachment', 'file' => $attachment->getPath()]);
3151
            $deleteParams = http_build_query(['action' => 'delete_attachment', 'file' => $attachment->getPath()]);
3152
3153
            $listItems .= '<li>'
3154
                .'<span class="fa-li fa fa-paperclip" aria-hidden="true"></span>'
3155
                .Display::url(
3156
                    Security::remove_XSS($attachment->getFilename()),
3157
                    $this->baseUrl.$downloadParams
3158
                );
3159
3160
            if ($currentUserId === $postOwnerId) {
3161
                $listItems .= PHP_EOL.Display::url($deleteIcon, $this->baseUrl.$deleteParams, $deleteAttrs);
3162
            }
3163
3164
            if ($attachment->getComment()) {
3165
                $listItems .= '<p class="text-muted">'.Security::remove_XSS($attachment->getComment()).'</p>';
3166
            }
3167
3168
            $listItems .= '</li>';
3169
        }
3170
3171
        $listItems .= '</ul>';
3172
3173
        if ($includeHeader) {
3174
            $listItems = '<h1 class="h4">'.get_lang('AttachmentFiles').'</h1>'
3175
                .$listItems;
3176
        }
3177
3178
        return $listItems;
3179
    }
3180
3181
    private function generateItemContent(Portfolio $item): string
3182
    {
3183
        $originId = $item->getOrigin();
3184
3185
        if (empty($originId)) {
3186
            return $item->getContent();
3187
        }
3188
3189
        $em = Database::getManager();
3190
3191
        $originContent = '';
3192
        $originContentFooter = '';
3193
3194
        if (Portfolio::TYPE_ITEM === $item->getOriginType()) {
3195
            $origin = $em->find(Portfolio::class, $item->getOrigin());
3196
3197
            if ($origin) {
3198
                $originContent = $origin->getContent();
3199
                $originContentFooter = vsprintf(
3200
                    get_lang('OriginallyPublishedAsXTitleByYUser'),
3201
                    [
3202
                        "<cite>{$origin->getTitle(true)}</cite>",
3203
                        $origin->getUser()->getCompleteName(),
3204
                    ]
3205
                );
3206
            }
3207
        } elseif (Portfolio::TYPE_COMMENT === $item->getOriginType()) {
3208
            $origin = $em->find(PortfolioComment::class, $item->getOrigin());
3209
3210
            if ($origin) {
3211
                $originContent = $origin->getContent();
3212
                $originContentFooter = vsprintf(
3213
                    get_lang('OriginallyCommentedByXUserInYItem'),
3214
                    [
3215
                        $origin->getAuthor()->getCompleteName(),
3216
                        "<cite>{$origin->getItem()->getTitle(true)}</cite>",
3217
                    ]
3218
                );
3219
            }
3220
        }
3221
3222
        if ($originContent) {
3223
            return "<figure>
3224
                    <blockquote>$originContent</blockquote>
3225
                    <figcaption style=\"margin-bottom: 10px;\">$originContentFooter</figcaption>
3226
                </figure>
3227
                <div class=\"clearfix\">".Security::remove_XSS($item->getContent()).'</div>'
3228
            ;
3229
        }
3230
3231
        return Security::remove_XSS($item->getContent());
3232
    }
3233
3234
    private function getItemsInHtmlFormatted(array $items): array
3235
    {
3236
        $itemsHtml = [];
3237
3238
        /** @var Portfolio $item */
3239
        foreach ($items as $item) {
3240
            $itemCourse = $item->getCourse();
3241
            $itemSession = $item->getSession();
3242
3243
            $creationDate = api_convert_and_format_date($item->getCreationDate());
3244
            $updateDate = api_convert_and_format_date($item->getUpdateDate());
3245
3246
            $metadata = '<ul class="list-unstyled text-muted">';
3247
3248
            if ($itemSession) {
3249
                $metadata .= '<li>'.get_lang('Course').': '.$itemSession->getName().' ('
3250
                    .$itemCourse->getTitle().') </li>';
3251
            } elseif ($itemCourse) {
3252
                $metadata .= '<li>'.get_lang('Course').': '.$itemCourse->getTitle().'</li>';
3253
            }
3254
3255
            $metadata .= '<li>'.sprintf(get_lang('CreationDateXDate'), $creationDate).'</li>';
3256
3257
            if ($itemCourse) {
3258
                $propertyInfo = api_get_item_property_info(
3259
                    $itemCourse->getId(),
3260
                    TOOL_PORTFOLIO,
3261
                    $item->getId(),
3262
                    $itemSession ? $itemSession->getId() : 0
3263
                );
3264
3265
                if ($propertyInfo) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $propertyInfo of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
3266
                    $metadata .= '<li>'
3267
                        .sprintf(
3268
                            get_lang('UpdatedOnDateXByUserY'),
3269
                            api_convert_and_format_date($propertyInfo['lastedit_date'], DATE_TIME_FORMAT_LONG),
3270
                            api_get_user_entity($propertyInfo['lastedit_user_id'])->getCompleteName()
3271
                        )
3272
                        .'</li>';
3273
                }
3274
            } else {
3275
                $metadata .= '<li>'.sprintf(get_lang('UpdateDateXDate'), $updateDate).'</li>';
3276
            }
3277
3278
            if ($item->getCategory()) {
3279
                $metadata .= '<li>'.sprintf(get_lang('CategoryXName'), $item->getCategory()->getTitle()).'</li>';
3280
            }
3281
3282
            $metadata .= '</ul>';
3283
3284
            $itemContent = $this->generateItemContent($item);
3285
3286
            $itemsHtml[] = Display::panel($itemContent, Security::remove_XSS($item->getTitle()), '', 'info', $metadata);
3287
        }
3288
3289
        return $itemsHtml;
3290
    }
3291
3292
    private function getCommentsInHtmlFormatted(array $comments): array
3293
    {
3294
        $commentsHtml = [];
3295
3296
        /** @var PortfolioComment $comment */
3297
        foreach ($comments as $comment) {
3298
            $item = $comment->getItem();
3299
            $date = api_convert_and_format_date($comment->getDate());
3300
3301
            $metadata = '<ul class="list-unstyled text-muted">';
3302
            $metadata .= '<li>'.sprintf(get_lang('DateXDate'), $date).'</li>';
3303
            $metadata .= '<li>'.sprintf(get_lang('PortfolioItemTitleXName'), Security::remove_XSS($item->getTitle()))
3304
                .'</li>';
3305
            $metadata .= '</ul>';
3306
3307
            $commentsHtml[] = Display::panel(
3308
                Security::remove_XSS($comment->getContent()),
3309
                '',
3310
                '',
3311
                'default',
3312
                $metadata
3313
            );
3314
        }
3315
3316
        return $commentsHtml;
3317
    }
3318
3319
    private function fixImagesSourcesToHtml(string $htmlContent): string
3320
    {
3321
        $doc = new DOMDocument();
3322
        @$doc->loadHTML($htmlContent);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for loadHTML(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

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

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
3323
3324
        $elements = $doc->getElementsByTagName('img');
3325
3326
        if (empty($elements->length)) {
3327
            return $htmlContent;
3328
        }
3329
3330
        $webCoursePath = api_get_path(WEB_COURSE_PATH);
3331
        $webUploadPath = api_get_path(WEB_UPLOAD_PATH);
3332
3333
        /** @var \DOMElement $element */
3334
        foreach ($elements as $element) {
3335
            $src = trim($element->getAttribute('src'));
3336
3337
            if (strpos($src, 'http') === 0) {
3338
                continue;
3339
            }
3340
3341
            if (strpos($src, '/app/upload/') === 0) {
3342
                $element->setAttribute(
3343
                    'src',
3344
                    preg_replace('/\/app/upload\//', $webUploadPath, $src, 1)
3345
                );
3346
3347
                continue;
3348
            }
3349
3350
            if (strpos($src, '/courses/') === 0) {
3351
                $element->setAttribute(
3352
                    'src',
3353
                    preg_replace('/\/courses\//', $webCoursePath, $src, 1)
3354
                );
3355
3356
                continue;
3357
            }
3358
        }
3359
3360
        return $doc->saveHTML();
3361
    }
3362
3363
    private function formatZipIndexFile(HTML_Table $tblItems, HTML_Table $tblComments): string
3364
    {
3365
        $htmlContent = Display::page_header($this->owner->getCompleteNameWithUsername());
3366
        $htmlContent .= Display::page_subheader2(get_lang('PortfolioItems'));
3367
3368
        $htmlContent .= $tblItems->getRowCount() > 0
3369
            ? $tblItems->toHtml()
3370
            : Display::return_message(get_lang('NoItemsInYourPortfolio'), 'warning');
3371
3372
        $htmlContent .= Display::page_subheader2(get_lang('PortfolioCommentsMade'));
3373
3374
        $htmlContent .= $tblComments->getRowCount() > 0
3375
            ? $tblComments->toHtml()
3376
            : Display::return_message(get_lang('YouHaveNotCommented'), 'warning');
3377
3378
        $webAssetsPath = api_get_path(WEB_PUBLIC_PATH).'assets/';
3379
3380
        $doc = new DOMDocument();
3381
        @$doc->loadHTML($htmlContent);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for loadHTML(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

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

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
3382
3383
        $stylesheet1 = $doc->createElement('link');
3384
        $stylesheet1->setAttribute('rel', 'stylesheet');
3385
        $stylesheet1->setAttribute('href', $webAssetsPath.'bootstrap/dist/css/bootstrap.min.css');
3386
        $stylesheet2 = $doc->createElement('link');
3387
        $stylesheet2->setAttribute('rel', 'stylesheet');
3388
        $stylesheet2->setAttribute('href', $webAssetsPath.'fontawesome/css/font-awesome.min.css');
3389
        $stylesheet3 = $doc->createElement('link');
3390
        $stylesheet3->setAttribute('rel', 'stylesheet');
3391
        $stylesheet3->setAttribute('href', ChamiloApi::getEditorDocStylePath());
3392
3393
        $head = $doc->createElement('head');
3394
        $head->appendChild($stylesheet1);
3395
        $head->appendChild($stylesheet2);
3396
        $head->appendChild($stylesheet3);
3397
3398
        $doc->documentElement->insertBefore(
3399
            $head,
3400
            $doc->getElementsByTagName('body')->item(0)
3401
        );
3402
3403
        return $doc->saveHTML();
3404
    }
3405
3406
    /**
3407
     * It parsers a title for a variable in lang.
3408
     *
3409
     * @param $defaultDisplayText
3410
     *
3411
     * @return string
3412
     */
3413
    private function getLanguageVariable($defaultDisplayText)
3414
    {
3415
        $variableLanguage = api_replace_dangerous_char(strtolower($defaultDisplayText));
3416
        $variableLanguage = preg_replace('/[^A-Za-z0-9\_]/', '', $variableLanguage); // Removes special chars except underscore.
3417
        if (is_numeric($variableLanguage[0])) {
3418
            $variableLanguage = '_'.$variableLanguage;
3419
        }
3420
        $variableLanguage = api_underscore_to_camel_case($variableLanguage);
3421
3422
        return $variableLanguage;
3423
    }
3424
3425
    /**
3426
     * It translates the text as parameter.
3427
     *
3428
     * @param $defaultDisplayText
3429
     *
3430
     * @return mixed
3431
     */
3432
    private function translateDisplayName($defaultDisplayText)
3433
    {
3434
        $variableLanguage = $this->getLanguageVariable($defaultDisplayText);
3435
3436
        return isset($GLOBALS[$variableLanguage]) ? $GLOBALS[$variableLanguage] : $defaultDisplayText;
3437
    }
3438
}
3439