Passed
Pull Request — 1.11.x (#4368)
by Angel Fernando Quiroz
18:36 queued 08:28
created

PortfolioController::generateItemContent()   B

Complexity

Conditions 7
Paths 11

Size

Total Lines 51
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 28
dl 0
loc 51
rs 8.5386
c 0
b 0
f 0
cc 7
nc 11
nop 1

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Component\Utils\ChamiloApi;
6
use Chamilo\CoreBundle\Entity\Course as CourseEntity;
7
use Chamilo\CoreBundle\Entity\ExtraField as ExtraFieldEntity;
8
use Chamilo\CoreBundle\Entity\ExtraFieldRelTag;
9
use Chamilo\CoreBundle\Entity\Portfolio;
10
use Chamilo\CoreBundle\Entity\PortfolioAttachment;
11
use Chamilo\CoreBundle\Entity\PortfolioCategory;
12
use Chamilo\CoreBundle\Entity\PortfolioComment;
13
use Chamilo\CoreBundle\Entity\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
        $form = new FormValidator('add_portfolio', 'post', $this->baseUrl.'action=add_item');
464
465
        if (api_get_configuration_value('save_titles_as_html')) {
466
            $form->addHtmlEditor('title', get_lang('Title'), true, false, ['ToolbarSet' => 'TitleAsHtml']);
467
        } else {
468
            $form->addText('title', get_lang('Title'));
469
            $form->applyFilter('title', 'trim');
470
        }
471
        $editorConfig = [
472
            'ToolbarSet' => 'NotebookStudent',
473
            'Width' => '100%',
474
            'Height' => '400',
475
            'cols-size' => [2, 10, 0],
476
        ];
477
        $form->addHtmlEditor('content', get_lang('Content'), true, false, $editorConfig);
478
479
        $categoriesSelect = $form->addSelect(
480
            'category',
481
            [get_lang('Category'), get_lang('PortfolioCategoryFieldHelp')]
482
        );
483
        $categoriesSelect->addOption(get_lang('SelectACategory'), 0);
484
        $parentCategories = $this->getCategoriesForIndex(null, 0);
485
        foreach ($parentCategories as $parentCategory) {
486
            $categoriesSelect->addOption($this->translateDisplayName($parentCategory->getTitle()), $parentCategory->getId());
487
            $subCategories = $this->getCategoriesForIndex(null, $parentCategory->getId());
488
            if (count($subCategories) > 0) {
489
                foreach ($subCategories as $subCategory) {
490
                    $categoriesSelect->addOption(' &mdash; '.$this->translateDisplayName($subCategory->getTitle()), $subCategory->getId());
491
                }
492
            }
493
        }
494
495
        $extraField = new ExtraField('portfolio');
496
        $extra = $extraField->addElements($form);
497
498
        $this->addAttachmentsFieldToForm($form);
499
500
        $form->addButtonCreate(get_lang('Create'));
501
502
        if ($form->validate()) {
503
            $values = $form->exportValues();
504
            $currentTime = new DateTime(
505
                api_get_utc_datetime(),
506
                new DateTimeZone('UTC')
507
            );
508
509
            $portfolio = new Portfolio();
510
            $portfolio
511
                ->setTitle($values['title'])
512
                ->setContent($values['content'])
513
                ->setUser($this->owner)
514
                ->setCourse($this->course)
515
                ->setSession($this->session)
516
                ->setCategory(
517
                    $this->em->find('ChamiloCoreBundle:PortfolioCategory', $values['category'])
518
                )
519
                ->setCreationDate($currentTime)
520
                ->setUpdateDate($currentTime);
521
522
            $this->em->persist($portfolio);
523
            $this->em->flush();
524
525
            $values['item_id'] = $portfolio->getId();
526
527
            $extraFieldValue = new ExtraFieldValue('portfolio');
528
            $extraFieldValue->saveFieldValues($values);
529
530
            $this->processAttachments(
531
                $form,
532
                $portfolio->getUser(),
533
                $portfolio->getId(),
534
                PortfolioAttachment::TYPE_ITEM
535
            );
536
537
            $hook = HookPortfolioItemAdded::create();
538
            $hook->setEventData(['portfolio' => $portfolio]);
539
            $hook->notifyItemAdded();
540
541
            if (1 == api_get_course_setting('email_alert_teachers_new_post')) {
542
                if ($this->session) {
543
                    $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

543
                    $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...
544
545
                    $teachers = SessionManager::getCoachesByCourseSession(
546
                        $this->session->getId(),
547
                        $this->course->getId()
548
                    );
549
                    $userIdListToSend = array_values($teachers);
550
                } else {
551
                    $messageCourseTitle = $this->course->getTitle();
552
553
                    $teachers = CourseManager::get_teacher_list_from_course_code($this->course->getCode());
554
555
                    $userIdListToSend = array_keys($teachers);
556
                }
557
558
                $messageSubject = sprintf(get_lang('PortfolioAlertNewPostSubject'), $messageCourseTitle);
559
560
                foreach ($userIdListToSend as $userIdToSend) {
561
                    $messageContent = sprintf(
562
                        get_lang('PortfolioAlertNewPostContent'),
563
                        $this->owner->getCompleteName(),
564
                        $messageCourseTitle,
565
                        $this->baseUrl.http_build_query(['action' => 'view', 'id' => $portfolio->getId()])
566
                    );
567
568
                    MessageManager::send_message_simple($userIdToSend, $messageSubject, $messageContent, 0, false, false, [], false);
569
                }
570
            }
571
572
            Display::addFlash(
573
                Display::return_message(get_lang('PortfolioItemAdded'), 'success')
574
            );
575
576
            header("Location: $this->baseUrl");
577
            exit;
578
        }
579
580
        $interbreadcrumb[] = [
581
            'name' => get_lang('Portfolio'),
582
            'url' => $this->baseUrl,
583
        ];
584
585
        $actions = [];
586
        $actions[] = Display::url(
587
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
588
            $this->baseUrl
589
        );
590
        $actions[] = '<a id="hide_bar_template" href="#" role="button">'.
591
            Display::return_icon('expand.png', get_lang('Expand'), ['id' => 'expand'], ICON_SIZE_MEDIUM).
592
            Display::return_icon('contract.png', get_lang('Collapse'), ['id' => 'contract', 'class' => 'hide'], ICON_SIZE_MEDIUM).'</a>';
593
594
        $js = '<script>
595
            $(function() {
596
                $(".scrollbar-light").scrollbar();
597
                $(".scroll-wrapper").css("height", "550px");
598
                expandColumnToogle("#hide_bar_template", {
599
                    selector: "#template_col",
600
                    width: 3
601
                }, {
602
                    selector: "#doc_form",
603
                    width: 9
604
                });
605
                CKEDITOR.on("instanceReady", function (e) {
606
                    showTemplates();
607
                });
608
                $(window).on("load", function () {
609
                    $("input[name=\'title\']").focus();
610
                });
611
                '.$extra['jquery_ready_content'].'
612
            });
613
        </script>';
614
        $content = '<div class="page-create">
615
            <div class="row" style="overflow:hidden">
616
            <div id="template_col" class="col-md-3">
617
                <div class="panel panel-default">
618
                <div class="panel-body">
619
                    <div id="frmModel" class="items-templates scrollbar-light"></div>
620
                </div>
621
                </div>
622
            </div>
623
            <div id="doc_form" class="col-md-9">
624
                '.$form->returnForm().'
625
            </div>
626
          </div></div>';
627
628
        $this->renderView(
629
            $content.$js,
630
            get_lang('AddPortfolioItem'),
631
            $actions
632
        );
633
    }
634
635
    /**
636
     * @throws \Doctrine\ORM\ORMException
637
     * @throws \Doctrine\ORM\OptimisticLockException
638
     * @throws \Doctrine\ORM\TransactionRequiredException
639
     * @throws \Exception
640
     */
641
    public function editItem(Portfolio $item)
642
    {
643
        global $interbreadcrumb;
644
645
        if (!api_is_allowed_to_edit() && !$this->itemBelongToOwner($item)) {
646
            api_not_allowed(true);
647
        }
648
649
        $itemCourse = $item->getCourse();
650
        $itemSession = $item->getSession();
651
652
        $form = new FormValidator('edit_portfolio', 'post', $this->baseUrl."action=edit_item&id={$item->getId()}");
653
654
        if (api_get_configuration_value('save_titles_as_html')) {
655
            $form->addHtmlEditor('title', get_lang('Title'), true, false, ['ToolbarSet' => 'TitleAsHtml']);
656
        } else {
657
            $form->addText('title', get_lang('Title'));
658
            $form->applyFilter('title', 'trim');
659
        }
660
661
        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...
662
            if (Portfolio::TYPE_ITEM === $item->getOriginType()) {
663
                $origin = $this->em->find(Portfolio::class, $item->getOrigin());
664
665
                $form->addLabel(
666
                    sprintf(get_lang('PortfolioItemFromXUser'), $origin->getUser()->getCompleteName()),
667
                    Display::panel(
668
                        Security::remove_XSS($origin->getContent())
669
                    )
670
                );
671
            } elseif (Portfolio::TYPE_COMMENT === $item->getOriginType()) {
672
                $origin = $this->em->find(PortfolioComment::class, $item->getOrigin());
673
674
                $form->addLabel(
675
                    sprintf(get_lang('PortfolioCommentFromXUser'), $origin->getAuthor()->getCompleteName()),
676
                    Display::panel(
677
                        Security::remove_XSS($origin->getContent())
678
                    )
679
                );
680
            }
681
        }
682
        $editorConfig = [
683
            'ToolbarSet' => 'NotebookStudent',
684
            'Width' => '100%',
685
            'Height' => '400',
686
            'cols-size' => [2, 10, 0],
687
        ];
688
        $form->addHtmlEditor('content', get_lang('Content'), true, false, $editorConfig);
689
        $categoriesSelect = $form->addSelect(
690
            'category',
691
            [get_lang('Category'), get_lang('PortfolioCategoryFieldHelp')]
692
        );
693
        $categoriesSelect->addOption(get_lang('SelectACategory'), 0);
694
        $parentCategories = $this->getCategoriesForIndex(null, 0);
695
        foreach ($parentCategories as $parentCategory) {
696
            $categoriesSelect->addOption($this->translateDisplayName($parentCategory->getTitle()), $parentCategory->getId());
697
            $subCategories = $this->getCategoriesForIndex(null, $parentCategory->getId());
698
            if (count($subCategories) > 0) {
699
                foreach ($subCategories as $subCategory) {
700
                    $categoriesSelect->addOption(' &mdash; '.$this->translateDisplayName($subCategory->getTitle()), $subCategory->getId());
701
                }
702
            }
703
        }
704
705
        $extraField = new ExtraField('portfolio');
706
        $extra = $extraField->addElements($form, $item->getId());
707
708
        $attachmentList = $this->generateAttachmentList($item, false);
709
710
        if (!empty($attachmentList)) {
711
            $form->addLabel(get_lang('AttachmentFiles'), $attachmentList);
712
        }
713
714
        $this->addAttachmentsFieldToForm($form);
715
716
        $form->addButtonUpdate(get_lang('Update'));
717
        $form->setDefaults(
718
            [
719
                'title' => $item->getTitle(),
720
                'content' => $item->getContent(),
721
                'category' => $item->getCategory() ? $item->getCategory()->getId() : '',
722
            ]
723
        );
724
725
        if ($form->validate()) {
726
            if ($itemCourse) {
0 ignored issues
show
introduced by
$itemCourse is of type Chamilo\CoreBundle\Entity\Course, thus it always evaluated to true.
Loading history...
727
                api_item_property_update(
728
                    api_get_course_info($itemCourse->getCode()),
729
                    TOOL_PORTFOLIO,
730
                    $item->getId(),
731
                    'PortfolioUpdated',
732
                    api_get_user_id(),
733
                    [],
734
                    null,
735
                    '',
736
                    '',
737
                    $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...
738
                );
739
            }
740
741
            $values = $form->exportValues();
742
            $currentTime = new DateTime(api_get_utc_datetime(), new DateTimeZone('UTC'));
743
744
            $item
745
                ->setTitle($values['title'])
746
                ->setContent($values['content'])
747
                ->setUpdateDate($currentTime)
748
                ->setCategory(
749
                    $this->em->find('ChamiloCoreBundle:PortfolioCategory', $values['category'])
750
                );
751
752
            $values['item_id'] = $item->getId();
753
754
            $extraFieldValue = new ExtraFieldValue('portfolio');
755
            $extraFieldValue->saveFieldValues($values);
756
757
            $this->em->persist($item);
758
            $this->em->flush();
759
760
            $this->processAttachments(
761
                $form,
762
                $item->getUser(),
763
                $item->getId(),
764
                PortfolioAttachment::TYPE_ITEM
765
            );
766
767
            Display::addFlash(
768
                Display::return_message(get_lang('ItemUpdated'), 'success')
769
            );
770
771
            header("Location: $this->baseUrl");
772
            exit;
773
        }
774
775
        $interbreadcrumb[] = [
776
            'name' => get_lang('Portfolio'),
777
            'url' => $this->baseUrl,
778
        ];
779
        $actions = [];
780
        $actions[] = Display::url(
781
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
782
            $this->baseUrl
783
        );
784
        $actions[] = '<a id="hide_bar_template" href="#" role="button">'.
785
            Display::return_icon('expand.png', get_lang('Expand'), ['id' => 'expand'], ICON_SIZE_MEDIUM).
786
            Display::return_icon('contract.png', get_lang('Collapse'), ['id' => 'contract', 'class' => 'hide'], ICON_SIZE_MEDIUM).'</a>';
787
788
        $js = '<script>
789
            $(function() {
790
                $(".scrollbar-light").scrollbar();
791
                $(".scroll-wrapper").css("height", "550px");
792
                expandColumnToogle("#hide_bar_template", {
793
                    selector: "#template_col",
794
                    width: 3
795
                }, {
796
                    selector: "#doc_form",
797
                    width: 9
798
                });
799
                CKEDITOR.on("instanceReady", function (e) {
800
                    showTemplates();
801
                });
802
                $(window).on("load", function () {
803
                    $("input[name=\'title\']").focus();
804
                });
805
                '.$extra['jquery_ready_content'].'
806
            });
807
        </script>';
808
        $content = '<div class="page-create">
809
            <div class="row" style="overflow:hidden">
810
            <div id="template_col" class="col-md-3">
811
                <div class="panel panel-default">
812
                <div class="panel-body">
813
                    <div id="frmModel" class="items-templates scrollbar-light"></div>
814
                </div>
815
                </div>
816
            </div>
817
            <div id="doc_form" class="col-md-9">
818
                '.$form->returnForm().'
819
            </div>
820
          </div></div>';
821
822
        $this->renderView(
823
            $content.$js,
824
            get_lang('EditPortfolioItem'),
825
            $actions
826
        );
827
    }
828
829
    /**
830
     * @throws \Doctrine\ORM\ORMException
831
     * @throws \Doctrine\ORM\OptimisticLockException
832
     */
833
    public function showHideItem(Portfolio $item)
834
    {
835
        if (!$this->itemBelongToOwner($item)) {
836
            api_not_allowed(true);
837
        }
838
839
        switch ($item->getVisibility()) {
840
            case Portfolio::VISIBILITY_HIDDEN:
841
                $item->setVisibility(Portfolio::VISIBILITY_VISIBLE);
842
                break;
843
            case Portfolio::VISIBILITY_VISIBLE:
844
                $item->setVisibility(Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER);
845
                break;
846
            case Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER:
847
            default:
848
                $item->setVisibility(Portfolio::VISIBILITY_HIDDEN);
849
                break;
850
        }
851
852
        $this->em->persist($item);
853
        $this->em->flush();
854
855
        Display::addFlash(
856
            Display::return_message(get_lang('VisibilityChanged'), 'success')
857
        );
858
859
        header("Location: $this->baseUrl");
860
        exit;
861
    }
862
863
    /**
864
     * @throws \Doctrine\ORM\ORMException
865
     * @throws \Doctrine\ORM\OptimisticLockException
866
     */
867
    public function deleteItem(Portfolio $item)
868
    {
869
        if (!$this->itemBelongToOwner($item)) {
870
            api_not_allowed(true);
871
        }
872
873
        $this->em->remove($item);
874
        $this->em->flush();
875
876
        Display::addFlash(
877
            Display::return_message(get_lang('ItemDeleted'), 'success')
878
        );
879
880
        header("Location: $this->baseUrl");
881
        exit;
882
    }
883
884
    /**
885
     * @throws \Exception
886
     */
887
    public function index(HttpRequest $httpRequest)
888
    {
889
        $listByUser = false;
890
        $listHighlighted = $httpRequest->query->has('list_highlighted');
891
892
        if ($httpRequest->query->has('user')) {
893
            $this->owner = api_get_user_entity($httpRequest->query->getInt('user'));
894
895
            if (empty($this->owner)) {
896
                api_not_allowed(true);
897
            }
898
899
            $listByUser = true;
900
        }
901
902
        $currentUserId = api_get_user_id();
903
904
        $actions = [];
905
906
        if (api_is_platform_admin()) {
907
            $actions[] = Display::url(
908
                Display::return_icon('add.png', get_lang('Add'), [], ICON_SIZE_MEDIUM),
909
                $this->baseUrl.'action=add_item'
910
            );
911
            $actions[] = Display::url(
912
                Display::return_icon('folder.png', get_lang('AddCategory'), [], ICON_SIZE_MEDIUM),
913
                $this->baseUrl.'action=list_categories'
914
            );
915
            $actions[] = Display::url(
916
                Display::return_icon('waiting_list.png', get_lang('PortfolioDetails'), [], ICON_SIZE_MEDIUM),
917
                $this->baseUrl.'action=details'
918
            );
919
        } else {
920
            if ($currentUserId == $this->owner->getId()) {
921
                $actions[] = Display::url(
922
                    Display::return_icon('add.png', get_lang('Add'), [], ICON_SIZE_MEDIUM),
923
                    $this->baseUrl.'action=add_item'
924
                );
925
                $actions[] = Display::url(
926
                    Display::return_icon('waiting_list.png', get_lang('PortfolioDetails'), [], ICON_SIZE_MEDIUM),
927
                    $this->baseUrl.'action=details'
928
                );
929
            } else {
930
                $actions[] = Display::url(
931
                    Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
932
                    $this->baseUrl
933
                );
934
            }
935
        }
936
937
        $frmStudentList = null;
938
        $frmTagList = null;
939
940
        $categories = [];
941
        $portfolio = [];
942
        if ($this->course) {
943
            $frmTagList = $this->createFormTagFilter($listByUser);
944
            $frmStudentList = $this->createFormStudentFilter($listByUser, $listHighlighted);
945
            $frmStudentList->setDefaults(['user' => $this->owner->getId()]);
946
            // it translates the category title with the current user language
947
            $categories = $this->getCategoriesForIndex(null, 0);
948
            if (count($categories) > 0) {
949
                foreach ($categories as &$category) {
950
                    $translated = $this->translateDisplayName($category->getTitle());
951
                    $category->setTitle($translated);
952
                }
953
            }
954
        } else {
955
            // it displays the list in Network Social for the current user
956
            $portfolio = $this->getCategoriesForIndex();
957
        }
958
959
        if ($listHighlighted) {
960
            $items = $this->getHighlightedItems();
961
        } else {
962
            $items = $this->getItemsForIndex($listByUser, $frmTagList);
963
        }
964
965
        // it gets and translate the sub-categories
966
        $categoryId = $httpRequest->query->getInt('categoryId');
967
        $subCategoryIdsReq = isset($_REQUEST['subCategoryIds']) ? Security::remove_XSS($_REQUEST['subCategoryIds']) : '';
968
        $subCategoryIds = $subCategoryIdsReq;
969
        if ('all' !== $subCategoryIdsReq) {
970
            $subCategoryIds = !empty($subCategoryIdsReq) ? explode(',', $subCategoryIdsReq) : [];
971
        }
972
        $subcategories = [];
973
        if ($categoryId > 0) {
974
            $subcategories = $this->getCategoriesForIndex(null, $categoryId);
975
            if (count($subcategories) > 0) {
976
                foreach ($subcategories as &$subcategory) {
977
                    $translated = $this->translateDisplayName($subcategory->getTitle());
978
                    $subcategory->setTitle($translated);
979
                }
980
            }
981
        }
982
983
        $template = new Template(null, false, false, false, false, false, false);
984
        $template->assign('user', $this->owner);
985
        $template->assign('course', $this->course);
986
        $template->assign('session', $this->session);
987
        $template->assign('portfolio', $portfolio);
988
        $template->assign('categories', $categories);
989
        $template->assign('uncategorized_items', $items);
990
        $template->assign('frm_student_list', $this->course ? $frmStudentList->returnForm() : '');
991
        $template->assign('frm_tag_list', $this->course ? $frmTagList->returnForm() : '');
992
        $template->assign('category_id', $categoryId);
993
        $template->assign('subcategories', $subcategories);
994
        $template->assign('subcategory_ids', $subCategoryIds);
995
996
        $js = '<script>
997
            $(function() {
998
                $(".category-filters").bind("click", function() {
999
                    var categoryId = parseInt($(this).find("input[type=\'radio\']").val());
1000
                    $("input[name=\'categoryId\']").val(categoryId);
1001
                    $("input[name=\'subCategoryIds\']").val("all");
1002
                    $("#frm_tag_list_submit").trigger("click");
1003
                });
1004
                $(".subcategory-filters").bind("click", function() {
1005
                    var checkedVals = $(".subcategory-filters:checkbox:checked").map(function() {
1006
                        return this.value;
1007
                    }).get();
1008
                    $("input[name=\'subCategoryIds\']").val(checkedVals.join(","));
1009
                    $("#frm_tag_list_submit").trigger("click");
1010
                });
1011
            });
1012
        </script>';
1013
        $template->assign('js_script', $js);
1014
        $layout = $template->get_template('portfolio/list.html.twig');
1015
1016
        Display::addFlash(
1017
            Display::return_message(get_lang('PortfolioPostAddHelp'), 'info', false)
1018
        );
1019
1020
        $content = $template->fetch($layout);
1021
1022
        $this->renderView($content, get_lang('Portfolio'), $actions);
1023
    }
1024
1025
    /**
1026
     * @throws \Doctrine\ORM\ORMException
1027
     * @throws \Doctrine\ORM\OptimisticLockException
1028
     * @throws \Doctrine\ORM\TransactionRequiredException
1029
     */
1030
    public function view(Portfolio $item)
1031
    {
1032
        global $interbreadcrumb;
1033
1034
        if (!$this->itemBelongToOwner($item)) {
1035
            if ($item->getVisibility() === Portfolio::VISIBILITY_HIDDEN
1036
                || ($item->getVisibility() === Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER && !api_is_allowed_to_edit())
1037
            ) {
1038
                api_not_allowed(true);
1039
            }
1040
        }
1041
1042
        HookPortfolioItemViewed::create()
1043
            ->setEventData(['portfolio' => $item])
1044
            ->notifyItemViewed()
1045
        ;
1046
1047
        $itemCourse = $item->getCourse();
1048
        $itemSession = $item->getSession();
1049
1050
        $form = $this->createCommentForm($item);
1051
1052
        $commentsRepo = $this->em->getRepository(PortfolioComment::class);
1053
1054
        $query = $commentsRepo->createQueryBuilder('comment')
1055
            ->where('comment.item = :item')
1056
            ->orderBy('comment.root, comment.lft', 'ASC')
1057
            ->setParameter('item', $item)
1058
            ->getQuery();
1059
1060
        $clockIcon = Display::returnFontAwesomeIcon('clock-o', '', true);
1061
1062
        $commentsHtml = $commentsRepo->buildTree(
1063
            $query->getArrayResult(),
1064
            [
1065
                'decorate' => true,
1066
                'rootOpen' => '<div class="media-list">',
1067
                'rootClose' => '</div>',
1068
                'childOpen' => function ($node) use ($commentsRepo) {
1069
                    /** @var PortfolioComment $comment */
1070
                    $comment = $commentsRepo->find($node['id']);
1071
                    $author = $comment->getAuthor();
1072
1073
                    $userPicture = UserManager::getUserPicture(
1074
                        $comment->getAuthor()->getId(),
1075
                        USER_IMAGE_SIZE_SMALL,
1076
                        null,
1077
                        [
1078
                            'picture_uri' => $author->getPictureUri(),
1079
                            'email' => $author->getEmail(),
1080
                        ]
1081
                    );
1082
1083
                    return '<article class="media" id="comment-'.$node['id'].'">
1084
                        <div class="media-left"><img class="media-object thumbnail" src="'.$userPicture.'" alt="'
1085
                        .$author->getCompleteName().'"></div>
1086
                        <div class="media-body">';
1087
                },
1088
                'childClose' => '</div></article>',
1089
                'nodeDecorator' => function ($node) use ($commentsRepo, $clockIcon, $item) {
1090
                    /** @var PortfolioComment $comment */
1091
                    $comment = $commentsRepo->find($node['id']);
1092
1093
                    $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...
1094
                        Display::return_icon('discuss.png', get_lang('ReplyToThisComment')),
1095
                        '#',
1096
                        [
1097
                            'data-comment' => htmlspecialchars(
1098
                                json_encode(['id' => $comment->getId()])
1099
                            ),
1100
                            'role' => 'button',
1101
                            'class' => 'btn-reply-to',
1102
                        ]
1103
                    );
1104
                    $commentActions[] = Display::url(
1105
                        Display::return_icon('copy.png', get_lang('CopyToMyPortfolio')),
1106
                        $this->baseUrl.http_build_query(
1107
                            [
1108
                                'action' => 'copy',
1109
                                'copy' => 'comment',
1110
                                'id' => $comment->getId(),
1111
                            ]
1112
                        )
1113
                    );
1114
1115
                    $isAllowedToEdit = api_is_allowed_to_edit();
1116
1117
                    if ($isAllowedToEdit) {
1118
                        $commentActions[] = Display::url(
1119
                            Display::return_icon('copy.png', get_lang('CopyToStudentPortfolio')),
1120
                            $this->baseUrl.http_build_query(
1121
                                [
1122
                                    'action' => 'teacher_copy',
1123
                                    'copy' => 'comment',
1124
                                    'id' => $comment->getId(),
1125
                                ]
1126
                            )
1127
                        );
1128
1129
                        if ($comment->isImportant()) {
1130
                            $commentActions[] = Display::url(
1131
                                Display::return_icon('drawing-pin.png', get_lang('UnmarkCommentAsImportant')),
1132
                                $this->baseUrl.http_build_query(
1133
                                    [
1134
                                        'action' => 'mark_important',
1135
                                        'item' => $item->getId(),
1136
                                        'id' => $comment->getId(),
1137
                                    ]
1138
                                )
1139
                            );
1140
                        } else {
1141
                            $commentActions[] = Display::url(
1142
                                Display::return_icon('drawing-pin.png', get_lang('MarkCommentAsImportant')),
1143
                                $this->baseUrl.http_build_query(
1144
                                    [
1145
                                        'action' => 'mark_important',
1146
                                        'item' => $item->getId(),
1147
                                        'id' => $comment->getId(),
1148
                                    ]
1149
                                )
1150
                            );
1151
                        }
1152
1153
                        if ($this->course && '1' === api_get_course_setting('qualify_portfolio_comment')) {
1154
                            $commentActions[] = Display::url(
1155
                                Display::return_icon('quiz.png', get_lang('QualifyThisPortfolioComment')),
1156
                                $this->baseUrl.http_build_query(
1157
                                    [
1158
                                        'action' => 'qualify',
1159
                                        'comment' => $comment->getId(),
1160
                                    ]
1161
                                )
1162
                            );
1163
                        }
1164
                    }
1165
1166
                    $nodeHtml = '<div class="pull-right">'.implode(PHP_EOL, $commentActions).'</div>'.PHP_EOL
1167
                        .'<footer class="media-heading h4">'.PHP_EOL
1168
                        .'<p>'.$comment->getAuthor()->getCompleteName().'</p>'.PHP_EOL;
1169
1170
                    if ($comment->isImportant()
1171
                        && ($this->itemBelongToOwner($comment->getItem()) || $isAllowedToEdit)
1172
                    ) {
1173
                        $nodeHtml .= '<span class="pull-right label label-warning origin-style">'
1174
                            .get_lang('CommentMarkedAsImportant')
1175
                            .'</span>'.PHP_EOL;
1176
                    }
1177
1178
                    $nodeHtml .= '<p class="small">'.$clockIcon.PHP_EOL
1179
                        .Display::dateToStringAgoAndLongDate($comment->getDate()).'</p>'.PHP_EOL;
1180
1181
                    $nodeHtml .= '</footer>'.PHP_EOL
1182
                        .Security::remove_XSS($comment->getContent()).PHP_EOL;
1183
1184
                    $nodeHtml .= $this->generateAttachmentList($comment);
1185
1186
                    return $nodeHtml;
1187
                },
1188
            ]
1189
        );
1190
1191
        $template = new Template(null, false, false, false, false, false, false);
1192
        $template->assign('baseurl', $this->baseUrl);
1193
        $template->assign('item', $item);
1194
        $template->assign('item_content', $this->generateItemContent($item));
1195
        $template->assign('comments', $commentsHtml);
1196
        $template->assign('form', $form);
1197
        $template->assign('attachment_list', $this->generateAttachmentList($item));
1198
1199
        if ($itemCourse) {
1200
            $propertyInfo = api_get_item_property_info(
1201
                $itemCourse->getId(),
1202
                TOOL_PORTFOLIO,
1203
                $item->getId(),
1204
                $itemSession ? $itemSession->getId() : 0
1205
            );
1206
1207
            if ($propertyInfo) {
1208
                $template->assign(
1209
                    'last_edit',
1210
                    [
1211
                        'date' => $propertyInfo['lastedit_date'],
1212
                        'user' => api_get_user_entity($propertyInfo['lastedit_user_id'])->getCompleteName(),
1213
                    ]
1214
                );
1215
            }
1216
        }
1217
1218
        $layout = $template->get_template('portfolio/view.html.twig');
1219
        $content = $template->fetch($layout);
1220
1221
        $interbreadcrumb[] = ['name' => get_lang('Portfolio'), 'url' => $this->baseUrl];
1222
1223
        $editLink = $actions[] = Display::url(
0 ignored issues
show
Comprehensibility Best Practice introduced by
$actions was never initialized. Although not strictly required by PHP, it is generally a good practice to add $actions = array(); before regardless.
Loading history...
1224
            Display::return_icon('edit.png', get_lang('Edit'), [], ICON_SIZE_MEDIUM),
1225
            $this->baseUrl.http_build_query(['action' => 'edit_item', 'id' => $item->getId()])
1226
        );
1227
1228
        $actions = [];
1229
        $actions[] = Display::url(
1230
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
1231
            $this->baseUrl
1232
        );
1233
1234
        if ($this->itemBelongToOwner($item)) {
1235
            $actions[] = $editLink;
1236
1237
            $visibilityUrl = $this->baseUrl.http_build_query(['action' => 'visibility', 'id' => $item->getId()]);
1238
1239
            if ($item->getVisibility() === Portfolio::VISIBILITY_HIDDEN) {
1240
                $actions[] = Display::url(
1241
                    Display::return_icon('invisible.png', get_lang('MakeVisible'), [], ICON_SIZE_MEDIUM),
1242
                    $visibilityUrl
1243
                );
1244
            } elseif ($item->getVisibility() === Portfolio::VISIBILITY_VISIBLE) {
1245
                $actions[] = Display::url(
1246
                    Display::return_icon('visible.png', get_lang('MakeVisibleForTeachers'), [], ICON_SIZE_MEDIUM),
1247
                    $visibilityUrl
1248
                );
1249
            } elseif ($item->getVisibility() === Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER) {
1250
                $actions[] = Display::url(
1251
                    Display::return_icon('eye-slash.png', get_lang('MakeInvisible'), [], ICON_SIZE_MEDIUM),
1252
                    $visibilityUrl
1253
                );
1254
            }
1255
1256
            $actions[] = Display::url(
1257
                Display::return_icon('delete.png', get_lang('Delete'), [], ICON_SIZE_MEDIUM),
1258
                $this->baseUrl.http_build_query(['action' => 'delete_item', 'id' => $item->getId()])
1259
            );
1260
        } else {
1261
            $actions[] = Display::url(
1262
                Display::return_icon('copy.png', get_lang('CopyToMyPortfolio'), [], ICON_SIZE_MEDIUM),
1263
                $this->baseUrl.http_build_query(['action' => 'copy', 'id' => $item->getId()])
1264
            );
1265
        }
1266
1267
        if (api_is_allowed_to_edit()) {
1268
            $actions[] = Display::url(
1269
                Display::return_icon('copy.png', get_lang('CopyToStudentPortfolio'), [], ICON_SIZE_MEDIUM),
1270
                $this->baseUrl.http_build_query(['action' => 'teacher_copy', 'copy' => 'item', 'id' => $item->getId()])
1271
            );
1272
            $actions[] = $editLink;
1273
1274
            $highlightedUrl = $this->baseUrl.http_build_query(['action' => 'highlighted', 'id' => $item->getId()]);
1275
1276
            if ($item->isHighlighted()) {
1277
                $actions[] = Display::url(
1278
                    Display::return_icon('award_red.png', get_lang('UnmarkAsHighlighted'), [], ICON_SIZE_MEDIUM),
1279
                    $highlightedUrl
1280
                );
1281
            } else {
1282
                $actions[] = Display::url(
1283
                    Display::return_icon('award_red_na.png', get_lang('MarkAsHighlighted'), [], ICON_SIZE_MEDIUM),
1284
                    $highlightedUrl
1285
                );
1286
            }
1287
1288
            if ($itemCourse && '1' === api_get_course_setting('qualify_portfolio_item')) {
1289
                $actions[] = Display::url(
1290
                    Display::return_icon('quiz.png', get_lang('QualifyThisPortfolioItem'), [], ICON_SIZE_MEDIUM),
1291
                    $this->baseUrl.http_build_query(['action' => 'qualify', 'item' => $item->getId()])
1292
                );
1293
            }
1294
        }
1295
1296
        $this->renderView($content, $item->getTitle(true), $actions, false);
1297
    }
1298
1299
    /**
1300
     * @throws \Doctrine\ORM\ORMException
1301
     * @throws \Doctrine\ORM\OptimisticLockException
1302
     */
1303
    public function copyItem(Portfolio $originItem)
1304
    {
1305
        $currentTime = api_get_utc_datetime(null, false, true);
1306
1307
        $portfolio = new Portfolio();
1308
        $portfolio
1309
            ->setVisibility(Portfolio::VISIBILITY_HIDDEN)
1310
            ->setTitle(
1311
                sprintf(get_lang('PortfolioItemFromXUser'), $originItem->getUser()->getCompleteName())
1312
            )
1313
            ->setContent('')
1314
            ->setUser($this->owner)
1315
            ->setOrigin($originItem->getId())
1316
            ->setOriginType(Portfolio::TYPE_ITEM)
1317
            ->setCourse($this->course)
1318
            ->setSession($this->session)
1319
            ->setCreationDate($currentTime)
1320
            ->setUpdateDate($currentTime);
1321
1322
        $this->em->persist($portfolio);
1323
        $this->em->flush();
1324
1325
        Display::addFlash(
1326
            Display::return_message(get_lang('PortfolioItemAdded'), 'success')
1327
        );
1328
1329
        header("Location: $this->baseUrl".http_build_query(['action' => 'edit_item', 'id' => $portfolio->getId()]));
1330
        exit;
1331
    }
1332
1333
    /**
1334
     * @throws \Doctrine\ORM\ORMException
1335
     * @throws \Doctrine\ORM\OptimisticLockException
1336
     */
1337
    public function copyComment(PortfolioComment $originComment)
1338
    {
1339
        $currentTime = api_get_utc_datetime(null, false, true);
1340
1341
        $portfolio = new Portfolio();
1342
        $portfolio
1343
            ->setVisibility(Portfolio::VISIBILITY_HIDDEN)
1344
            ->setTitle(
1345
                sprintf(get_lang('PortfolioCommentFromXUser'), $originComment->getAuthor()->getCompleteName())
1346
            )
1347
            ->setContent('')
1348
            ->setUser($this->owner)
1349
            ->setOrigin($originComment->getId())
1350
            ->setOriginType(Portfolio::TYPE_COMMENT)
1351
            ->setCourse($this->course)
1352
            ->setSession($this->session)
1353
            ->setCreationDate($currentTime)
1354
            ->setUpdateDate($currentTime);
1355
1356
        $this->em->persist($portfolio);
1357
        $this->em->flush();
1358
1359
        Display::addFlash(
1360
            Display::return_message(get_lang('PortfolioItemAdded'), 'success')
1361
        );
1362
1363
        header("Location: $this->baseUrl".http_build_query(['action' => 'edit_item', 'id' => $portfolio->getId()]));
1364
        exit;
1365
    }
1366
1367
    /**
1368
     * @throws \Doctrine\ORM\ORMException
1369
     * @throws \Doctrine\ORM\OptimisticLockException
1370
     * @throws \Exception
1371
     */
1372
    public function teacherCopyItem(Portfolio $originItem)
1373
    {
1374
        $actionParams = http_build_query(['action' => 'teacher_copy', 'copy' => 'item', 'id' => $originItem->getId()]);
1375
1376
        $form = new FormValidator('teacher_copy_portfolio', 'post', $this->baseUrl.$actionParams);
1377
1378
        if (api_get_configuration_value('save_titles_as_html')) {
1379
            $form->addHtmlEditor('title', get_lang('Title'), true, false, ['ToolbarSet' => 'TitleAsHtml']);
1380
        } else {
1381
            $form->addText('title', get_lang('Title'));
1382
            $form->applyFilter('title', 'trim');
1383
        }
1384
1385
        $form->addLabel(
1386
            sprintf(get_lang('PortfolioItemFromXUser'), $originItem->getUser()->getCompleteName()),
1387
            Display::panel(
1388
                Security::remove_XSS($originItem->getContent())
1389
            )
1390
        );
1391
        $form->addHtmlEditor('content', get_lang('Content'), true, false, ['ToolbarSet' => 'NotebookStudent']);
1392
1393
        $urlParams = http_build_query(
1394
            [
1395
                'a' => 'search_user_by_course',
1396
                'course_id' => $this->course->getId(),
1397
                'session_id' => $this->session ? $this->session->getId() : 0,
1398
            ]
1399
        );
1400
        $form->addSelectAjax(
1401
            'students',
1402
            get_lang('Students'),
1403
            [],
1404
            [
1405
                'url' => api_get_path(WEB_AJAX_PATH)."course.ajax.php?$urlParams",
1406
                'multiple' => true,
1407
            ]
1408
        );
1409
        $form->addRule('students', get_lang('ThisFieldIsRequired'), 'required');
1410
        $form->addButtonCreate(get_lang('Save'));
1411
1412
        $toolName = get_lang('CopyToStudentPortfolio');
1413
        $content = $form->returnForm();
1414
1415
        if ($form->validate()) {
1416
            $values = $form->exportValues();
1417
1418
            $currentTime = api_get_utc_datetime(null, false, true);
1419
1420
            foreach ($values['students'] as $studentId) {
1421
                $owner = api_get_user_entity($studentId);
1422
1423
                $portfolio = new Portfolio();
1424
                $portfolio
1425
                    ->setVisibility(Portfolio::VISIBILITY_HIDDEN)
1426
                    ->setTitle($values['title'])
1427
                    ->setContent($values['content'])
1428
                    ->setUser($owner)
1429
                    ->setOrigin($originItem->getId())
1430
                    ->setOriginType(Portfolio::TYPE_ITEM)
1431
                    ->setCourse($this->course)
1432
                    ->setSession($this->session)
1433
                    ->setCreationDate($currentTime)
1434
                    ->setUpdateDate($currentTime);
1435
1436
                $this->em->persist($portfolio);
1437
            }
1438
1439
            $this->em->flush();
1440
1441
            Display::addFlash(
1442
                Display::return_message(get_lang('PortfolioItemAddedToStudents'), 'success')
1443
            );
1444
1445
            header("Location: $this->baseUrl");
1446
            exit;
1447
        }
1448
1449
        $this->renderView($content, $toolName);
1450
    }
1451
1452
    /**
1453
     * @throws \Doctrine\ORM\ORMException
1454
     * @throws \Doctrine\ORM\OptimisticLockException
1455
     * @throws \Exception
1456
     */
1457
    public function teacherCopyComment(PortfolioComment $originComment)
1458
    {
1459
        $actionParams = http_build_query(
1460
            [
1461
                'action' => 'teacher_copy',
1462
                'copy' => 'comment',
1463
                'id' => $originComment->getId(),
1464
            ]
1465
        );
1466
1467
        $form = new FormValidator('teacher_copy_portfolio', 'post', $this->baseUrl.$actionParams);
1468
1469
        if (api_get_configuration_value('save_titles_as_html')) {
1470
            $form->addHtmlEditor('title', get_lang('Title'), true, false, ['ToolbarSet' => 'TitleAsHtml']);
1471
        } else {
1472
            $form->addText('title', get_lang('Title'));
1473
            $form->applyFilter('title', 'trim');
1474
        }
1475
1476
        $form->addLabel(
1477
            sprintf(get_lang('PortfolioCommentFromXUser'), $originComment->getAuthor()->getCompleteName()),
1478
            Display::panel(
1479
                Security::remove_XSS($originComment->getContent())
1480
            )
1481
        );
1482
        $form->addHtmlEditor('content', get_lang('Content'), true, false, ['ToolbarSet' => 'NotebookStudent']);
1483
1484
        $urlParams = http_build_query(
1485
            [
1486
                'a' => 'search_user_by_course',
1487
                'course_id' => $this->course->getId(),
1488
                'session_id' => $this->session ? $this->session->getId() : 0,
1489
            ]
1490
        );
1491
        $form->addSelectAjax(
1492
            'students',
1493
            get_lang('Students'),
1494
            [],
1495
            [
1496
                'url' => api_get_path(WEB_AJAX_PATH)."course.ajax.php?$urlParams",
1497
                'multiple' => true,
1498
            ]
1499
        );
1500
        $form->addRule('students', get_lang('ThisFieldIsRequired'), 'required');
1501
        $form->addButtonCreate(get_lang('Save'));
1502
1503
        $toolName = get_lang('CopyToStudentPortfolio');
1504
        $content = $form->returnForm();
1505
1506
        if ($form->validate()) {
1507
            $values = $form->exportValues();
1508
1509
            $currentTime = api_get_utc_datetime(null, false, true);
1510
1511
            foreach ($values['students'] as $studentId) {
1512
                $owner = api_get_user_entity($studentId);
1513
1514
                $portfolio = new Portfolio();
1515
                $portfolio
1516
                    ->setVisibility(Portfolio::VISIBILITY_HIDDEN)
1517
                    ->setTitle($values['title'])
1518
                    ->setContent($values['content'])
1519
                    ->setUser($owner)
1520
                    ->setOrigin($originComment->getId())
1521
                    ->setOriginType(Portfolio::TYPE_COMMENT)
1522
                    ->setCourse($this->course)
1523
                    ->setSession($this->session)
1524
                    ->setCreationDate($currentTime)
1525
                    ->setUpdateDate($currentTime);
1526
1527
                $this->em->persist($portfolio);
1528
            }
1529
1530
            $this->em->flush();
1531
1532
            Display::addFlash(
1533
                Display::return_message(get_lang('PortfolioItemAddedToStudents'), 'success')
1534
            );
1535
1536
            header("Location: $this->baseUrl");
1537
            exit;
1538
        }
1539
1540
        $this->renderView($content, $toolName);
1541
    }
1542
1543
    /**
1544
     * @throws \Doctrine\ORM\ORMException
1545
     * @throws \Doctrine\ORM\OptimisticLockException
1546
     */
1547
    public function markImportantCommentInItem(Portfolio $item, PortfolioComment $comment)
1548
    {
1549
        if ($comment->getItem()->getId() !== $item->getId()) {
1550
            api_not_allowed(true);
1551
        }
1552
1553
        $comment->setIsImportant(
1554
            !$comment->isImportant()
1555
        );
1556
1557
        $this->em->persist($comment);
1558
        $this->em->flush();
1559
1560
        Display::addFlash(
1561
            Display::return_message(get_lang('CommentMarkedAsImportant'), 'success')
1562
        );
1563
1564
        header("Location: $this->baseUrl".http_build_query(['action' => 'view', 'id' => $item->getId()]));
1565
        exit;
1566
    }
1567
1568
    /**
1569
     * @throws \Exception
1570
     */
1571
    public function details(HttpRequest $httpRequest)
1572
    {
1573
        $currentUserId = api_get_user_id();
1574
        $isAllowedToFilterStudent = $this->course && api_is_allowed_to_edit();
1575
1576
        $actions = [];
1577
        $actions[] = Display::url(
1578
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
1579
            $this->baseUrl
1580
        );
1581
        $actions[] = Display::url(
1582
            Display::return_icon('pdf.png', get_lang('ExportMyPortfolioDataPdf'), [], ICON_SIZE_MEDIUM),
1583
            $this->baseUrl.http_build_query(['action' => 'export_pdf'])
1584
        );
1585
        $actions[] = Display::url(
1586
            Display::return_icon('save_pack.png', get_lang('ExportMyPortfolioDataZip'), [], ICON_SIZE_MEDIUM),
1587
            $this->baseUrl.http_build_query(['action' => 'export_zip'])
1588
        );
1589
1590
        $frmStudent = null;
1591
1592
        if ($isAllowedToFilterStudent) {
1593
            if ($httpRequest->query->has('user')) {
1594
                $this->owner = api_get_user_entity($httpRequest->query->getInt('user'));
1595
1596
                if (empty($this->owner)) {
1597
                    api_not_allowed(true);
1598
                }
1599
1600
                $actions[1] = Display::url(
1601
                    Display::return_icon('pdf.png', get_lang('ExportMyPortfolioDataPdf'), [], ICON_SIZE_MEDIUM),
1602
                    $this->baseUrl.http_build_query(['action' => 'export_pdf', 'user' => $this->owner->getId()])
1603
                );
1604
                $actions[2] = Display::url(
1605
                    Display::return_icon('save_pack.png', get_lang('ExportMyPortfolioDataZip'), [], ICON_SIZE_MEDIUM),
1606
                    $this->baseUrl.http_build_query(['action' => 'export_zip', 'user' => $this->owner->getId()])
1607
                );
1608
            }
1609
1610
            $frmStudent = new FormValidator('frm_student_list', 'get');
1611
1612
            $urlParams = http_build_query(
1613
                [
1614
                    'a' => 'search_user_by_course',
1615
                    'course_id' => $this->course->getId(),
1616
                    'session_id' => $this->session ? $this->session->getId() : 0,
1617
                ]
1618
            );
1619
1620
            $frmStudent
1621
                ->addSelectAjax(
1622
                    'user',
1623
                    get_lang('SelectLearnerPortfolio'),
1624
                    [],
1625
                    [
1626
                        'url' => api_get_path(WEB_AJAX_PATH)."course.ajax.php?$urlParams",
1627
                        'placeholder' => get_lang('SearchStudent'),
1628
                        'formatResult' => SelectAjax::templateResultForUsersInCourse(),
1629
                        'formatSelection' => SelectAjax::templateSelectionForUsersInCourse(),
1630
                    ]
1631
                )
1632
                ->addOption(
1633
                    $this->owner->getCompleteName(),
1634
                    $this->owner->getId(),
1635
                    [
1636
                        'data-avatarurl' => UserManager::getUserPicture($this->owner->getId()),
1637
                        'data-username' => $this->owner->getUsername(),
1638
                    ]
1639
                )
1640
            ;
1641
            $frmStudent->setDefaults(['user' => $this->owner->getId()]);
1642
            $frmStudent->addHidden('action', 'details');
1643
            $frmStudent->addHidden('cidReq', $this->course->getCode());
1644
            $frmStudent->addHidden('id_session', $this->session ? $this->session->getId() : 0);
1645
            $frmStudent->addButtonFilter(get_lang('Filter'));
1646
        }
1647
1648
        $itemsRepo = $this->em->getRepository(Portfolio::class);
1649
        $commentsRepo = $this->em->getRepository(PortfolioComment::class);
1650
1651
        $getItemsTotalNumber = function () use ($itemsRepo, $isAllowedToFilterStudent, $currentUserId) {
1652
            $qb = $itemsRepo->createQueryBuilder('i');
1653
            $qb
1654
                ->select('COUNT(i)')
1655
                ->where('i.user = :user')
1656
                ->setParameter('user', $this->owner);
1657
1658
            if ($this->course) {
1659
                $qb
1660
                    ->andWhere('i.course = :course')
1661
                    ->setParameter('course', $this->course);
1662
1663
                if ($this->session) {
1664
                    $qb
1665
                        ->andWhere('i.session = :session')
1666
                        ->setParameter('session', $this->session);
1667
                } else {
1668
                    $qb->andWhere('i.session IS NULL');
1669
                }
1670
            }
1671
1672
            if ($isAllowedToFilterStudent && $currentUserId !== $this->owner->getId()) {
1673
                $visibilityCriteria = [
1674
                    Portfolio::VISIBILITY_VISIBLE,
1675
                    Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER,
1676
                ];
1677
1678
                $qb->andWhere(
1679
                    $qb->expr()->in('i.visibility', $visibilityCriteria)
1680
                );
1681
            }
1682
1683
            return $qb->getQuery()->getSingleScalarResult();
1684
        };
1685
        $getItemsData = function ($from, $limit, $columnNo, $orderDirection) use ($itemsRepo, $isAllowedToFilterStudent, $currentUserId) {
1686
            $qb = $itemsRepo->createQueryBuilder('item')
1687
                ->where('item.user = :user')
1688
                ->leftJoin('item.category', 'category')
1689
                ->leftJoin('item.course', 'course')
1690
                ->leftJoin('item.session', 'session')
1691
                ->setParameter('user', $this->owner);
1692
1693
            if ($this->course) {
1694
                $qb
1695
                    ->andWhere('item.course = :course_id')
1696
                    ->setParameter('course_id', $this->course);
1697
1698
                if ($this->session) {
1699
                    $qb
1700
                        ->andWhere('item.session = :session')
1701
                        ->setParameter('session', $this->session);
1702
                } else {
1703
                    $qb->andWhere('item.session IS NULL');
1704
                }
1705
            }
1706
1707
            if ($isAllowedToFilterStudent && $currentUserId !== $this->owner->getId()) {
1708
                $visibilityCriteria = [
1709
                    Portfolio::VISIBILITY_VISIBLE,
1710
                    Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER,
1711
                ];
1712
1713
                $qb->andWhere(
1714
                    $qb->expr()->in('item.visibility', $visibilityCriteria)
1715
                );
1716
            }
1717
1718
            if (0 == $columnNo) {
1719
                $qb->orderBy('item.title', $orderDirection);
1720
            } elseif (1 == $columnNo) {
1721
                $qb->orderBy('item.creationDate', $orderDirection);
1722
            } elseif (2 == $columnNo) {
1723
                $qb->orderBy('item.updateDate', $orderDirection);
1724
            } elseif (3 == $columnNo) {
1725
                $qb->orderBy('category.title', $orderDirection);
1726
            } elseif (5 == $columnNo) {
1727
                $qb->orderBy('item.score', $orderDirection);
1728
            } elseif (6 == $columnNo) {
1729
                $qb->orderBy('course.title', $orderDirection);
1730
            } elseif (7 == $columnNo) {
1731
                $qb->orderBy('session.name', $orderDirection);
1732
            }
1733
1734
            $qb->setFirstResult($from)->setMaxResults($limit);
1735
1736
            return array_map(
1737
                function (Portfolio $item) {
1738
                    $category = $item->getCategory();
1739
1740
                    $row = [];
1741
                    $row[] = $item;
1742
                    $row[] = $item->getCreationDate();
1743
                    $row[] = $item->getUpdateDate();
1744
                    $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...
1745
                    $row[] = $item->getComments()->count();
1746
                    $row[] = $item->getScore();
1747
1748
                    if (!$this->course) {
1749
                        $row[] = $item->getCourse();
1750
                        $row[] = $item->getSession();
1751
                    }
1752
1753
                    return $row;
1754
                },
1755
                $qb->getQuery()->getResult()
1756
            );
1757
        };
1758
1759
        $portfolioItemColumnFilter = function (Portfolio $item) {
1760
            return Display::url(
1761
                $item->getTitle(true),
1762
                $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()])
1763
            );
1764
        };
1765
        $convertFormatDateColumnFilter = function (DateTime $date) {
1766
            return api_convert_and_format_date($date);
1767
        };
1768
1769
        $tblItems = new SortableTable('tbl_items', $getItemsTotalNumber, $getItemsData, 1, 20, 'DESC');
1770
        $tblItems->set_additional_parameters(['action' => 'details', 'user' => $this->owner->getId()]);
1771
        $tblItems->set_header(0, get_lang('Title'));
1772
        $tblItems->set_column_filter(0, $portfolioItemColumnFilter);
1773
        $tblItems->set_header(1, get_lang('CreationDate'), true, [], ['class' => 'text-center']);
1774
        $tblItems->set_column_filter(1, $convertFormatDateColumnFilter);
1775
        $tblItems->set_header(2, get_lang('LastUpdate'), true, [], ['class' => 'text-center']);
1776
        $tblItems->set_column_filter(2, $convertFormatDateColumnFilter);
1777
        $tblItems->set_header(3, get_lang('Category'));
1778
        $tblItems->set_header(4, get_lang('Comments'), false, [], ['class' => 'text-right']);
1779
        $tblItems->set_header(5, get_lang('Score'), true, [], ['class' => 'text-right']);
1780
1781
        if (!$this->course) {
1782
            $tblItems->set_header(6, get_lang('Course'));
1783
            $tblItems->set_header(7, get_lang('Session'));
1784
        }
1785
1786
        $getCommentsTotalNumber = function () use ($commentsRepo) {
1787
            $qb = $commentsRepo->createQueryBuilder('c');
1788
            $qb
1789
                ->select('COUNT(c)')
1790
                ->where('c.author = :author')
1791
                ->setParameter('author', $this->owner);
1792
1793
            if ($this->course) {
1794
                $qb
1795
                    ->innerJoin('c.item', 'i')
1796
                    ->andWhere('i.course = :course')
1797
                    ->setParameter('course', $this->course);
1798
1799
                if ($this->session) {
1800
                    $qb
1801
                        ->andWhere('i.session = :session')
1802
                        ->setParameter('session', $this->session);
1803
                } else {
1804
                    $qb->andWhere('i.session IS NULL');
1805
                }
1806
            }
1807
1808
            return $qb->getQuery()->getSingleScalarResult();
1809
        };
1810
        $getCommentsData = function ($from, $limit, $columnNo, $orderDirection) use ($commentsRepo) {
1811
            $qb = $commentsRepo->createQueryBuilder('comment');
1812
            $qb
1813
                ->where('comment.author = :user')
1814
                ->innerJoin('comment.item', 'item')
1815
                ->setParameter('user', $this->owner);
1816
1817
            if ($this->course) {
1818
                $qb
1819
                    ->innerJoin('comment.item', 'i')
1820
                    ->andWhere('item.course = :course')
1821
                    ->setParameter('course', $this->course);
1822
1823
                if ($this->session) {
1824
                    $qb
1825
                        ->andWhere('item.session = :session')
1826
                        ->setParameter('session', $this->session);
1827
                } else {
1828
                    $qb->andWhere('item.session IS NULL');
1829
                }
1830
            }
1831
1832
            if (0 == $columnNo) {
1833
                $qb->orderBy('comment.content', $orderDirection);
1834
            } elseif (1 == $columnNo) {
1835
                $qb->orderBy('comment.date', $orderDirection);
1836
            } elseif (2 == $columnNo) {
1837
                $qb->orderBy('item.title', $orderDirection);
1838
            } elseif (3 == $columnNo) {
1839
                $qb->orderBy('comment.score', $orderDirection);
1840
            }
1841
1842
            $qb->setFirstResult($from)->setMaxResults($limit);
1843
1844
            return array_map(
1845
                function (PortfolioComment $comment) {
1846
                    return [
1847
                        $comment,
1848
                        $comment->getDate(),
1849
                        $comment->getItem(),
1850
                        $comment->getScore(),
1851
                    ];
1852
                },
1853
                $qb->getQuery()->getResult()
1854
            );
1855
        };
1856
1857
        $tblComments = new SortableTable('tbl_comments', $getCommentsTotalNumber, $getCommentsData, 1, 20, 'DESC');
1858
        $tblComments->set_additional_parameters(['action' => 'details', 'user' => $this->owner->getId()]);
1859
        $tblComments->set_header(0, get_lang('Resume'));
1860
        $tblComments->set_column_filter(
1861
            0,
1862
            function (PortfolioComment $comment) {
1863
                return Display::url(
1864
                    $comment->getExcerpt(),
1865
                    $this->baseUrl.http_build_query(['action' => 'view', 'id' => $comment->getItem()->getId()])
1866
                    .'#comment-'.$comment->getId()
1867
                );
1868
            }
1869
        );
1870
        $tblComments->set_header(1, get_lang('Date'), true, [], ['class' => 'text-center']);
1871
        $tblComments->set_column_filter(1, $convertFormatDateColumnFilter);
1872
        $tblComments->set_header(2, get_lang('PortfolioItemTitle'));
1873
        $tblComments->set_column_filter(2, $portfolioItemColumnFilter);
1874
        $tblComments->set_header(3, get_lang('Score'), true, [], ['class' => 'text-right']);
1875
1876
        $content = '';
1877
1878
        if ($frmStudent) {
1879
            $content .= $frmStudent->returnForm();
1880
        }
1881
1882
        $totalNumberOfItems = $tblItems->get_total_number_of_items();
1883
        $totalNumberOfComments = $tblComments->get_total_number_of_items();
1884
        $requiredNumberOfItems = (int) api_get_course_setting('portfolio_number_items');
1885
        $requiredNumberOfComments = (int) api_get_course_setting('portfolio_number_comments');
1886
1887
        $itemsSubtitle = '';
1888
1889
        if ($requiredNumberOfItems > 0) {
1890
            $itemsSubtitle = sprintf(
1891
                get_lang('XAddedYRequired'),
1892
                $totalNumberOfItems,
1893
                $requiredNumberOfItems
1894
            );
1895
        }
1896
1897
        $content .= Display::page_subheader2(
1898
            get_lang('PortfolioItems'),
1899
            $itemsSubtitle
1900
        ).PHP_EOL;
1901
1902
        if ($totalNumberOfItems > 0) {
1903
            $content .= $tblItems->return_table().PHP_EOL;
1904
        } else {
1905
            $content .= Display::return_message(get_lang('NoItemsInYourPortfolio'), 'warning');
1906
        }
1907
1908
        $commentsSubtitle = '';
1909
1910
        if ($requiredNumberOfComments > 0) {
1911
            $commentsSubtitle = sprintf(
1912
                get_lang('XAddedYRequired'),
1913
                $totalNumberOfComments,
1914
                $requiredNumberOfComments
1915
            );
1916
        }
1917
1918
        $content .= Display::page_subheader2(
1919
            get_lang('PortfolioCommentsMade'),
1920
            $commentsSubtitle
1921
        ).PHP_EOL;
1922
1923
        if ($totalNumberOfComments > 0) {
1924
            $content .= $tblComments->return_table().PHP_EOL;
1925
        } else {
1926
            $content .= Display::return_message(get_lang('YouHaveNotCommented'), 'warning');
1927
        }
1928
1929
        $this->renderView($content, get_lang('PortfolioDetails'), $actions);
1930
    }
1931
1932
    /**
1933
     * @throws \MpdfException
1934
     */
1935
    public function exportPdf(HttpRequest $httpRequest)
1936
    {
1937
        $currentUserId = api_get_user_id();
1938
        $isAllowedToFilterStudent = $this->course && api_is_allowed_to_edit();
1939
1940
        if ($isAllowedToFilterStudent) {
1941
            if ($httpRequest->query->has('user')) {
1942
                $this->owner = api_get_user_entity($httpRequest->query->getInt('user'));
1943
1944
                if (empty($this->owner)) {
1945
                    api_not_allowed(true);
1946
                }
1947
            }
1948
        }
1949
1950
        $pdfContent = Display::page_header($this->owner->getCompleteName());
1951
1952
        if ($this->course) {
1953
            $pdfContent .= '<p>'.get_lang('Course').': ';
1954
1955
            if ($this->session) {
1956
                $pdfContent .= $this->session->getName().' ('.$this->course->getTitle().')';
1957
            } else {
1958
                $pdfContent .= $this->course->getTitle();
1959
            }
1960
1961
            $pdfContent .= '</p>';
1962
        }
1963
1964
        $visibility = [];
1965
1966
        if ($isAllowedToFilterStudent && $currentUserId !== $this->owner->getId()) {
1967
            $visibility[] = Portfolio::VISIBILITY_VISIBLE;
1968
            $visibility[] = Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER;
1969
        }
1970
1971
        $items = $this->em
1972
            ->getRepository(Portfolio::class)
1973
            ->findItemsByUser(
1974
                $this->owner,
1975
                $this->course,
1976
                $this->session,
1977
                null,
1978
                $visibility
1979
            );
1980
        $comments = $this->em
1981
            ->getRepository(PortfolioComment::class)
1982
            ->findCommentsByUser($this->owner, $this->course, $this->session);
1983
1984
        $itemsHtml = $this->getItemsInHtmlFormatted($items);
1985
        $commentsHtml = $this->getCommentsInHtmlFormatted($comments);
1986
1987
        $totalNumberOfItems = count($itemsHtml);
1988
        $totalNumberOfComments = count($commentsHtml);
1989
        $requiredNumberOfItems = (int) api_get_course_setting('portfolio_number_items');
1990
        $requiredNumberOfComments = (int) api_get_course_setting('portfolio_number_comments');
1991
1992
        $itemsSubtitle = '';
1993
        $commentsSubtitle = '';
1994
1995
        if ($requiredNumberOfItems > 0) {
1996
            $itemsSubtitle = sprintf(
1997
                get_lang('XAddedYRequired'),
1998
                $totalNumberOfItems,
1999
                $requiredNumberOfItems
2000
            );
2001
        }
2002
2003
        if ($requiredNumberOfComments > 0) {
2004
            $commentsSubtitle = sprintf(
2005
                get_lang('XAddedYRequired'),
2006
                $totalNumberOfComments,
2007
                $requiredNumberOfComments
2008
            );
2009
        }
2010
2011
        $pdfContent .= Display::page_subheader2(
2012
            get_lang('PortfolioItems'),
2013
            $itemsSubtitle
2014
        );
2015
2016
        if ($totalNumberOfItems > 0) {
2017
            $pdfContent .= implode(PHP_EOL, $itemsHtml);
2018
        } else {
2019
            $pdfContent .= Display::return_message(get_lang('NoItemsInYourPortfolio'), 'warning');
2020
        }
2021
2022
        $pdfContent .= Display::page_subheader2(
2023
            get_lang('PortfolioCommentsMade'),
2024
            $commentsSubtitle
2025
        );
2026
2027
        if ($totalNumberOfComments > 0) {
2028
            $pdfContent .= implode(PHP_EOL, $commentsHtml);
2029
        } else {
2030
            $pdfContent .= Display::return_message(get_lang('YouHaveNotCommented'), 'warning');
2031
        }
2032
2033
        $pdfName = $this->owner->getCompleteName()
2034
            .($this->course ? '_'.$this->course->getCode() : '')
2035
            .'_'.get_lang('Portfolio');
2036
2037
        $pdf = new PDF();
2038
        $pdf->content_to_pdf(
2039
            $pdfContent,
2040
            null,
2041
            $pdfName,
2042
            $this->course ? $this->course->getCode() : null,
2043
            'D',
2044
            false,
2045
            null,
2046
            false,
2047
            true
2048
        );
2049
    }
2050
2051
    public function exportZip(HttpRequest $httpRequest)
2052
    {
2053
        $currentUserId = api_get_user_id();
2054
        $isAllowedToFilterStudent = $this->course && api_is_allowed_to_edit();
2055
2056
        if ($isAllowedToFilterStudent) {
2057
            if ($httpRequest->query->has('user')) {
2058
                $this->owner = api_get_user_entity($httpRequest->query->getInt('user'));
2059
2060
                if (empty($this->owner)) {
2061
                    api_not_allowed(true);
2062
                }
2063
            }
2064
        }
2065
2066
        $itemsRepo = $this->em->getRepository(Portfolio::class);
2067
        $commentsRepo = $this->em->getRepository(PortfolioComment::class);
2068
        $attachmentsRepo = $this->em->getRepository(PortfolioAttachment::class);
2069
2070
        $visibility = [];
2071
2072
        if ($isAllowedToFilterStudent && $currentUserId !== $this->owner->getId()) {
2073
            $visibility[] = Portfolio::VISIBILITY_VISIBLE;
2074
            $visibility[] = Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER;
2075
        }
2076
2077
        $items = $itemsRepo->findItemsByUser(
2078
            $this->owner,
2079
            $this->course,
2080
            $this->session,
2081
            null,
2082
            $visibility
2083
        );
2084
        $comments = $commentsRepo->findCommentsByUser($this->owner, $this->course, $this->session);
2085
2086
        $itemsHtml = $this->getItemsInHtmlFormatted($items);
2087
        $commentsHtml = $this->getCommentsInHtmlFormatted($comments);
2088
2089
        $sysArchivePath = api_get_path(SYS_ARCHIVE_PATH);
2090
        $tempPortfolioDirectory = $sysArchivePath."portfolio/{$this->owner->getId()}";
2091
2092
        $userDirectory = UserManager::getUserPathById($this->owner->getId(), 'system');
2093
        $attachmentsDirectory = $userDirectory.'portfolio_attachments/';
2094
2095
        $tblItemsHeaders = [];
2096
        $tblItemsHeaders[] = get_lang('Title');
2097
        $tblItemsHeaders[] = get_lang('CreationDate');
2098
        $tblItemsHeaders[] = get_lang('LastUpdate');
2099
        $tblItemsHeaders[] = get_lang('Category');
2100
        $tblItemsHeaders[] = get_lang('Category');
2101
        $tblItemsHeaders[] = get_lang('Score');
2102
        $tblItemsHeaders[] = get_lang('Course');
2103
        $tblItemsHeaders[] = get_lang('Session');
2104
        $tblItemsData = [];
2105
2106
        $tblCommentsHeaders = [];
2107
        $tblCommentsHeaders[] = get_lang('Resume');
2108
        $tblCommentsHeaders[] = get_lang('Date');
2109
        $tblCommentsHeaders[] = get_lang('PortfolioItemTitle');
2110
        $tblCommentsHeaders[] = get_lang('Score');
2111
        $tblCommentsData = [];
2112
2113
        $filenames = [];
2114
2115
        $fs = new Filesystem();
2116
2117
        /**
2118
         * @var int       $i
2119
         * @var Portfolio $item
2120
         */
2121
        foreach ($items as $i => $item) {
2122
            $itemCategory = $item->getCategory();
2123
            $itemCourse = $item->getCourse();
2124
            $itemSession = $item->getSession();
2125
2126
            $itemDirectory = $item->getCreationDate()->format('Y-m-d-H-i-s');
2127
2128
            $itemFilename = sprintf('%s/items/%s/item.html', $tempPortfolioDirectory, $itemDirectory);
2129
            $itemFileContent = $this->fixImagesSourcesToHtml($itemsHtml[$i]);
2130
2131
            $fs->dumpFile($itemFilename, $itemFileContent);
2132
2133
            $filenames[] = $itemFilename;
2134
2135
            $attachments = $attachmentsRepo->findFromItem($item);
2136
2137
            /** @var PortfolioAttachment $attachment */
2138
            foreach ($attachments as $attachment) {
2139
                $attachmentFilename = sprintf(
2140
                    '%s/items/%s/attachments/%s',
2141
                    $tempPortfolioDirectory,
2142
                    $itemDirectory,
2143
                    $attachment->getFilename()
2144
                );
2145
2146
                $fs->copy(
2147
                    $attachmentsDirectory.$attachment->getPath(),
2148
                    $attachmentFilename
2149
                );
2150
2151
                $filenames[] = $attachmentFilename;
2152
            }
2153
2154
            $tblItemsData[] = [
2155
                Display::url(
2156
                    Security::remove_XSS($item->getTitle()),
2157
                    sprintf('items/%s/item.html', $itemDirectory)
2158
                ),
2159
                api_convert_and_format_date($item->getCreationDate()),
2160
                api_convert_and_format_date($item->getUpdateDate()),
2161
                $itemCategory ? $itemCategory->getTitle() : null,
2162
                $item->getComments()->count(),
2163
                $item->getScore(),
2164
                $itemCourse->getTitle(),
2165
                $itemSession ? $itemSession->getName() : null,
2166
            ];
2167
        }
2168
2169
        /**
2170
         * @var int              $i
2171
         * @var PortfolioComment $comment
2172
         */
2173
        foreach ($comments as $i => $comment) {
2174
            $commentDirectory = $comment->getDate()->format('Y-m-d-H-i-s');
2175
2176
            $commentFileContent = $this->fixImagesSourcesToHtml($commentsHtml[$i]);
2177
            $commentFilename = sprintf('%s/comments/%s/comment.html', $tempPortfolioDirectory, $commentDirectory);
2178
2179
            $fs->dumpFile($commentFilename, $commentFileContent);
2180
2181
            $filenames[] = $commentFilename;
2182
2183
            $attachments = $attachmentsRepo->findFromComment($comment);
2184
2185
            /** @var PortfolioAttachment $attachment */
2186
            foreach ($attachments as $attachment) {
2187
                $attachmentFilename = sprintf(
2188
                    '%s/comments/%s/attachments/%s',
2189
                    $tempPortfolioDirectory,
2190
                    $commentDirectory,
2191
                    $attachment->getFilename()
2192
                );
2193
2194
                $fs->copy(
2195
                    $attachmentsDirectory.$attachment->getPath(),
2196
                    $attachmentFilename
2197
                );
2198
2199
                $filenames[] = $attachmentFilename;
2200
            }
2201
2202
            $tblCommentsData[] = [
2203
                Display::url(
2204
                    $comment->getExcerpt(),
2205
                    sprintf('comments/%s/comment.html', $commentDirectory)
2206
                ),
2207
                api_convert_and_format_date($comment->getDate()),
2208
                Security::remove_XSS($comment->getItem()->getTitle()),
2209
                $comment->getScore(),
2210
            ];
2211
        }
2212
2213
        $tblItems = new HTML_Table(['class' => 'table table-hover table-striped table-bordered data_table']);
2214
        $tblItems->setHeaders($tblItemsHeaders);
2215
        $tblItems->setData($tblItemsData);
2216
2217
        $tblComments = new HTML_Table(['class' => 'table table-hover table-striped table-bordered data_table']);
2218
        $tblComments->setHeaders($tblCommentsHeaders);
2219
        $tblComments->setData($tblCommentsData);
2220
2221
        $itemFilename = sprintf('%s/index.html', $tempPortfolioDirectory);
2222
2223
        $filenames[] = $itemFilename;
2224
2225
        $fs->dumpFile(
2226
            $itemFilename,
2227
            $this->formatZipIndexFile($tblItems, $tblComments)
2228
        );
2229
2230
        $zipName = $this->owner->getCompleteName()
2231
            .($this->course ? '_'.$this->course->getCode() : '')
2232
            .'_'.get_lang('Portfolio');
2233
        $tempZipFile = $sysArchivePath."portfolio/$zipName.zip";
2234
        $zip = new PclZip($tempZipFile);
2235
2236
        foreach ($filenames as $filename) {
2237
            $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...
2238
        }
2239
2240
        DocumentManager::file_send_for_download($tempZipFile, true, "$zipName.zip");
2241
2242
        $fs->remove($tempPortfolioDirectory);
2243
        $fs->remove($tempZipFile);
2244
    }
2245
2246
    public function qualifyItem(Portfolio $item)
2247
    {
2248
        global $interbreadcrumb;
2249
2250
        $em = Database::getManager();
2251
2252
        $formAction = $this->baseUrl.http_build_query(['action' => 'qualify', 'item' => $item->getId()]);
2253
2254
        $form = new FormValidator('frm_qualify', 'post', $formAction);
2255
        $form->addUserAvatar('user', get_lang('Author'));
2256
        $form->addLabel(get_lang('Title'), $item->getTitle());
2257
2258
        $itemContent = $this->generateItemContent($item);
2259
2260
        $form->addLabel(get_lang('Content'), $itemContent);
2261
        $form->addNumeric(
2262
            'score',
2263
            [get_lang('QualifyNumeric'), null, ' / '.api_get_course_setting('portfolio_max_score')]
2264
        );
2265
        $form->addButtonSave(get_lang('QualifyThisPortfolioItem'));
2266
2267
        if ($form->validate()) {
2268
            $values = $form->exportValues();
2269
2270
            $item->setScore($values['score']);
2271
2272
            $em->persist($item);
2273
            $em->flush();
2274
2275
            Display::addFlash(
2276
                Display::return_message(get_lang('PortfolioItemGraded'), 'success')
2277
            );
2278
2279
            header("Location: $formAction");
2280
            exit();
2281
        }
2282
2283
        $form->setDefaults(
2284
            [
2285
                'user' => $item->getUser(),
2286
                'score' => (float) $item->getScore(),
2287
            ]
2288
        );
2289
2290
        $interbreadcrumb[] = [
2291
            'name' => get_lang('Portfolio'),
2292
            'url' => $this->baseUrl,
2293
        ];
2294
        $interbreadcrumb[] = [
2295
            'name' => $item->getTitle(true),
2296
            'url' => $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()]),
2297
        ];
2298
2299
        $actions = [];
2300
        $actions[] = Display::url(
2301
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
2302
            $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()])
2303
        );
2304
2305
        $this->renderView($form->returnForm(), get_lang('Qualify'), $actions);
2306
    }
2307
2308
    public function qualifyComment(PortfolioComment $comment)
2309
    {
2310
        global $interbreadcrumb;
2311
2312
        $em = Database::getManager();
2313
2314
        $item = $comment->getItem();
2315
        $commentPath = $em->getRepository(PortfolioComment::class)->getPath($comment);
2316
2317
        $template = new Template('', false, false, false, true, false, false);
2318
        $template->assign('item', $item);
2319
        $template->assign('comments_path', $commentPath);
2320
        $commentContext = $template->fetch(
2321
            $template->get_template('portfolio/comment_context.html.twig')
2322
        );
2323
2324
        $formAction = $this->baseUrl.http_build_query(['action' => 'qualify', 'comment' => $comment->getId()]);
2325
2326
        $form = new FormValidator('frm_qualify', 'post', $formAction);
2327
        $form->addHtml($commentContext);
2328
        $form->addUserAvatar('user', get_lang('Author'));
2329
        $form->addLabel(get_lang('Comment'), $comment->getContent());
2330
        $form->addNumeric(
2331
            'score',
2332
            [get_lang('QualifyNumeric'), null, '/ '.api_get_course_setting('portfolio_max_score')]
2333
        );
2334
        $form->addButtonSave(get_lang('QualifyThisPortfolioComment'));
2335
2336
        if ($form->validate()) {
2337
            $values = $form->exportValues();
2338
2339
            $comment->setScore($values['score']);
2340
2341
            $em->persist($comment);
2342
            $em->flush();
2343
2344
            Display::addFlash(
2345
                Display::return_message(get_lang('PortfolioCommentGraded'), 'success')
2346
            );
2347
2348
            header("Location: $formAction");
2349
            exit();
2350
        }
2351
2352
        $form->setDefaults(
2353
            [
2354
                'user' => $comment->getAuthor(),
2355
                'score' => (float) $comment->getScore(),
2356
            ]
2357
        );
2358
2359
        $interbreadcrumb[] = [
2360
            'name' => get_lang('Portfolio'),
2361
            'url' => $this->baseUrl,
2362
        ];
2363
        $interbreadcrumb[] = [
2364
            'name' => $item->getTitle(true),
2365
            'url' => $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()]),
2366
        ];
2367
2368
        $actions = [];
2369
        $actions[] = Display::url(
2370
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
2371
            $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()])
2372
        );
2373
2374
        $this->renderView($form->returnForm(), get_lang('Qualify'), $actions);
2375
    }
2376
2377
    public function downloadAttachment(HttpRequest $httpRequest)
2378
    {
2379
        $path = $httpRequest->query->get('file');
2380
2381
        if (empty($path)) {
2382
            api_not_allowed(true);
2383
        }
2384
2385
        $em = Database::getManager();
2386
        $attachmentRepo = $em->getRepository(PortfolioAttachment::class);
2387
2388
        $attachment = $attachmentRepo->findOneByPath($path);
2389
2390
        if (empty($attachment)) {
2391
            api_not_allowed(true);
2392
        }
2393
2394
        $originOwnerId = 0;
2395
2396
        if (PortfolioAttachment::TYPE_ITEM === $attachment->getOriginType()) {
2397
            $item = $em->find(Portfolio::class, $attachment->getOrigin());
2398
2399
            $originOwnerId = $item->getUser()->getId();
2400
        } elseif (PortfolioAttachment::TYPE_COMMENT === $attachment->getOriginType()) {
2401
            $comment = $em->find(PortfolioComment::class, $attachment->getOrigin());
2402
2403
            $originOwnerId = $comment->getAuthor()->getId();
2404
        } else {
2405
            api_not_allowed(true);
2406
        }
2407
2408
        $userDirectory = UserManager::getUserPathById($originOwnerId, 'system');
2409
        $attachmentsDirectory = $userDirectory.'portfolio_attachments/';
2410
        $attachmentFilename = $attachmentsDirectory.$attachment->getPath();
2411
2412
        if (!Security::check_abs_path($attachmentFilename, $attachmentsDirectory)) {
2413
            api_not_allowed(true);
2414
        }
2415
2416
        $downloaded = DocumentManager::file_send_for_download(
2417
            $attachmentFilename,
2418
            true,
2419
            $attachment->getFilename()
2420
        );
2421
2422
        if (!$downloaded) {
2423
            api_not_allowed(true);
2424
        }
2425
    }
2426
2427
    public function deleteAttachment(HttpRequest $httpRequest)
2428
    {
2429
        $currentUserId = api_get_user_id();
2430
2431
        $path = $httpRequest->query->get('file');
2432
2433
        if (empty($path)) {
2434
            api_not_allowed(true);
2435
        }
2436
2437
        $em = Database::getManager();
2438
        $fs = new Filesystem();
2439
2440
        $attachmentRepo = $em->getRepository(PortfolioAttachment::class);
2441
        $attachment = $attachmentRepo->findOneByPath($path);
2442
2443
        if (empty($attachment)) {
2444
            api_not_allowed(true);
2445
        }
2446
2447
        $originOwnerId = 0;
2448
        $itemId = 0;
2449
2450
        if (PortfolioAttachment::TYPE_ITEM === $attachment->getOriginType()) {
2451
            $item = $em->find(Portfolio::class, $attachment->getOrigin());
2452
            $originOwnerId = $item->getUser()->getId();
2453
            $itemId = $item->getId();
2454
        } elseif (PortfolioAttachment::TYPE_COMMENT === $attachment->getOriginType()) {
2455
            $comment = $em->find(PortfolioComment::class, $attachment->getOrigin());
2456
            $originOwnerId = $comment->getAuthor()->getId();
2457
            $itemId = $comment->getItem()->getId();
2458
        }
2459
2460
        if ($currentUserId !== $originOwnerId) {
2461
            api_not_allowed(true);
2462
        }
2463
2464
        $em->remove($attachment);
2465
        $em->flush();
2466
2467
        $userDirectory = UserManager::getUserPathById($originOwnerId, 'system');
2468
        $attachmentsDirectory = $userDirectory.'portfolio_attachments/';
2469
        $attachmentFilename = $attachmentsDirectory.$attachment->getPath();
2470
2471
        $fs->remove($attachmentFilename);
2472
2473
        if ($httpRequest->isXmlHttpRequest()) {
2474
            echo Display::return_message(get_lang('AttachmentFileDeleteSuccess'), 'success');
2475
        } else {
2476
            Display::addFlash(
2477
                Display::return_message(get_lang('AttachmentFileDeleteSuccess'), 'success')
2478
            );
2479
2480
            header('Location: '.$this->baseUrl.http_build_query(['action' => 'view', 'id' => $itemId]));
2481
        }
2482
2483
        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...
2484
    }
2485
2486
    /**
2487
     * @throws \Doctrine\ORM\OptimisticLockException
2488
     * @throws \Doctrine\ORM\ORMException
2489
     */
2490
    public function markAsHighlighted(Portfolio $item)
2491
    {
2492
        if ($item->getCourse()->getId() !== (int) api_get_course_int_id()) {
2493
            api_not_allowed(true);
2494
        }
2495
2496
        $item->setIsHighlighted(
2497
            !$item->isHighlighted()
2498
        );
2499
2500
        Database::getManager()->flush();
2501
2502
        Display::addFlash(
2503
            Display::return_message(
2504
                $item->isHighlighted() ? get_lang('MarkedAsHighlighted') : get_lang('UnmarkedAsHighlighted'),
2505
                'success'
2506
            )
2507
        );
2508
2509
        header("Location: $this->baseUrl".http_build_query(['action' => 'view', 'id' => $item->getId()]));
2510
        exit;
2511
    }
2512
2513
    /**
2514
     * @param bool $showHeader
2515
     */
2516
    private function renderView(string $content, string $toolName, array $actions = [], $showHeader = true)
2517
    {
2518
        global $this_section;
2519
2520
        $this_section = $this->course ? SECTION_COURSES : SECTION_SOCIAL;
2521
2522
        $view = new Template($toolName);
2523
2524
        if ($showHeader) {
2525
            $view->assign('header', $toolName);
2526
        }
2527
2528
        $actionsStr = '';
2529
2530
        if ($this->course) {
2531
            $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...
2532
        }
2533
2534
        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...
2535
            $actions = implode('', $actions);
2536
2537
            $actionsStr .= Display::toolbarAction('portfolio-toolbar', [$actions]);
2538
        }
2539
2540
        $view->assign('baseurl', $this->baseUrl);
2541
        $view->assign('actions', $actionsStr);
2542
2543
        $view->assign('content', $content);
2544
        $view->display_one_col_template();
2545
    }
2546
2547
    private function categoryBelongToOwner(PortfolioCategory $category): bool
2548
    {
2549
        if ($category->getUser()->getId() != $this->owner->getId()) {
2550
            return false;
2551
        }
2552
2553
        return true;
2554
    }
2555
2556
    private function addAttachmentsFieldToForm(FormValidator $form)
2557
    {
2558
        $form->addButton('add_attachment', get_lang('AddAttachment'), 'plus');
2559
        $form->addHtml('<div id="container-attachments" style="display: none;">');
2560
        $form->addFile('attachment_file[]', get_lang('FilesAttachment'));
2561
        $form->addText('attachment_comment[]', get_lang('Description'), false);
2562
        $form->addHtml('</div>');
2563
2564
        $script = "$(function () {
2565
            var attachmentsTemplate = $('#container-attachments').html();
2566
            var \$btnAdd = $('[name=\"add_attachment\"]');
2567
            var \$reference = \$btnAdd.parents('.form-group');
2568
2569
            \$btnAdd.on('click', function (e) {
2570
                e.preventDefault();
2571
2572
                $(attachmentsTemplate).insertBefore(\$reference);
2573
            });
2574
        })";
2575
2576
        $form->addHtml("<script>$script</script>");
2577
    }
2578
2579
    private function processAttachments(
2580
        FormValidator $form,
2581
        User $user,
2582
        int $originId,
2583
        int $originType
2584
    ) {
2585
        $em = Database::getManager();
2586
        $fs = new Filesystem();
2587
2588
        $comments = $form->getSubmitValue('attachment_comment');
2589
2590
        foreach ($_FILES['attachment_file']['error'] as $i => $attachmentFileError) {
2591
            if ($attachmentFileError != UPLOAD_ERR_OK) {
2592
                continue;
2593
            }
2594
2595
            $_file = [
2596
                'name' => $_FILES['attachment_file']['name'][$i],
2597
                'type' => $_FILES['attachment_file']['type'][$i],
2598
                'tmp_name' => $_FILES['attachment_file']['tmp_name'][$i],
2599
                'size' => $_FILES['attachment_file']['size'][$i],
2600
            ];
2601
2602
            if (empty($_file['type'])) {
2603
                $_file['type'] = DocumentManager::file_get_mime_type($_file['name']);
2604
            }
2605
2606
            $newFileName = add_ext_on_mime(stripslashes($_file['name']), $_file['type']);
2607
2608
            if (!filter_extension($newFileName)) {
2609
                Display::addFlash(Display::return_message(get_lang('UplUnableToSaveFileFilteredExtension'), 'error'));
2610
                continue;
2611
            }
2612
2613
            $newFileName = uniqid();
2614
            $attachmentsDirectory = UserManager::getUserPathById($user->getId(), 'system').'portfolio_attachments/';
2615
2616
            if (!$fs->exists($attachmentsDirectory)) {
2617
                $fs->mkdir($attachmentsDirectory, api_get_permissions_for_new_directories());
2618
            }
2619
2620
            $attachmentFilename = $attachmentsDirectory.$newFileName;
2621
2622
            if (is_uploaded_file($_file['tmp_name'])) {
2623
                $moved = move_uploaded_file($_file['tmp_name'], $attachmentFilename);
2624
2625
                if (!$moved) {
2626
                    Display::addFlash(Display::return_message(get_lang('UplUnableToSaveFile'), 'error'));
2627
                    continue;
2628
                }
2629
            }
2630
2631
            $attachment = new PortfolioAttachment();
2632
            $attachment
2633
                ->setFilename($_file['name'])
2634
                ->setComment($comments[$i])
2635
                ->setPath($newFileName)
2636
                ->setOrigin($originId)
2637
                ->setOriginType($originType)
2638
                ->setSize($_file['size']);
2639
2640
            $em->persist($attachment);
2641
            $em->flush();
2642
        }
2643
    }
2644
2645
    private function itemBelongToOwner(Portfolio $item): bool
2646
    {
2647
        if ($item->getUser()->getId() != $this->owner->getId()) {
2648
            return false;
2649
        }
2650
2651
        return true;
2652
    }
2653
2654
    private function createFormTagFilter(bool $listByUser = false): FormValidator
2655
    {
2656
        $em = Database::getManager();
2657
        $tagTepo = $em->getRepository(Tag::class);
2658
2659
        $frmTagList = new FormValidator(
2660
            'frm_tag_list',
2661
            'get',
2662
            $this->baseUrl.($listByUser ? 'user='.$this->owner->getId() : ''),
2663
            '',
2664
            [],
2665
            FormValidator::LAYOUT_BOX
2666
        );
2667
2668
        $frmTagList->addDatePicker('date', get_lang('CreationDate'));
2669
2670
        /** @var SelectAjax $txtTags */
2671
        $txtTags = $frmTagList->addSelectAjax(
2672
            'tags',
2673
            get_lang('Tags'),
2674
            [],
2675
            [
2676
                'multiple' => 'multiple',
2677
                'url' => api_get_path(WEB_AJAX_PATH)."extra_field.ajax.php?a=search_tags&field_id=29&type=portfolio&byid=1",
2678
            ]
2679
        );
2680
        $selectedTags = $txtTags->getValue();
2681
2682
        if (!empty($selectedTags)) {
2683
            foreach ($tagTepo->findById($selectedTags) as $tag) {
2684
                $txtTags->addOption($tag->getTag(), $tag->getId());
2685
            }
2686
        }
2687
2688
        $frmTagList->addText('text', get_lang('Search'), false)->setIcon('search');
2689
        $frmTagList->applyFilter('text', 'trim');
2690
        $frmTagList->addHtml('<br>');
2691
        $frmTagList->addButtonFilter(get_lang('Filter'));
2692
2693
        if ($this->course) {
2694
            $frmTagList->addHidden('cidReq', $this->course->getCode());
2695
            $frmTagList->addHidden('id_session', $this->session ? $this->session->getId() : 0);
2696
            $frmTagList->addHidden('gidReq', 0);
2697
            $frmTagList->addHidden('gradebook', 0);
2698
            $frmTagList->addHidden('origin', '');
2699
            $frmTagList->addHidden('categoryId', 0);
2700
            $frmTagList->addHidden('subCategoryIds', '');
2701
2702
            if ($listByUser) {
2703
                $frmTagList->addHidden('user', $this->owner->getId());
2704
            }
2705
        }
2706
2707
        return $frmTagList;
2708
    }
2709
2710
    /**
2711
     * @throws Exception
2712
     */
2713
    private function createFormStudentFilter(bool $listByUser = false, bool $listHighlighted = false): FormValidator
2714
    {
2715
        $frmStudentList = new FormValidator(
2716
            'frm_student_list',
2717
            'get',
2718
            $this->baseUrl,
2719
            '',
2720
            [],
2721
            FormValidator::LAYOUT_BOX
2722
        );
2723
2724
        $urlParams = http_build_query(
2725
            [
2726
                'a' => 'search_user_by_course',
2727
                'course_id' => $this->course->getId(),
2728
                'session_id' => $this->session ? $this->session->getId() : 0,
2729
            ]
2730
        );
2731
2732
        /** @var SelectAjax $slctUser */
2733
        $slctUser = $frmStudentList->addSelectAjax(
2734
            'user',
2735
            get_lang('SelectLearnerPortfolio'),
2736
            [],
2737
            [
2738
                'url' => api_get_path(WEB_AJAX_PATH)."course.ajax.php?$urlParams",
2739
                'placeholder' => get_lang('SearchStudent'),
2740
                'formatResult' => SelectAjax::templateResultForUsersInCourse(),
2741
                'formatSelection' => SelectAjax::templateSelectionForUsersInCourse(),
2742
            ]
2743
        );
2744
2745
        if ($listByUser) {
2746
            $slctUser->addOption(
2747
                $this->owner->getCompleteName(),
2748
                $this->owner->getId(),
2749
                [
2750
                    'data-avatarurl' => UserManager::getUserPicture($this->owner->getId()),
2751
                    'data-username' => $this->owner->getUsername(),
2752
                ]
2753
            );
2754
2755
            $link = Display::url(
2756
                get_lang('BackToMainPortfolio'),
2757
                $this->baseUrl
2758
            );
2759
        } else {
2760
            $link = Display::url(
2761
                get_lang('SeeMyPortfolio'),
2762
                $this->baseUrl.http_build_query(['user' => api_get_user_id()])
2763
            );
2764
        }
2765
2766
        $frmStudentList->addHtml("<p>$link</p>");
2767
2768
        if ($listHighlighted) {
2769
            $link = Display::url(
2770
                get_lang('BackToMainPortfolio'),
2771
                $this->baseUrl
2772
            );
2773
        } else {
2774
            $link = Display::url(
2775
                get_lang('SeeHighlights'),
2776
                $this->baseUrl.http_build_query(['list_highlighted' => true])
2777
            );
2778
        }
2779
2780
        $frmStudentList->addHtml("<p>$link</p>");
2781
2782
        return $frmStudentList;
2783
    }
2784
2785
    private function getCategoriesForIndex(?int $currentUserId = null, ?int $parentId = null): array
2786
    {
2787
        $categoriesCriteria = [];
2788
        if (isset($currentUserId)) {
2789
            $categoriesCriteria['user'] = $this->owner;
2790
        }
2791
        if (!api_is_platform_admin() && $currentUserId !== $this->owner->getId()) {
2792
            $categoriesCriteria['isVisible'] = true;
2793
        }
2794
        if (isset($parentId)) {
2795
            $categoriesCriteria['parentId'] = $parentId;
2796
        }
2797
2798
        return $this->em
2799
            ->getRepository(PortfolioCategory::class)
2800
            ->findBy($categoriesCriteria);
2801
    }
2802
2803
    private function getHighlightedItems()
2804
    {
2805
        $queryBuilder = $this->em->createQueryBuilder();
2806
        $queryBuilder
2807
            ->select('pi')
2808
            ->from(Portfolio::class, 'pi')
2809
            ->where('pi.course = :course')
2810
            ->andWhere('pi.isHighlighted = TRUE')
2811
            ->setParameter('course', $this->course);
2812
2813
        if ($this->session) {
2814
            $queryBuilder->andWhere('pi.session = :session');
2815
            $queryBuilder->setParameter('session', $this->session);
2816
        } else {
2817
            $queryBuilder->andWhere('pi.session IS NULL');
2818
        }
2819
2820
        $visibilityCriteria = [Portfolio::VISIBILITY_VISIBLE];
2821
2822
        if (api_is_allowed_to_edit()) {
2823
            $visibilityCriteria[] = Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER;
2824
        }
2825
2826
        $queryBuilder
2827
            ->andWhere(
2828
                $queryBuilder->expr()->orX(
2829
                    'pi.user = :current_user',
2830
                    $queryBuilder->expr()->andX(
2831
                        'pi.user != :current_user',
2832
                        $queryBuilder->expr()->in('pi.visibility', $visibilityCriteria)
2833
                    )
2834
                )
2835
            )
2836
            ->setParameter('current_user', api_get_user_id());
2837
2838
        $queryBuilder->orderBy('pi.creationDate', 'DESC');
2839
2840
        return $queryBuilder->getQuery()->getResult();
2841
    }
2842
2843
    private function getItemsForIndex(
2844
        bool $listByUser = false,
2845
        FormValidator $frmFilterList = null
2846
    ) {
2847
        $currentUserId = api_get_user_id();
2848
2849
        if ($this->course) {
2850
            $queryBuilder = $this->em->createQueryBuilder();
2851
            $queryBuilder
2852
                ->select('pi')
2853
                ->from(Portfolio::class, 'pi')
2854
                ->where('pi.course = :course');
2855
2856
            $queryBuilder->setParameter('course', $this->course);
2857
2858
            if ($this->session) {
2859
                $queryBuilder->andWhere('pi.session = :session');
2860
                $queryBuilder->setParameter('session', $this->session);
2861
            } else {
2862
                $queryBuilder->andWhere('pi.session IS NULL');
2863
            }
2864
2865
            if ($frmFilterList && $frmFilterList->validate()) {
2866
                $values = $frmFilterList->exportValues();
2867
2868
                if (!empty($values['date'])) {
2869
                    $queryBuilder
2870
                        ->andWhere('pi.creationDate >= :date')
2871
                        ->setParameter(':date', api_get_utc_datetime($values['date'], false, true))
2872
                    ;
2873
                }
2874
2875
                if (!empty($values['tags'])) {
2876
                    $queryBuilder
2877
                        ->innerJoin(ExtraFieldRelTag::class, 'efrt', Join::WITH, 'efrt.itemId = pi.id')
2878
                        ->innerJoin(ExtraFieldEntity::class, 'ef', Join::WITH, 'ef.id = efrt.fieldId')
2879
                        ->andWhere('ef.extraFieldType = :efType')
2880
                        ->andWhere('ef.variable = :variable')
2881
                        ->andWhere('efrt.tagId IN (:tags)');
2882
2883
                    $queryBuilder->setParameter('efType', ExtraFieldEntity::PORTFOLIO_TYPE);
2884
                    $queryBuilder->setParameter('variable', 'tags');
2885
                    $queryBuilder->setParameter('tags', $values['tags']);
2886
                }
2887
2888
                if (!empty($values['text'])) {
2889
                    $queryBuilder->andWhere(
2890
                        $queryBuilder->expr()->orX(
2891
                            $queryBuilder->expr()->like('pi.title', ':text'),
2892
                            $queryBuilder->expr()->like('pi.content', ':text')
2893
                        )
2894
                    );
2895
2896
                    $queryBuilder->setParameter('text', '%'.$values['text'].'%');
2897
                }
2898
2899
                // Filters by category level 0
2900
                $searchCategories = [];
2901
                if (!empty($values['categoryId'])) {
2902
                    $searchCategories[] = $values['categoryId'];
2903
                    $subCategories = $this->getCategoriesForIndex(null, $values['categoryId']);
2904
                    if (count($subCategories) > 0) {
2905
                        foreach ($subCategories as $subCategory) {
2906
                            $searchCategories[] = $subCategory->getId();
2907
                        }
2908
                    }
2909
                    $queryBuilder->andWhere('pi.category IN('.implode(',', $searchCategories).')');
2910
                }
2911
2912
                // Filters by sub-category, don't show the selected values
2913
                $diff = [];
2914
                if (!empty($values['subCategoryIds']) && !('all' === $values['subCategoryIds'])) {
2915
                    $subCategoryIds = explode(',', $values['subCategoryIds']);
2916
                    $diff = array_diff($searchCategories, $subCategoryIds);
2917
                } else {
2918
                    if (trim($values['subCategoryIds']) === '') {
2919
                        $diff = $searchCategories;
2920
                    }
2921
                }
2922
                if (!empty($diff)) {
2923
                    unset($diff[0]);
2924
                    if (!empty($diff)) {
2925
                        $queryBuilder->andWhere('pi.category NOT IN('.implode(',', $diff).')');
2926
                    }
2927
                }
2928
            }
2929
2930
            if ($listByUser) {
2931
                $queryBuilder
2932
                    ->andWhere('pi.user = :user')
2933
                    ->setParameter('user', $this->owner);
2934
            }
2935
2936
            $visibilityCriteria = [Portfolio::VISIBILITY_VISIBLE];
2937
2938
            if (api_is_allowed_to_edit()) {
2939
                $visibilityCriteria[] = Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER;
2940
            }
2941
2942
            $queryBuilder
2943
                ->andWhere(
2944
                    $queryBuilder->expr()->orX(
2945
                        'pi.user = :current_user',
2946
                        $queryBuilder->expr()->andX(
2947
                            'pi.user != :current_user',
2948
                            $queryBuilder->expr()->in('pi.visibility', $visibilityCriteria)
2949
                        )
2950
                    )
2951
                )
2952
                ->setParameter('current_user', $currentUserId);
2953
2954
            $queryBuilder->orderBy('pi.creationDate', 'DESC');
2955
2956
            $items = $queryBuilder->getQuery()->getResult();
2957
        } else {
2958
            $itemsCriteria = [];
2959
            $itemsCriteria['category'] = null;
2960
            $itemsCriteria['user'] = $this->owner;
2961
2962
            if ($currentUserId !== $this->owner->getId()) {
2963
                $itemsCriteria['visibility'] = Portfolio::VISIBILITY_VISIBLE;
2964
            }
2965
2966
            $items = $this->em
2967
                ->getRepository(Portfolio::class)
2968
                ->findBy($itemsCriteria, ['creationDate' => 'DESC']);
2969
        }
2970
2971
        return $items;
2972
    }
2973
2974
    /**
2975
     * @throws \Doctrine\ORM\ORMException
2976
     * @throws \Doctrine\ORM\OptimisticLockException
2977
     * @throws \Doctrine\ORM\TransactionRequiredException
2978
     */
2979
    private function createCommentForm(Portfolio $item): string
2980
    {
2981
        $formAction = $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()]);
2982
2983
        $form = new FormValidator('frm_comment', 'post', $formAction);
2984
        $form->addHeader(get_lang('AddNewComment'));
2985
        $form->addHtmlEditor('content', get_lang('Comments'), true, false, ['ToolbarSet' => 'Minimal']);
2986
        $form->addHidden('item', $item->getId());
2987
        $form->addHidden('parent', 0);
2988
        $form->applyFilter('content', 'trim');
2989
2990
        $this->addAttachmentsFieldToForm($form);
2991
2992
        $form->addButtonSave(get_lang('Save'));
2993
2994
        if ($form->validate()) {
2995
            $values = $form->exportValues();
2996
2997
            $parentComment = $this->em->find(PortfolioComment::class, $values['parent']);
2998
2999
            $comment = new PortfolioComment();
3000
            $comment
3001
                ->setAuthor($this->owner)
3002
                ->setParent($parentComment)
3003
                ->setContent($values['content'])
3004
                ->setDate(api_get_utc_datetime(null, false, true))
3005
                ->setItem($item);
3006
3007
            $this->em->persist($comment);
3008
            $this->em->flush();
3009
3010
            $this->processAttachments(
3011
                $form,
3012
                $comment->getAuthor(),
3013
                $comment->getId(),
3014
                PortfolioAttachment::TYPE_COMMENT
3015
            );
3016
3017
            $hook = HookPortfolioItemCommented::create();
3018
            $hook->setEventData(['comment' => $comment]);
3019
            $hook->notifyItemCommented();
3020
3021
            Display::addFlash(
3022
                Display::return_message(get_lang('CommentAdded'), 'success')
3023
            );
3024
3025
            header("Location: $formAction");
3026
            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...
3027
        }
3028
3029
        return $form->returnForm();
3030
    }
3031
3032
    private function generateAttachmentList($post, bool $includeHeader = true): string
3033
    {
3034
        $attachmentsRepo = $this->em->getRepository(PortfolioAttachment::class);
3035
3036
        $postOwnerId = 0;
3037
3038
        if ($post instanceof Portfolio) {
3039
            $attachments = $attachmentsRepo->findFromItem($post);
3040
3041
            $postOwnerId = $post->getUser()->getId();
3042
        } elseif ($post instanceof PortfolioComment) {
3043
            $attachments = $attachmentsRepo->findFromComment($post);
3044
3045
            $postOwnerId = $post->getAuthor()->getId();
3046
        }
3047
3048
        if (empty($attachments)) {
3049
            return '';
3050
        }
3051
3052
        $currentUserId = api_get_user_id();
3053
3054
        $listItems = '<ul class="fa-ul">';
3055
3056
        $deleteIcon = Display::return_icon(
3057
            'delete.png',
3058
            get_lang('DeleteAttachment'),
3059
            ['style' => 'display: inline-block'],
3060
            ICON_SIZE_TINY
3061
        );
3062
        $deleteAttrs = ['class' => 'btn-portfolio-delete'];
3063
3064
        /** @var PortfolioAttachment $attachment */
3065
        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...
3066
            $downloadParams = http_build_query(['action' => 'download_attachment', 'file' => $attachment->getPath()]);
3067
            $deleteParams = http_build_query(['action' => 'delete_attachment', 'file' => $attachment->getPath()]);
3068
3069
            $listItems .= '<li>'
3070
                .'<span class="fa-li fa fa-paperclip" aria-hidden="true"></span>'
3071
                .Display::url(
3072
                    Security::remove_XSS($attachment->getFilename()),
3073
                    $this->baseUrl.$downloadParams
3074
                );
3075
3076
            if ($currentUserId === $postOwnerId) {
3077
                $listItems .= PHP_EOL.Display::url($deleteIcon, $this->baseUrl.$deleteParams, $deleteAttrs);
3078
            }
3079
3080
            if ($attachment->getComment()) {
3081
                $listItems .= '<p class="text-muted">'.Security::remove_XSS($attachment->getComment()).'</p>';
3082
            }
3083
3084
            $listItems .= '</li>';
3085
        }
3086
3087
        $listItems .= '</ul>';
3088
3089
        if ($includeHeader) {
3090
            $listItems = '<h1 class="h4">'.get_lang('AttachmentFiles').'</h1>'
3091
                .$listItems;
3092
        }
3093
3094
        return $listItems;
3095
    }
3096
3097
    private function generateItemContent(Portfolio $item): string
3098
    {
3099
        $originId = $item->getOrigin();
3100
3101
        if (empty($originId)) {
3102
            return $item->getContent();
3103
        }
3104
3105
        $em = Database::getManager();
3106
3107
        $originContent = '';
3108
        $originContentFooter = '';
3109
3110
        if (Portfolio::TYPE_ITEM === $item->getOriginType()) {
3111
            $origin = $em->find(Portfolio::class, $item->getOrigin());
3112
3113
            if ($origin) {
3114
                $originContent = $origin->getContent();
3115
                $originContentFooter = vsprintf(
3116
                    get_lang('OriginallyPublishedAsXTitleByYUser'),
3117
                    [
3118
                        "<cite>{$origin->getTitle(true)}</cite>",
3119
                        $origin->getUser()->getCompleteName(),
3120
                    ]
3121
                );
3122
            }
3123
        } elseif (Portfolio::TYPE_COMMENT === $item->getOriginType()) {
3124
            $origin = $em->find(PortfolioComment::class, $item->getOrigin());
3125
3126
            if ($origin) {
3127
                $originContent = $origin->getContent();
3128
                $originContentFooter = vsprintf(
3129
                    get_lang('OriginallyCommentedByXUserInYItem'),
3130
                    [
3131
                        $origin->getAuthor()->getCompleteName(),
3132
                        "<cite>{$origin->getItem()->getTitle(true)}</cite>",
3133
                    ]
3134
                );
3135
            }
3136
        }
3137
3138
        if ($originContent) {
3139
            return "<figure>
3140
                    <blockquote>$originContent</blockquote>
3141
                    <figcaption style=\"margin-bottom: 10px;\">$originContentFooter</figcaption>
3142
                </figure>
3143
                <div class=\"clearfix\">".Security::remove_XSS($item->getContent()).'</div>'
3144
            ;
3145
        }
3146
3147
        return Security::remove_XSS($item->getContent());
3148
    }
3149
3150
    private function getItemsInHtmlFormatted(array $items): array
3151
    {
3152
        $itemsHtml = [];
3153
3154
        /** @var Portfolio $item */
3155
        foreach ($items as $item) {
3156
            $itemCourse = $item->getCourse();
3157
            $itemSession = $item->getSession();
3158
3159
            $creationDate = api_convert_and_format_date($item->getCreationDate());
3160
            $updateDate = api_convert_and_format_date($item->getUpdateDate());
3161
3162
            $metadata = '<ul class="list-unstyled text-muted">';
3163
3164
            if ($itemSession) {
3165
                $metadata .= '<li>'.get_lang('Course').': '.$itemSession->getName().' ('
3166
                    .$itemCourse->getTitle().') </li>';
3167
            } elseif ($itemCourse) {
3168
                $metadata .= '<li>'.get_lang('Course').': '.$itemCourse->getTitle().'</li>';
3169
            }
3170
3171
            $metadata .= '<li>'.sprintf(get_lang('CreationDateXDate'), $creationDate).'</li>';
3172
3173
            if ($itemCourse) {
3174
                $propertyInfo = api_get_item_property_info(
3175
                    $itemCourse->getId(),
3176
                    TOOL_PORTFOLIO,
3177
                    $item->getId(),
3178
                    $itemSession ? $itemSession->getId() : 0
3179
                );
3180
3181
                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...
3182
                    $metadata .= '<li>'
3183
                        .sprintf(
3184
                            get_lang('UpdatedOnDateXByUserY'),
3185
                            api_convert_and_format_date($propertyInfo['lastedit_date'], DATE_TIME_FORMAT_LONG),
3186
                            api_get_user_entity($propertyInfo['lastedit_user_id'])->getCompleteName()
3187
                        )
3188
                        .'</li>';
3189
                }
3190
            } else {
3191
                $metadata .= '<li>'.sprintf(get_lang('UpdateDateXDate'), $updateDate).'</li>';
3192
            }
3193
3194
            if ($item->getCategory()) {
3195
                $metadata .= '<li>'.sprintf(get_lang('CategoryXName'), $item->getCategory()->getTitle()).'</li>';
3196
            }
3197
3198
            $metadata .= '</ul>';
3199
3200
            $itemContent = $this->generateItemContent($item);
3201
3202
            $itemsHtml[] = Display::panel($itemContent, Security::remove_XSS($item->getTitle()), '', 'info', $metadata);
3203
        }
3204
3205
        return $itemsHtml;
3206
    }
3207
3208
    private function getCommentsInHtmlFormatted(array $comments): array
3209
    {
3210
        $commentsHtml = [];
3211
3212
        /** @var PortfolioComment $comment */
3213
        foreach ($comments as $comment) {
3214
            $item = $comment->getItem();
3215
            $date = api_convert_and_format_date($comment->getDate());
3216
3217
            $metadata = '<ul class="list-unstyled text-muted">';
3218
            $metadata .= '<li>'.sprintf(get_lang('DateXDate'), $date).'</li>';
3219
            $metadata .= '<li>'.sprintf(get_lang('PortfolioItemTitleXName'), Security::remove_XSS($item->getTitle()))
3220
                .'</li>';
3221
            $metadata .= '</ul>';
3222
3223
            $commentsHtml[] = Display::panel(
3224
                Security::remove_XSS($comment->getContent()),
3225
                '',
3226
                '',
3227
                'default',
3228
                $metadata
3229
            );
3230
        }
3231
3232
        return $commentsHtml;
3233
    }
3234
3235
    private function fixImagesSourcesToHtml(string $htmlContent): string
3236
    {
3237
        $doc = new DOMDocument();
3238
        @$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

3238
        /** @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...
3239
3240
        $elements = $doc->getElementsByTagName('img');
3241
3242
        if (empty($elements->length)) {
3243
            return $htmlContent;
3244
        }
3245
3246
        $webCoursePath = api_get_path(WEB_COURSE_PATH);
3247
        $webUploadPath = api_get_path(WEB_UPLOAD_PATH);
3248
3249
        /** @var \DOMElement $element */
3250
        foreach ($elements as $element) {
3251
            $src = trim($element->getAttribute('src'));
3252
3253
            if (strpos($src, 'http') === 0) {
3254
                continue;
3255
            }
3256
3257
            if (strpos($src, '/app/upload/') === 0) {
3258
                $element->setAttribute(
3259
                    'src',
3260
                    preg_replace('/\/app/upload\//', $webUploadPath, $src, 1)
3261
                );
3262
3263
                continue;
3264
            }
3265
3266
            if (strpos($src, '/courses/') === 0) {
3267
                $element->setAttribute(
3268
                    'src',
3269
                    preg_replace('/\/courses\//', $webCoursePath, $src, 1)
3270
                );
3271
3272
                continue;
3273
            }
3274
        }
3275
3276
        return $doc->saveHTML();
3277
    }
3278
3279
    private function formatZipIndexFile(HTML_Table $tblItems, HTML_Table $tblComments): string
3280
    {
3281
        $htmlContent = Display::page_header($this->owner->getCompleteNameWithUsername());
3282
        $htmlContent .= Display::page_subheader2(get_lang('PortfolioItems'));
3283
3284
        $htmlContent .= $tblItems->getRowCount() > 0
3285
            ? $tblItems->toHtml()
3286
            : Display::return_message(get_lang('NoItemsInYourPortfolio'), 'warning');
3287
3288
        $htmlContent .= Display::page_subheader2(get_lang('PortfolioCommentsMade'));
3289
3290
        $htmlContent .= $tblComments->getRowCount() > 0
3291
            ? $tblComments->toHtml()
3292
            : Display::return_message(get_lang('YouHaveNotCommented'), 'warning');
3293
3294
        $webAssetsPath = api_get_path(WEB_PUBLIC_PATH).'assets/';
3295
3296
        $doc = new DOMDocument();
3297
        @$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

3297
        /** @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...
3298
3299
        $stylesheet1 = $doc->createElement('link');
3300
        $stylesheet1->setAttribute('rel', 'stylesheet');
3301
        $stylesheet1->setAttribute('href', $webAssetsPath.'bootstrap/dist/css/bootstrap.min.css');
3302
        $stylesheet2 = $doc->createElement('link');
3303
        $stylesheet2->setAttribute('rel', 'stylesheet');
3304
        $stylesheet2->setAttribute('href', $webAssetsPath.'fontawesome/css/font-awesome.min.css');
3305
        $stylesheet3 = $doc->createElement('link');
3306
        $stylesheet3->setAttribute('rel', 'stylesheet');
3307
        $stylesheet3->setAttribute('href', ChamiloApi::getEditorDocStylePath());
3308
3309
        $head = $doc->createElement('head');
3310
        $head->appendChild($stylesheet1);
3311
        $head->appendChild($stylesheet2);
3312
        $head->appendChild($stylesheet3);
3313
3314
        $doc->documentElement->insertBefore(
3315
            $head,
3316
            $doc->getElementsByTagName('body')->item(0)
3317
        );
3318
3319
        return $doc->saveHTML();
3320
    }
3321
3322
    /**
3323
     * It parsers a title for a variable in lang.
3324
     *
3325
     * @param $defaultDisplayText
3326
     *
3327
     * @return string
3328
     */
3329
    private function getLanguageVariable($defaultDisplayText)
3330
    {
3331
        $variableLanguage = api_replace_dangerous_char(strtolower($defaultDisplayText));
3332
        $variableLanguage = preg_replace('/[^A-Za-z0-9\_]/', '', $variableLanguage); // Removes special chars except underscore.
3333
        if (is_numeric($variableLanguage[0])) {
3334
            $variableLanguage = '_'.$variableLanguage;
3335
        }
3336
        $variableLanguage = api_underscore_to_camel_case($variableLanguage);
3337
3338
        return $variableLanguage;
3339
    }
3340
3341
    /**
3342
     * It translates the text as parameter.
3343
     *
3344
     * @param $defaultDisplayText
3345
     *
3346
     * @return mixed
3347
     */
3348
    private function translateDisplayName($defaultDisplayText)
3349
    {
3350
        $variableLanguage = $this->getLanguageVariable($defaultDisplayText);
3351
3352
        return isset($GLOBALS[$variableLanguage]) ? $GLOBALS[$variableLanguage] : $defaultDisplayText;
3353
    }
3354
}
3355