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

PortfolioController::view()   D

Complexity

Conditions 15
Paths 9

Size

Total Lines 199
Code Lines 119

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 119
c 0
b 0
f 0
dl 0
loc 199
rs 4.7333
cc 15
nc 9
nop 1

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Component\Utils\ChamiloApi;
6
use Chamilo\CoreBundle\Entity\Course as CourseEntity;
7
use Chamilo\CoreBundle\Entity\ExtraField as ExtraFieldEntity;
8
use Chamilo\CoreBundle\Entity\ExtraFieldRelTag;
9
use Chamilo\CoreBundle\Entity\Portfolio;
10
use Chamilo\CoreBundle\Entity\PortfolioAttachment;
11
use Chamilo\CoreBundle\Entity\PortfolioCategory;
12
use Chamilo\CoreBundle\Entity\PortfolioComment;
13
use Chamilo\CoreBundle\Entity\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
891
        if ($httpRequest->query->has('user')) {
892
            $this->owner = api_get_user_entity($httpRequest->query->getInt('user'));
893
894
            if (empty($this->owner)) {
895
                api_not_allowed(true);
896
            }
897
898
            $listByUser = true;
899
        }
900
901
        $currentUserId = api_get_user_id();
902
903
        $actions = [];
904
905
        if (api_is_platform_admin()) {
906
            $actions[] = Display::url(
907
                Display::return_icon('add.png', get_lang('Add'), [], ICON_SIZE_MEDIUM),
908
                $this->baseUrl.'action=add_item'
909
            );
910
            $actions[] = Display::url(
911
                Display::return_icon('folder.png', get_lang('AddCategory'), [], ICON_SIZE_MEDIUM),
912
                $this->baseUrl.'action=list_categories'
913
            );
914
            $actions[] = Display::url(
915
                Display::return_icon('waiting_list.png', get_lang('PortfolioDetails'), [], ICON_SIZE_MEDIUM),
916
                $this->baseUrl.'action=details'
917
            );
918
        } else {
919
            if ($currentUserId == $this->owner->getId()) {
920
                $actions[] = Display::url(
921
                    Display::return_icon('add.png', get_lang('Add'), [], ICON_SIZE_MEDIUM),
922
                    $this->baseUrl.'action=add_item'
923
                );
924
                $actions[] = Display::url(
925
                    Display::return_icon('waiting_list.png', get_lang('PortfolioDetails'), [], ICON_SIZE_MEDIUM),
926
                    $this->baseUrl.'action=details'
927
                );
928
            } else {
929
                $actions[] = Display::url(
930
                    Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
931
                    $this->baseUrl
932
                );
933
            }
934
        }
935
936
        $frmStudentList = null;
937
        $frmTagList = null;
938
939
        $categories = [];
940
        $portfolio = [];
941
        if ($this->course) {
942
            $frmTagList = $this->createFormTagFilter($listByUser);
943
            $frmStudentList = $this->createFormStudentFilter($listByUser);
944
            $frmStudentList->setDefaults(['user' => $this->owner->getId()]);
945
            // it translates the category title with the current user language
946
            $categories = $this->getCategoriesForIndex(null, 0);
947
            if (count($categories) > 0) {
948
                foreach ($categories as &$category) {
949
                    $translated = $this->translateDisplayName($category->getTitle());
950
                    $category->setTitle($translated);
951
                }
952
            }
953
        } else {
954
            // it displays the list in Network Social for the current user
955
            $portfolio = $this->getCategoriesForIndex();
956
        }
957
958
        $items = $this->getItemsForIndex($listByUser, $frmTagList);
959
        // it gets and translate the sub-categories
960
        $categoryId = $httpRequest->query->getInt('categoryId');
961
        $subCategoryIdsReq = isset($_REQUEST['subCategoryIds']) ? Security::remove_XSS($_REQUEST['subCategoryIds']) : '';
962
        $subCategoryIds = $subCategoryIdsReq;
963
        if ('all' !== $subCategoryIdsReq) {
964
            $subCategoryIds = !empty($subCategoryIdsReq) ? explode(',', $subCategoryIdsReq) : [];
965
        }
966
        $subcategories = [];
967
        if ($categoryId > 0) {
968
            $subcategories = $this->getCategoriesForIndex(null, $categoryId);
969
            if (count($subcategories) > 0) {
970
                foreach ($subcategories as &$subcategory) {
971
                    $translated = $this->translateDisplayName($subcategory->getTitle());
972
                    $subcategory->setTitle($translated);
973
                }
974
            }
975
        }
976
977
        $template = new Template(null, false, false, false, false, false, false);
978
        $template->assign('user', $this->owner);
979
        $template->assign('course', $this->course);
980
        $template->assign('session', $this->session);
981
        $template->assign('portfolio', $portfolio);
982
        $template->assign('categories', $categories);
983
        $template->assign('uncategorized_items', $items);
984
        $template->assign('frm_student_list', $this->course ? $frmStudentList->returnForm() : '');
985
        $template->assign('frm_tag_list', $this->course ? $frmTagList->returnForm() : '');
986
        $template->assign('category_id', $categoryId);
987
        $template->assign('subcategories', $subcategories);
988
        $template->assign('subcategory_ids', $subCategoryIds);
989
990
        $js = '<script>
991
            $(function() {
992
                $(".category-filters").bind("click", function() {
993
                    var categoryId = parseInt($(this).find("input[type=\'radio\']").val());
994
                    $("input[name=\'categoryId\']").val(categoryId);
995
                    $("input[name=\'subCategoryIds\']").val("all");
996
                    $("#frm_tag_list_submit").trigger("click");
997
                });
998
                $(".subcategory-filters").bind("click", function() {
999
                    var checkedVals = $(".subcategory-filters:checkbox:checked").map(function() {
1000
                        return this.value;
1001
                    }).get();
1002
                    $("input[name=\'subCategoryIds\']").val(checkedVals.join(","));
1003
                    $("#frm_tag_list_submit").trigger("click");
1004
                });
1005
            });
1006
        </script>';
1007
        $template->assign('js_script', $js);
1008
        $layout = $template->get_template('portfolio/list.html.twig');
1009
1010
        Display::addFlash(
1011
            Display::return_message(get_lang('PortfolioPostAddHelp'), 'info', false)
1012
        );
1013
1014
        $content = $template->fetch($layout);
1015
1016
        $this->renderView($content, get_lang('Portfolio'), $actions);
1017
    }
1018
1019
    /**
1020
     * @throws \Doctrine\ORM\ORMException
1021
     * @throws \Doctrine\ORM\OptimisticLockException
1022
     * @throws \Doctrine\ORM\TransactionRequiredException
1023
     */
1024
    public function view(Portfolio $item)
1025
    {
1026
        global $interbreadcrumb;
1027
1028
        if (!$this->itemBelongToOwner($item)) {
1029
            if ($item->getVisibility() === Portfolio::VISIBILITY_HIDDEN
1030
                || ($item->getVisibility() === Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER && !api_is_allowed_to_edit())
1031
            ) {
1032
                api_not_allowed(true);
1033
            }
1034
        }
1035
1036
        HookPortfolioItemViewed::create()
1037
            ->setEventData(['portfolio' => $item])
1038
            ->notifyItemViewed()
1039
        ;
1040
1041
        $itemCourse = $item->getCourse();
1042
        $itemSession = $item->getSession();
1043
1044
        $form = $this->createCommentForm($item);
1045
1046
        $commentsRepo = $this->em->getRepository(PortfolioComment::class);
1047
1048
        $query = $commentsRepo->createQueryBuilder('comment')
1049
            ->where('comment.item = :item')
1050
            ->orderBy('comment.root, comment.lft', 'ASC')
1051
            ->setParameter('item', $item)
1052
            ->getQuery();
1053
1054
        $clockIcon = Display::returnFontAwesomeIcon('clock-o', '', true);
1055
1056
        $commentsHtml = $commentsRepo->buildTree(
1057
            $query->getArrayResult(),
1058
            [
1059
                'decorate' => true,
1060
                'rootOpen' => '<ul class="media-list">',
1061
                'rootClose' => '</ul>',
1062
                'childOpen' => function ($node) use ($commentsRepo) {
1063
                    /** @var PortfolioComment $comment */
1064
                    $comment = $commentsRepo->find($node['id']);
1065
                    $author = $comment->getAuthor();
1066
1067
                    $userPicture = UserManager::getUserPicture(
1068
                        $comment->getAuthor()->getId(),
1069
                        USER_IMAGE_SIZE_SMALL,
1070
                        null,
1071
                        [
1072
                            'picture_uri' => $author->getPictureUri(),
1073
                            'email' => $author->getEmail(),
1074
                        ]
1075
                    );
1076
1077
                    return '<li class="media" id="comment-'.$node['id'].'">
1078
                        <div class="media-left"><img class="media-object thumbnail" src="'.$userPicture.'" alt="'
1079
                        .$author->getCompleteName().'"></div>
1080
                        <div class="media-body">';
1081
                },
1082
                'childClose' => '</div></li>',
1083
                'nodeDecorator' => function ($node) use ($commentsRepo, $clockIcon, $item) {
1084
                    /** @var PortfolioComment $comment */
1085
                    $comment = $commentsRepo->find($node['id']);
1086
1087
                    $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...
1088
                        Display::return_icon('discuss.png', get_lang('ReplyToThisComment')),
1089
                        '#',
1090
                        [
1091
                            'data-comment' => htmlspecialchars(
1092
                                json_encode(['id' => $comment->getId()])
1093
                            ),
1094
                            'role' => 'button',
1095
                            'class' => 'btn-reply-to',
1096
                        ]
1097
                    );
1098
                    $commentActions[] = Display::url(
1099
                        Display::return_icon('copy.png', get_lang('CopyToMyPortfolio')),
1100
                        $this->baseUrl.http_build_query(
1101
                            [
1102
                                'action' => 'copy',
1103
                                'copy' => 'comment',
1104
                                'id' => $comment->getId(),
1105
                            ]
1106
                        )
1107
                    );
1108
1109
                    $isAllowedToEdit = api_is_allowed_to_edit();
1110
1111
                    if ($isAllowedToEdit) {
1112
                        $commentActions[] = Display::url(
1113
                            Display::return_icon('copy.png', get_lang('CopyToStudentPortfolio')),
1114
                            $this->baseUrl.http_build_query(
1115
                                [
1116
                                    'action' => 'teacher_copy',
1117
                                    'copy' => 'comment',
1118
                                    'id' => $comment->getId(),
1119
                                ]
1120
                            )
1121
                        );
1122
1123
                        if ($comment->isImportant()) {
1124
                            $commentActions[] = Display::url(
1125
                                Display::return_icon('drawing-pin.png', get_lang('UnmarkCommentAsImportant')),
1126
                                $this->baseUrl.http_build_query(
1127
                                    [
1128
                                        'action' => 'mark_important',
1129
                                        'item' => $item->getId(),
1130
                                        'id' => $comment->getId(),
1131
                                    ]
1132
                                )
1133
                            );
1134
                        } else {
1135
                            $commentActions[] = Display::url(
1136
                                Display::return_icon('drawing-pin.png', get_lang('MarkCommentAsImportant')),
1137
                                $this->baseUrl.http_build_query(
1138
                                    [
1139
                                        'action' => 'mark_important',
1140
                                        'item' => $item->getId(),
1141
                                        'id' => $comment->getId(),
1142
                                    ]
1143
                                )
1144
                            );
1145
                        }
1146
1147
                        if ($this->course && '1' === api_get_course_setting('qualify_portfolio_comment')) {
1148
                            $commentActions[] = Display::url(
1149
                                Display::return_icon('quiz.png', get_lang('QualifyThisPortfolioComment')),
1150
                                $this->baseUrl.http_build_query(
1151
                                    [
1152
                                        'action' => 'qualify',
1153
                                        'comment' => $comment->getId(),
1154
                                    ]
1155
                                )
1156
                            );
1157
                        }
1158
                    }
1159
1160
                    $nodeHtml = '<p class="media-heading h4">'.PHP_EOL
1161
                        .$comment->getAuthor()->getCompleteName().PHP_EOL.'<small>'.$clockIcon.PHP_EOL
1162
                        .Display::dateToStringAgoAndLongDate($comment->getDate()).'</small>'.PHP_EOL;
1163
1164
                    if ($comment->isImportant()
1165
                        && ($this->itemBelongToOwner($comment->getItem()) || $isAllowedToEdit)
1166
                    ) {
1167
                        $nodeHtml .= '<span class="label label-warning origin-style">'
1168
                            .get_lang('CommentMarkedAsImportant')
1169
                            .'</span>'.PHP_EOL;
1170
                    }
1171
1172
                    $nodeHtml .= '</p>'.PHP_EOL
1173
                        .'<div class="pull-right">'.implode(PHP_EOL, $commentActions).'</div>'
1174
                        .Security::remove_XSS($comment->getContent())
1175
                        .PHP_EOL;
1176
1177
                    $nodeHtml .= $this->generateAttachmentList($comment);
1178
1179
                    return $nodeHtml;
1180
                },
1181
            ]
1182
        );
1183
1184
        $template = new Template(null, false, false, false, false, false, false);
1185
        $template->assign('baseurl', $this->baseUrl);
1186
        $template->assign('item', $item);
1187
        $template->assign('item_content', $this->generateItemContent($item));
1188
        $template->assign('comments', $commentsHtml);
1189
        $template->assign('form', $form);
1190
        $template->assign('attachment_list', $this->generateAttachmentList($item));
1191
1192
        if ($itemCourse) {
1193
            $propertyInfo = api_get_item_property_info(
1194
                $itemCourse->getId(),
1195
                TOOL_PORTFOLIO,
1196
                $item->getId(),
1197
                $itemSession ? $itemSession->getId() : 0
1198
            );
1199
1200
            if ($propertyInfo) {
1201
                $template->assign(
1202
                    'last_edit',
1203
                    [
1204
                        'date' => $propertyInfo['lastedit_date'],
1205
                        'user' => api_get_user_entity($propertyInfo['lastedit_user_id'])->getCompleteName(),
1206
                    ]
1207
                );
1208
            }
1209
        }
1210
1211
        $layout = $template->get_template('portfolio/view.html.twig');
1212
        $content = $template->fetch($layout);
1213
1214
        $interbreadcrumb[] = ['name' => get_lang('Portfolio'), 'url' => $this->baseUrl];
1215
1216
        $actions = [];
1217
        $actions[] = Display::url(
1218
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
1219
            $this->baseUrl
1220
        );
1221
1222
        $this->renderView($content, $item->getTitle(true), $actions, false);
1223
    }
1224
1225
    /**
1226
     * @throws \Doctrine\ORM\ORMException
1227
     * @throws \Doctrine\ORM\OptimisticLockException
1228
     */
1229
    public function copyItem(Portfolio $originItem)
1230
    {
1231
        $currentTime = api_get_utc_datetime(null, false, true);
1232
1233
        $portfolio = new Portfolio();
1234
        $portfolio
1235
            ->setVisibility(Portfolio::VISIBILITY_HIDDEN)
1236
            ->setTitle(
1237
                sprintf(get_lang('PortfolioItemFromXUser'), $originItem->getUser()->getCompleteName())
1238
            )
1239
            ->setContent('')
1240
            ->setUser($this->owner)
1241
            ->setOrigin($originItem->getId())
1242
            ->setOriginType(Portfolio::TYPE_ITEM)
1243
            ->setCourse($this->course)
1244
            ->setSession($this->session)
1245
            ->setCreationDate($currentTime)
1246
            ->setUpdateDate($currentTime);
1247
1248
        $this->em->persist($portfolio);
1249
        $this->em->flush();
1250
1251
        Display::addFlash(
1252
            Display::return_message(get_lang('PortfolioItemAdded'), 'success')
1253
        );
1254
1255
        header("Location: $this->baseUrl".http_build_query(['action' => 'edit_item', 'id' => $portfolio->getId()]));
1256
        exit;
1257
    }
1258
1259
    /**
1260
     * @throws \Doctrine\ORM\ORMException
1261
     * @throws \Doctrine\ORM\OptimisticLockException
1262
     */
1263
    public function copyComment(PortfolioComment $originComment)
1264
    {
1265
        $currentTime = api_get_utc_datetime(null, false, true);
1266
1267
        $portfolio = new Portfolio();
1268
        $portfolio
1269
            ->setVisibility(Portfolio::VISIBILITY_HIDDEN)
1270
            ->setTitle(
1271
                sprintf(get_lang('PortfolioCommentFromXUser'), $originComment->getAuthor()->getCompleteName())
1272
            )
1273
            ->setContent('')
1274
            ->setUser($this->owner)
1275
            ->setOrigin($originComment->getId())
1276
            ->setOriginType(Portfolio::TYPE_COMMENT)
1277
            ->setCourse($this->course)
1278
            ->setSession($this->session)
1279
            ->setCreationDate($currentTime)
1280
            ->setUpdateDate($currentTime);
1281
1282
        $this->em->persist($portfolio);
1283
        $this->em->flush();
1284
1285
        Display::addFlash(
1286
            Display::return_message(get_lang('PortfolioItemAdded'), 'success')
1287
        );
1288
1289
        header("Location: $this->baseUrl".http_build_query(['action' => 'edit_item', 'id' => $portfolio->getId()]));
1290
        exit;
1291
    }
1292
1293
    /**
1294
     * @throws \Doctrine\ORM\ORMException
1295
     * @throws \Doctrine\ORM\OptimisticLockException
1296
     * @throws \Exception
1297
     */
1298
    public function teacherCopyItem(Portfolio $originItem)
1299
    {
1300
        $actionParams = http_build_query(['action' => 'teacher_copy', 'copy' => 'item', 'id' => $originItem->getId()]);
1301
1302
        $form = new FormValidator('teacher_copy_portfolio', 'post', $this->baseUrl.$actionParams);
1303
1304
        if (api_get_configuration_value('save_titles_as_html')) {
1305
            $form->addHtmlEditor('title', get_lang('Title'), true, false, ['ToolbarSet' => 'TitleAsHtml']);
1306
        } else {
1307
            $form->addText('title', get_lang('Title'));
1308
            $form->applyFilter('title', 'trim');
1309
        }
1310
1311
        $form->addLabel(
1312
            sprintf(get_lang('PortfolioItemFromXUser'), $originItem->getUser()->getCompleteName()),
1313
            Display::panel(
1314
                Security::remove_XSS($originItem->getContent())
1315
            )
1316
        );
1317
        $form->addHtmlEditor('content', get_lang('Content'), true, false, ['ToolbarSet' => 'NotebookStudent']);
1318
1319
        $urlParams = http_build_query(
1320
            [
1321
                'a' => 'search_user_by_course',
1322
                'course_id' => $this->course->getId(),
1323
                'session_id' => $this->session ? $this->session->getId() : 0,
1324
            ]
1325
        );
1326
        $form->addSelectAjax(
1327
            'students',
1328
            get_lang('Students'),
1329
            [],
1330
            [
1331
                'url' => api_get_path(WEB_AJAX_PATH)."course.ajax.php?$urlParams",
1332
                'multiple' => true,
1333
            ]
1334
        );
1335
        $form->addRule('students', get_lang('ThisFieldIsRequired'), 'required');
1336
        $form->addButtonCreate(get_lang('Save'));
1337
1338
        $toolName = get_lang('CopyToStudentPortfolio');
1339
        $content = $form->returnForm();
1340
1341
        if ($form->validate()) {
1342
            $values = $form->exportValues();
1343
1344
            $currentTime = api_get_utc_datetime(null, false, true);
1345
1346
            foreach ($values['students'] as $studentId) {
1347
                $owner = api_get_user_entity($studentId);
1348
1349
                $portfolio = new Portfolio();
1350
                $portfolio
1351
                    ->setVisibility(Portfolio::VISIBILITY_HIDDEN)
1352
                    ->setTitle($values['title'])
1353
                    ->setContent($values['content'])
1354
                    ->setUser($owner)
1355
                    ->setOrigin($originItem->getId())
1356
                    ->setOriginType(Portfolio::TYPE_ITEM)
1357
                    ->setCourse($this->course)
1358
                    ->setSession($this->session)
1359
                    ->setCreationDate($currentTime)
1360
                    ->setUpdateDate($currentTime);
1361
1362
                $this->em->persist($portfolio);
1363
            }
1364
1365
            $this->em->flush();
1366
1367
            Display::addFlash(
1368
                Display::return_message(get_lang('PortfolioItemAddedToStudents'), 'success')
1369
            );
1370
1371
            header("Location: $this->baseUrl");
1372
            exit;
1373
        }
1374
1375
        $this->renderView($content, $toolName);
1376
    }
1377
1378
    /**
1379
     * @throws \Doctrine\ORM\ORMException
1380
     * @throws \Doctrine\ORM\OptimisticLockException
1381
     * @throws \Exception
1382
     */
1383
    public function teacherCopyComment(PortfolioComment $originComment)
1384
    {
1385
        $actionParams = http_build_query(
1386
            [
1387
                'action' => 'teacher_copy',
1388
                'copy' => 'comment',
1389
                'id' => $originComment->getId(),
1390
            ]
1391
        );
1392
1393
        $form = new FormValidator('teacher_copy_portfolio', 'post', $this->baseUrl.$actionParams);
1394
1395
        if (api_get_configuration_value('save_titles_as_html')) {
1396
            $form->addHtmlEditor('title', get_lang('Title'), true, false, ['ToolbarSet' => 'TitleAsHtml']);
1397
        } else {
1398
            $form->addText('title', get_lang('Title'));
1399
            $form->applyFilter('title', 'trim');
1400
        }
1401
1402
        $form->addLabel(
1403
            sprintf(get_lang('PortfolioCommentFromXUser'), $originComment->getAuthor()->getCompleteName()),
1404
            Display::panel(
1405
                Security::remove_XSS($originComment->getContent())
1406
            )
1407
        );
1408
        $form->addHtmlEditor('content', get_lang('Content'), true, false, ['ToolbarSet' => 'NotebookStudent']);
1409
1410
        $urlParams = http_build_query(
1411
            [
1412
                'a' => 'search_user_by_course',
1413
                'course_id' => $this->course->getId(),
1414
                'session_id' => $this->session ? $this->session->getId() : 0,
1415
            ]
1416
        );
1417
        $form->addSelectAjax(
1418
            'students',
1419
            get_lang('Students'),
1420
            [],
1421
            [
1422
                'url' => api_get_path(WEB_AJAX_PATH)."course.ajax.php?$urlParams",
1423
                'multiple' => true,
1424
            ]
1425
        );
1426
        $form->addRule('students', get_lang('ThisFieldIsRequired'), 'required');
1427
        $form->addButtonCreate(get_lang('Save'));
1428
1429
        $toolName = get_lang('CopyToStudentPortfolio');
1430
        $content = $form->returnForm();
1431
1432
        if ($form->validate()) {
1433
            $values = $form->exportValues();
1434
1435
            $currentTime = api_get_utc_datetime(null, false, true);
1436
1437
            foreach ($values['students'] as $studentId) {
1438
                $owner = api_get_user_entity($studentId);
1439
1440
                $portfolio = new Portfolio();
1441
                $portfolio
1442
                    ->setVisibility(Portfolio::VISIBILITY_HIDDEN)
1443
                    ->setTitle($values['title'])
1444
                    ->setContent($values['content'])
1445
                    ->setUser($owner)
1446
                    ->setOrigin($originComment->getId())
1447
                    ->setOriginType(Portfolio::TYPE_COMMENT)
1448
                    ->setCourse($this->course)
1449
                    ->setSession($this->session)
1450
                    ->setCreationDate($currentTime)
1451
                    ->setUpdateDate($currentTime);
1452
1453
                $this->em->persist($portfolio);
1454
            }
1455
1456
            $this->em->flush();
1457
1458
            Display::addFlash(
1459
                Display::return_message(get_lang('PortfolioItemAddedToStudents'), 'success')
1460
            );
1461
1462
            header("Location: $this->baseUrl");
1463
            exit;
1464
        }
1465
1466
        $this->renderView($content, $toolName);
1467
    }
1468
1469
    /**
1470
     * @throws \Doctrine\ORM\ORMException
1471
     * @throws \Doctrine\ORM\OptimisticLockException
1472
     */
1473
    public function markImportantCommentInItem(Portfolio $item, PortfolioComment $comment)
1474
    {
1475
        if ($comment->getItem()->getId() !== $item->getId()) {
1476
            api_not_allowed(true);
1477
        }
1478
1479
        $comment->setIsImportant(
1480
            !$comment->isImportant()
1481
        );
1482
1483
        $this->em->persist($comment);
1484
        $this->em->flush();
1485
1486
        Display::addFlash(
1487
            Display::return_message(get_lang('CommentMarkedAsImportant'), 'success')
1488
        );
1489
1490
        header("Location: $this->baseUrl".http_build_query(['action' => 'view', 'id' => $item->getId()]));
1491
        exit;
1492
    }
1493
1494
    /**
1495
     * @throws \Exception
1496
     */
1497
    public function details(HttpRequest $httpRequest)
1498
    {
1499
        $currentUserId = api_get_user_id();
1500
        $isAllowedToFilterStudent = $this->course && api_is_allowed_to_edit();
1501
1502
        $actions = [];
1503
        $actions[] = Display::url(
1504
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
1505
            $this->baseUrl
1506
        );
1507
        $actions[] = Display::url(
1508
            Display::return_icon('pdf.png', get_lang('ExportMyPortfolioDataPdf'), [], ICON_SIZE_MEDIUM),
1509
            $this->baseUrl.http_build_query(['action' => 'export_pdf'])
1510
        );
1511
        $actions[] = Display::url(
1512
            Display::return_icon('save_pack.png', get_lang('ExportMyPortfolioDataZip'), [], ICON_SIZE_MEDIUM),
1513
            $this->baseUrl.http_build_query(['action' => 'export_zip'])
1514
        );
1515
1516
        $frmStudent = null;
1517
1518
        if ($isAllowedToFilterStudent) {
1519
            if ($httpRequest->query->has('user')) {
1520
                $this->owner = api_get_user_entity($httpRequest->query->getInt('user'));
1521
1522
                if (empty($this->owner)) {
1523
                    api_not_allowed(true);
1524
                }
1525
1526
                $actions[1] = Display::url(
1527
                    Display::return_icon('pdf.png', get_lang('ExportMyPortfolioDataPdf'), [], ICON_SIZE_MEDIUM),
1528
                    $this->baseUrl.http_build_query(['action' => 'export_pdf', 'user' => $this->owner->getId()])
1529
                );
1530
                $actions[2] = Display::url(
1531
                    Display::return_icon('save_pack.png', get_lang('ExportMyPortfolioDataZip'), [], ICON_SIZE_MEDIUM),
1532
                    $this->baseUrl.http_build_query(['action' => 'export_zip', 'user' => $this->owner->getId()])
1533
                );
1534
            }
1535
1536
            $frmStudent = new FormValidator('frm_student_list', 'get');
1537
            $slctStudentOptions = [];
1538
            $slctStudentOptions[$this->owner->getId()] = $this->owner->getCompleteName();
1539
1540
            $urlParams = http_build_query(
1541
                [
1542
                    'a' => 'search_user_by_course',
1543
                    'course_id' => $this->course->getId(),
1544
                    'session_id' => $this->session ? $this->session->getId() : 0,
1545
                ]
1546
            );
1547
1548
            $frmStudent->addSelectAjax(
1549
                'user',
1550
                get_lang('SelectLearnerPortfolio'),
1551
                $slctStudentOptions,
1552
                [
1553
                    'url' => api_get_path(WEB_AJAX_PATH)."course.ajax.php?$urlParams",
1554
                    'placeholder' => get_lang('SearchStudent'),
1555
                ]
1556
            );
1557
            $frmStudent->setDefaults(['user' => $this->owner->getId()]);
1558
            $frmStudent->addHidden('action', 'details');
1559
            $frmStudent->addHidden('cidReq', $this->course->getCode());
1560
            $frmStudent->addHidden('id_session', $this->session ? $this->session->getId() : 0);
1561
            $frmStudent->addButtonFilter(get_lang('Filter'));
1562
        }
1563
1564
        $itemsRepo = $this->em->getRepository(Portfolio::class);
1565
        $commentsRepo = $this->em->getRepository(PortfolioComment::class);
1566
1567
        $getItemsTotalNumber = function () use ($itemsRepo, $isAllowedToFilterStudent, $currentUserId) {
1568
            $qb = $itemsRepo->createQueryBuilder('i');
1569
            $qb
1570
                ->select('COUNT(i)')
1571
                ->where('i.user = :user')
1572
                ->setParameter('user', $this->owner);
1573
1574
            if ($this->course) {
1575
                $qb
1576
                    ->andWhere('i.course = :course')
1577
                    ->setParameter('course', $this->course);
1578
1579
                if ($this->session) {
1580
                    $qb
1581
                        ->andWhere('i.session = :session')
1582
                        ->setParameter('session', $this->session);
1583
                } else {
1584
                    $qb->andWhere('i.session IS NULL');
1585
                }
1586
            }
1587
1588
            if ($isAllowedToFilterStudent && $currentUserId !== $this->owner->getId()) {
1589
                $visibilityCriteria = [
1590
                    Portfolio::VISIBILITY_VISIBLE,
1591
                    Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER,
1592
                ];
1593
1594
                $qb->andWhere(
1595
                    $qb->expr()->in('i.visibility', $visibilityCriteria)
1596
                );
1597
            }
1598
1599
            return $qb->getQuery()->getSingleScalarResult();
1600
        };
1601
        $getItemsData = function ($from, $limit, $columnNo, $orderDirection) use ($itemsRepo, $isAllowedToFilterStudent, $currentUserId) {
1602
            $qb = $itemsRepo->createQueryBuilder('item')
1603
                ->where('item.user = :user')
1604
                ->leftJoin('item.category', 'category')
1605
                ->leftJoin('item.course', 'course')
1606
                ->leftJoin('item.session', 'session')
1607
                ->setParameter('user', $this->owner);
1608
1609
            if ($this->course) {
1610
                $qb
1611
                    ->andWhere('item.course = :course_id')
1612
                    ->setParameter('course_id', $this->course);
1613
1614
                if ($this->session) {
1615
                    $qb
1616
                        ->andWhere('item.session = :session')
1617
                        ->setParameter('session', $this->session);
1618
                } else {
1619
                    $qb->andWhere('item.session IS NULL');
1620
                }
1621
            }
1622
1623
            if ($isAllowedToFilterStudent && $currentUserId !== $this->owner->getId()) {
1624
                $visibilityCriteria = [
1625
                    Portfolio::VISIBILITY_VISIBLE,
1626
                    Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER,
1627
                ];
1628
1629
                $qb->andWhere(
1630
                    $qb->expr()->in('item.visibility', $visibilityCriteria)
1631
                );
1632
            }
1633
1634
            if (0 == $columnNo) {
1635
                $qb->orderBy('item.title', $orderDirection);
1636
            } elseif (1 == $columnNo) {
1637
                $qb->orderBy('item.creationDate', $orderDirection);
1638
            } elseif (2 == $columnNo) {
1639
                $qb->orderBy('item.updateDate', $orderDirection);
1640
            } elseif (3 == $columnNo) {
1641
                $qb->orderBy('category.title', $orderDirection);
1642
            } elseif (5 == $columnNo) {
1643
                $qb->orderBy('item.score', $orderDirection);
1644
            } elseif (6 == $columnNo) {
1645
                $qb->orderBy('course.title', $orderDirection);
1646
            } elseif (7 == $columnNo) {
1647
                $qb->orderBy('session.name', $orderDirection);
1648
            }
1649
1650
            $qb->setFirstResult($from)->setMaxResults($limit);
1651
1652
            return array_map(
1653
                function (Portfolio $item) {
1654
                    $category = $item->getCategory();
1655
1656
                    $row = [];
1657
                    $row[] = $item;
1658
                    $row[] = $item->getCreationDate();
1659
                    $row[] = $item->getUpdateDate();
1660
                    $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...
1661
                    $row[] = $item->getComments()->count();
1662
                    $row[] = $item->getScore();
1663
1664
                    if (!$this->course) {
1665
                        $row[] = $item->getCourse();
1666
                        $row[] = $item->getSession();
1667
                    }
1668
1669
                    return $row;
1670
                },
1671
                $qb->getQuery()->getResult()
1672
            );
1673
        };
1674
1675
        $portfolioItemColumnFilter = function (Portfolio $item) {
1676
            return Display::url(
1677
                $item->getTitle(true),
1678
                $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()])
1679
            );
1680
        };
1681
        $convertFormatDateColumnFilter = function (DateTime $date) {
1682
            return api_convert_and_format_date($date);
1683
        };
1684
1685
        $tblItems = new SortableTable('tbl_items', $getItemsTotalNumber, $getItemsData, 1, 20, 'DESC');
1686
        $tblItems->set_additional_parameters(['action' => 'details', 'user' => $this->owner->getId()]);
1687
        $tblItems->set_header(0, get_lang('Title'));
1688
        $tblItems->set_column_filter(0, $portfolioItemColumnFilter);
1689
        $tblItems->set_header(1, get_lang('CreationDate'), true, [], ['class' => 'text-center']);
1690
        $tblItems->set_column_filter(1, $convertFormatDateColumnFilter);
1691
        $tblItems->set_header(2, get_lang('LastUpdate'), true, [], ['class' => 'text-center']);
1692
        $tblItems->set_column_filter(2, $convertFormatDateColumnFilter);
1693
        $tblItems->set_header(3, get_lang('Category'));
1694
        $tblItems->set_header(4, get_lang('Comments'), false, [], ['class' => 'text-right']);
1695
        $tblItems->set_header(5, get_lang('Score'), true, [], ['class' => 'text-right']);
1696
1697
        if (!$this->course) {
1698
            $tblItems->set_header(6, get_lang('Course'));
1699
            $tblItems->set_header(7, get_lang('Session'));
1700
        }
1701
1702
        $getCommentsTotalNumber = function () use ($commentsRepo) {
1703
            $qb = $commentsRepo->createQueryBuilder('c');
1704
            $qb
1705
                ->select('COUNT(c)')
1706
                ->where('c.author = :author')
1707
                ->setParameter('author', $this->owner);
1708
1709
            if ($this->course) {
1710
                $qb
1711
                    ->innerJoin('c.item', 'i')
1712
                    ->andWhere('i.course = :course')
1713
                    ->setParameter('course', $this->course);
1714
1715
                if ($this->session) {
1716
                    $qb
1717
                        ->andWhere('i.session = :session')
1718
                        ->setParameter('session', $this->session);
1719
                } else {
1720
                    $qb->andWhere('i.session IS NULL');
1721
                }
1722
            }
1723
1724
            return $qb->getQuery()->getSingleScalarResult();
1725
        };
1726
        $getCommentsData = function ($from, $limit, $columnNo, $orderDirection) use ($commentsRepo) {
1727
            $qb = $commentsRepo->createQueryBuilder('comment');
1728
            $qb
1729
                ->where('comment.author = :user')
1730
                ->innerJoin('comment.item', 'item')
1731
                ->setParameter('user', $this->owner);
1732
1733
            if ($this->course) {
1734
                $qb
1735
                    ->innerJoin('comment.item', 'i')
1736
                    ->andWhere('item.course = :course')
1737
                    ->setParameter('course', $this->course);
1738
1739
                if ($this->session) {
1740
                    $qb
1741
                        ->andWhere('item.session = :session')
1742
                        ->setParameter('session', $this->session);
1743
                } else {
1744
                    $qb->andWhere('item.session IS NULL');
1745
                }
1746
            }
1747
1748
            if (0 == $columnNo) {
1749
                $qb->orderBy('comment.content', $orderDirection);
1750
            } elseif (1 == $columnNo) {
1751
                $qb->orderBy('comment.date', $orderDirection);
1752
            } elseif (2 == $columnNo) {
1753
                $qb->orderBy('item.title', $orderDirection);
1754
            } elseif (3 == $columnNo) {
1755
                $qb->orderBy('comment.score', $orderDirection);
1756
            }
1757
1758
            $qb->setFirstResult($from)->setMaxResults($limit);
1759
1760
            return array_map(
1761
                function (PortfolioComment $comment) {
1762
                    return [
1763
                        $comment,
1764
                        $comment->getDate(),
1765
                        $comment->getItem(),
1766
                        $comment->getScore(),
1767
                    ];
1768
                },
1769
                $qb->getQuery()->getResult()
1770
            );
1771
        };
1772
1773
        $tblComments = new SortableTable('tbl_comments', $getCommentsTotalNumber, $getCommentsData, 1, 20, 'DESC');
1774
        $tblComments->set_additional_parameters(['action' => 'details', 'user' => $this->owner->getId()]);
1775
        $tblComments->set_header(0, get_lang('Resume'));
1776
        $tblComments->set_column_filter(
1777
            0,
1778
            function (PortfolioComment $comment) {
1779
                return Display::url(
1780
                    $comment->getExcerpt(),
1781
                    $this->baseUrl.http_build_query(['action' => 'view', 'id' => $comment->getItem()->getId()])
1782
                    .'#comment-'.$comment->getId()
1783
                );
1784
            }
1785
        );
1786
        $tblComments->set_header(1, get_lang('Date'), true, [], ['class' => 'text-center']);
1787
        $tblComments->set_column_filter(1, $convertFormatDateColumnFilter);
1788
        $tblComments->set_header(2, get_lang('PortfolioItemTitle'));
1789
        $tblComments->set_column_filter(2, $portfolioItemColumnFilter);
1790
        $tblComments->set_header(3, get_lang('Score'), true, [], ['class' => 'text-right']);
1791
1792
        $content = '';
1793
1794
        if ($frmStudent) {
1795
            $content .= $frmStudent->returnForm();
1796
        }
1797
1798
        $content .= Display::page_subheader2(get_lang('PortfolioItems')).PHP_EOL;
1799
1800
        if ($tblItems->get_total_number_of_items() > 0) {
1801
            $content .= $tblItems->return_table().PHP_EOL;
1802
        } else {
1803
            $content .= Display::return_message(get_lang('NoItemsInYourPortfolio'), 'warning');
1804
        }
1805
1806
        $content .= Display::page_subheader2(get_lang('PortfolioCommentsMade')).PHP_EOL;
1807
1808
        if ($tblComments->get_total_number_of_items() > 0) {
1809
            $content .= $tblComments->return_table().PHP_EOL;
1810
        } else {
1811
            $content .= Display::return_message(get_lang('YouHaveNotCommented'), 'warning');
1812
        }
1813
1814
        $this->renderView($content, get_lang('PortfolioDetails'), $actions);
1815
    }
1816
1817
    /**
1818
     * @throws \MpdfException
1819
     */
1820
    public function exportPdf(HttpRequest $httpRequest)
1821
    {
1822
        $currentUserId = api_get_user_id();
1823
        $isAllowedToFilterStudent = $this->course && api_is_allowed_to_edit();
1824
1825
        if ($isAllowedToFilterStudent) {
1826
            if ($httpRequest->query->has('user')) {
1827
                $this->owner = api_get_user_entity($httpRequest->query->getInt('user'));
1828
1829
                if (empty($this->owner)) {
1830
                    api_not_allowed(true);
1831
                }
1832
            }
1833
        }
1834
1835
        $pdfContent = Display::page_header($this->owner->getCompleteName());
1836
1837
        if ($this->course) {
1838
            $pdfContent .= '<p>'.get_lang('Course').': ';
1839
1840
            if ($this->session) {
1841
                $pdfContent .= $this->session->getName().' ('.$this->course->getTitle().')';
1842
            } else {
1843
                $pdfContent .= $this->course->getTitle();
1844
            }
1845
1846
            $pdfContent .= '</p>';
1847
        }
1848
1849
        $visibility = [];
1850
1851
        if ($isAllowedToFilterStudent && $currentUserId !== $this->owner->getId()) {
1852
            $visibility[] = Portfolio::VISIBILITY_VISIBLE;
1853
            $visibility[] = Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER;
1854
        }
1855
1856
        $items = $this->em
1857
            ->getRepository(Portfolio::class)
1858
            ->findItemsByUser(
1859
                $this->owner,
1860
                $this->course,
1861
                $this->session,
1862
                null,
1863
                $visibility
1864
            );
1865
        $comments = $this->em
1866
            ->getRepository(PortfolioComment::class)
1867
            ->findCommentsByUser($this->owner, $this->course, $this->session);
1868
1869
        $itemsHtml = $this->getItemsInHtmlFormatted($items);
1870
        $commentsHtml = $this->getCommentsInHtmlFormatted($comments);
1871
1872
        $pdfContent .= Display::page_subheader2(get_lang('PortfolioItems'));
1873
1874
        if (count($itemsHtml) > 0) {
1875
            $pdfContent .= implode(PHP_EOL, $itemsHtml);
1876
        } else {
1877
            $pdfContent .= Display::return_message(get_lang('NoItemsInYourPortfolio'), 'warning');
1878
        }
1879
1880
        $pdfContent .= Display::page_subheader2(get_lang('PortfolioCommentsMade'));
1881
1882
        if (count($commentsHtml) > 0) {
1883
            $pdfContent .= implode(PHP_EOL, $commentsHtml);
1884
        } else {
1885
            $pdfContent .= Display::return_message(get_lang('YouHaveNotCommented'), 'warning');
1886
        }
1887
1888
        $pdfName = $this->owner->getCompleteName()
1889
            .($this->course ? '_'.$this->course->getCode() : '')
1890
            .'_'.get_lang('Portfolio');
1891
1892
        $pdf = new PDF();
1893
        $pdf->content_to_pdf(
1894
            $pdfContent,
1895
            null,
1896
            $pdfName,
1897
            $this->course ? $this->course->getCode() : null,
1898
            'D',
1899
            false,
1900
            null,
1901
            false,
1902
            true
1903
        );
1904
    }
1905
1906
    public function exportZip(HttpRequest $httpRequest)
1907
    {
1908
        $currentUserId = api_get_user_id();
1909
        $isAllowedToFilterStudent = $this->course && api_is_allowed_to_edit();
1910
1911
        if ($isAllowedToFilterStudent) {
1912
            if ($httpRequest->query->has('user')) {
1913
                $this->owner = api_get_user_entity($httpRequest->query->getInt('user'));
1914
1915
                if (empty($this->owner)) {
1916
                    api_not_allowed(true);
1917
                }
1918
            }
1919
        }
1920
1921
        $itemsRepo = $this->em->getRepository(Portfolio::class);
1922
        $commentsRepo = $this->em->getRepository(PortfolioComment::class);
1923
        $attachmentsRepo = $this->em->getRepository(PortfolioAttachment::class);
1924
1925
        $visibility = [];
1926
1927
        if ($isAllowedToFilterStudent && $currentUserId !== $this->owner->getId()) {
1928
            $visibility[] = Portfolio::VISIBILITY_VISIBLE;
1929
            $visibility[] = Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER;
1930
        }
1931
1932
        $items = $itemsRepo->findItemsByUser(
1933
            $this->owner,
1934
            $this->course,
1935
            $this->session,
1936
            null,
1937
            $visibility
1938
        );
1939
        $comments = $commentsRepo->findCommentsByUser($this->owner, $this->course, $this->session);
1940
1941
        $itemsHtml = $this->getItemsInHtmlFormatted($items);
1942
        $commentsHtml = $this->getCommentsInHtmlFormatted($comments);
1943
1944
        $sysArchivePath = api_get_path(SYS_ARCHIVE_PATH);
1945
        $tempPortfolioDirectory = $sysArchivePath."portfolio/{$this->owner->getId()}";
1946
1947
        $userDirectory = UserManager::getUserPathById($this->owner->getId(), 'system');
1948
        $attachmentsDirectory = $userDirectory.'portfolio_attachments/';
1949
1950
        $tblItemsHeaders = [];
1951
        $tblItemsHeaders[] = get_lang('Title');
1952
        $tblItemsHeaders[] = get_lang('CreationDate');
1953
        $tblItemsHeaders[] = get_lang('LastUpdate');
1954
        $tblItemsHeaders[] = get_lang('Category');
1955
        $tblItemsHeaders[] = get_lang('Category');
1956
        $tblItemsHeaders[] = get_lang('Score');
1957
        $tblItemsHeaders[] = get_lang('Course');
1958
        $tblItemsHeaders[] = get_lang('Session');
1959
        $tblItemsData = [];
1960
1961
        $tblCommentsHeaders = [];
1962
        $tblCommentsHeaders[] = get_lang('Resume');
1963
        $tblCommentsHeaders[] = get_lang('Date');
1964
        $tblCommentsHeaders[] = get_lang('PortfolioItemTitle');
1965
        $tblCommentsHeaders[] = get_lang('Score');
1966
        $tblCommentsData = [];
1967
1968
        $filenames = [];
1969
1970
        $fs = new Filesystem();
1971
1972
        /**
1973
         * @var int       $i
1974
         * @var Portfolio $item
1975
         */
1976
        foreach ($items as $i => $item) {
1977
            $itemCategory = $item->getCategory();
1978
            $itemCourse = $item->getCourse();
1979
            $itemSession = $item->getSession();
1980
1981
            $itemDirectory = $item->getCreationDate()->format('Y-m-d-H-i-s');
1982
1983
            $itemFilename = sprintf('%s/items/%s/item.html', $tempPortfolioDirectory, $itemDirectory);
1984
            $itemFileContent = $this->fixImagesSourcesToHtml($itemsHtml[$i]);
1985
1986
            $fs->dumpFile($itemFilename, $itemFileContent);
1987
1988
            $filenames[] = $itemFilename;
1989
1990
            $attachments = $attachmentsRepo->findFromItem($item);
1991
1992
            /** @var PortfolioAttachment $attachment */
1993
            foreach ($attachments as $attachment) {
1994
                $attachmentFilename = sprintf(
1995
                    '%s/items/%s/attachments/%s',
1996
                    $tempPortfolioDirectory,
1997
                    $itemDirectory,
1998
                    $attachment->getFilename()
1999
                );
2000
2001
                $fs->copy(
2002
                    $attachmentsDirectory.$attachment->getPath(),
2003
                    $attachmentFilename
2004
                );
2005
2006
                $filenames[] = $attachmentFilename;
2007
            }
2008
2009
            $tblItemsData[] = [
2010
                Display::url(
2011
                    Security::remove_XSS($item->getTitle()),
2012
                    sprintf('items/%s/item.html', $itemDirectory)
2013
                ),
2014
                api_convert_and_format_date($item->getCreationDate()),
2015
                api_convert_and_format_date($item->getUpdateDate()),
2016
                $itemCategory ? $itemCategory->getTitle() : null,
2017
                $item->getComments()->count(),
2018
                $item->getScore(),
2019
                $itemCourse->getTitle(),
2020
                $itemSession ? $itemSession->getName() : null,
2021
            ];
2022
        }
2023
2024
        /**
2025
         * @var int              $i
2026
         * @var PortfolioComment $comment
2027
         */
2028
        foreach ($comments as $i => $comment) {
2029
            $commentDirectory = $comment->getDate()->format('Y-m-d-H-i-s');
2030
2031
            $commentFileContent = $this->fixImagesSourcesToHtml($commentsHtml[$i]);
2032
            $commentFilename = sprintf('%s/comments/%s/comment.html', $tempPortfolioDirectory, $commentDirectory);
2033
2034
            $fs->dumpFile($commentFilename, $commentFileContent);
2035
2036
            $filenames[] = $commentFilename;
2037
2038
            $attachments = $attachmentsRepo->findFromComment($comment);
2039
2040
            /** @var PortfolioAttachment $attachment */
2041
            foreach ($attachments as $attachment) {
2042
                $attachmentFilename = sprintf(
2043
                    '%s/comments/%s/attachments/%s',
2044
                    $tempPortfolioDirectory,
2045
                    $commentDirectory,
2046
                    $attachment->getFilename()
2047
                );
2048
2049
                $fs->copy(
2050
                    $attachmentsDirectory.$attachment->getPath(),
2051
                    $attachmentFilename
2052
                );
2053
2054
                $filenames[] = $attachmentFilename;
2055
            }
2056
2057
            $tblCommentsData[] = [
2058
                Display::url(
2059
                    $comment->getExcerpt(),
2060
                    sprintf('comments/%s/comment.html', $commentDirectory)
2061
                ),
2062
                api_convert_and_format_date($comment->getDate()),
2063
                Security::remove_XSS($comment->getItem()->getTitle()),
2064
                $comment->getScore(),
2065
            ];
2066
        }
2067
2068
        $tblItems = new HTML_Table(['class' => 'table table-hover table-striped table-bordered data_table']);
2069
        $tblItems->setHeaders($tblItemsHeaders);
2070
        $tblItems->setData($tblItemsData);
2071
2072
        $tblComments = new HTML_Table(['class' => 'table table-hover table-striped table-bordered data_table']);
2073
        $tblComments->setHeaders($tblCommentsHeaders);
2074
        $tblComments->setData($tblCommentsData);
2075
2076
        $itemFilename = sprintf('%s/index.html', $tempPortfolioDirectory);
2077
2078
        $filenames[] = $itemFilename;
2079
2080
        $fs->dumpFile(
2081
            $itemFilename,
2082
            $this->formatZipIndexFile($tblItems, $tblComments)
2083
        );
2084
2085
        $zipName = $this->owner->getCompleteName()
2086
            .($this->course ? '_'.$this->course->getCode() : '')
2087
            .'_'.get_lang('Portfolio');
2088
        $tempZipFile = $sysArchivePath."portfolio/$zipName.zip";
2089
        $zip = new PclZip($tempZipFile);
2090
2091
        foreach ($filenames as $filename) {
2092
            $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...
2093
        }
2094
2095
        DocumentManager::file_send_for_download($tempZipFile, true, "$zipName.zip");
2096
2097
        $fs->remove($tempPortfolioDirectory);
2098
        $fs->remove($tempZipFile);
2099
    }
2100
2101
    public function qualifyItem(Portfolio $item)
2102
    {
2103
        global $interbreadcrumb;
2104
2105
        $em = Database::getManager();
2106
2107
        $formAction = $this->baseUrl.http_build_query(['action' => 'qualify', 'item' => $item->getId()]);
2108
2109
        $form = new FormValidator('frm_qualify', 'post', $formAction);
2110
        $form->addUserAvatar('user', get_lang('Author'));
2111
        $form->addLabel(get_lang('Title'), $item->getTitle());
2112
2113
        $itemContent = $this->generateItemContent($item);
2114
2115
        $form->addLabel(get_lang('Content'), $itemContent);
2116
        $form->addNumeric(
2117
            'score',
2118
            [get_lang('QualifyNumeric'), null, ' / '.api_get_course_setting('portfolio_max_score')]
2119
        );
2120
        $form->addButtonSave(get_lang('QualifyThisPortfolioItem'));
2121
2122
        if ($form->validate()) {
2123
            $values = $form->exportValues();
2124
2125
            $item->setScore($values['score']);
2126
2127
            $em->persist($item);
2128
            $em->flush();
2129
2130
            Display::addFlash(
2131
                Display::return_message(get_lang('PortfolioItemGraded'), 'success')
2132
            );
2133
2134
            header("Location: $formAction");
2135
            exit();
2136
        }
2137
2138
        $form->setDefaults(
2139
            [
2140
                'user' => $item->getUser(),
2141
                'score' => (float) $item->getScore(),
2142
            ]
2143
        );
2144
2145
        $interbreadcrumb[] = [
2146
            'name' => get_lang('Portfolio'),
2147
            'url' => $this->baseUrl,
2148
        ];
2149
        $interbreadcrumb[] = [
2150
            'name' => $item->getTitle(true),
2151
            'url' => $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()]),
2152
        ];
2153
2154
        $actions = [];
2155
        $actions[] = Display::url(
2156
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
2157
            $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()])
2158
        );
2159
2160
        $this->renderView($form->returnForm(), get_lang('Qualify'), $actions);
2161
    }
2162
2163
    public function qualifyComment(PortfolioComment $comment)
2164
    {
2165
        global $interbreadcrumb;
2166
2167
        $em = Database::getManager();
2168
2169
        $item = $comment->getItem();
2170
        $commentPath = $em->getRepository(PortfolioComment::class)->getPath($comment);
2171
2172
        $template = new Template('', false, false, false, true, false, false);
2173
        $template->assign('item', $item);
2174
        $template->assign('comments_path', $commentPath);
2175
        $commentContext = $template->fetch(
2176
            $template->get_template('portfolio/comment_context.html.twig')
2177
        );
2178
2179
        $formAction = $this->baseUrl.http_build_query(['action' => 'qualify', 'comment' => $comment->getId()]);
2180
2181
        $form = new FormValidator('frm_qualify', 'post', $formAction);
2182
        $form->addHtml($commentContext);
2183
        $form->addUserAvatar('user', get_lang('Author'));
2184
        $form->addLabel(get_lang('Comment'), $comment->getContent());
2185
        $form->addNumeric(
2186
            'score',
2187
            [get_lang('QualifyNumeric'), null, '/ '.api_get_course_setting('portfolio_max_score')]
2188
        );
2189
        $form->addButtonSave(get_lang('QualifyThisPortfolioComment'));
2190
2191
        if ($form->validate()) {
2192
            $values = $form->exportValues();
2193
2194
            $comment->setScore($values['score']);
2195
2196
            $em->persist($comment);
2197
            $em->flush();
2198
2199
            Display::addFlash(
2200
                Display::return_message(get_lang('PortfolioCommentGraded'), 'success')
2201
            );
2202
2203
            header("Location: $formAction");
2204
            exit();
2205
        }
2206
2207
        $form->setDefaults(
2208
            [
2209
                'user' => $comment->getAuthor(),
2210
                'score' => (float) $comment->getScore(),
2211
            ]
2212
        );
2213
2214
        $interbreadcrumb[] = [
2215
            'name' => get_lang('Portfolio'),
2216
            'url' => $this->baseUrl,
2217
        ];
2218
        $interbreadcrumb[] = [
2219
            'name' => $item->getTitle(true),
2220
            'url' => $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()]),
2221
        ];
2222
2223
        $actions = [];
2224
        $actions[] = Display::url(
2225
            Display::return_icon('back.png', get_lang('Back'), [], ICON_SIZE_MEDIUM),
2226
            $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()])
2227
        );
2228
2229
        $this->renderView($form->returnForm(), get_lang('Qualify'), $actions);
2230
    }
2231
2232
    public function downloadAttachment(HttpRequest $httpRequest)
2233
    {
2234
        $path = $httpRequest->query->get('file');
2235
2236
        if (empty($path)) {
2237
            api_not_allowed(true);
2238
        }
2239
2240
        $em = Database::getManager();
2241
        $attachmentRepo = $em->getRepository(PortfolioAttachment::class);
2242
2243
        $attachment = $attachmentRepo->findOneByPath($path);
2244
2245
        if (empty($attachment)) {
2246
            api_not_allowed(true);
2247
        }
2248
2249
        $originOwnerId = 0;
2250
2251
        if (PortfolioAttachment::TYPE_ITEM === $attachment->getOriginType()) {
2252
            $item = $em->find(Portfolio::class, $attachment->getOrigin());
2253
2254
            $originOwnerId = $item->getUser()->getId();
2255
        } elseif (PortfolioAttachment::TYPE_COMMENT === $attachment->getOriginType()) {
2256
            $comment = $em->find(PortfolioComment::class, $attachment->getOrigin());
2257
2258
            $originOwnerId = $comment->getAuthor()->getId();
2259
        } else {
2260
            api_not_allowed(true);
2261
        }
2262
2263
        $userDirectory = UserManager::getUserPathById($originOwnerId, 'system');
2264
        $attachmentsDirectory = $userDirectory.'portfolio_attachments/';
2265
        $attachmentFilename = $attachmentsDirectory.$attachment->getPath();
2266
2267
        if (!Security::check_abs_path($attachmentFilename, $attachmentsDirectory)) {
2268
            api_not_allowed(true);
2269
        }
2270
2271
        $downloaded = DocumentManager::file_send_for_download(
2272
            $attachmentFilename,
2273
            true,
2274
            $attachment->getFilename()
2275
        );
2276
2277
        if (!$downloaded) {
2278
            api_not_allowed(true);
2279
        }
2280
    }
2281
2282
    public function deleteAttachment(HttpRequest $httpRequest)
2283
    {
2284
        $currentUserId = api_get_user_id();
2285
2286
        $path = $httpRequest->query->get('file');
2287
2288
        if (empty($path)) {
2289
            api_not_allowed(true);
2290
        }
2291
2292
        $em = Database::getManager();
2293
        $fs = new Filesystem();
2294
2295
        $attachmentRepo = $em->getRepository(PortfolioAttachment::class);
2296
        $attachment = $attachmentRepo->findOneByPath($path);
2297
2298
        if (empty($attachment)) {
2299
            api_not_allowed(true);
2300
        }
2301
2302
        $originOwnerId = 0;
2303
        $itemId = 0;
2304
2305
        if (PortfolioAttachment::TYPE_ITEM === $attachment->getOriginType()) {
2306
            $item = $em->find(Portfolio::class, $attachment->getOrigin());
2307
            $originOwnerId = $item->getUser()->getId();
2308
            $itemId = $item->getId();
2309
        } elseif (PortfolioAttachment::TYPE_COMMENT === $attachment->getOriginType()) {
2310
            $comment = $em->find(PortfolioComment::class, $attachment->getOrigin());
2311
            $originOwnerId = $comment->getAuthor()->getId();
2312
            $itemId = $comment->getItem()->getId();
2313
        }
2314
2315
        if ($currentUserId !== $originOwnerId) {
2316
            api_not_allowed(true);
2317
        }
2318
2319
        $em->remove($attachment);
2320
        $em->flush();
2321
2322
        $userDirectory = UserManager::getUserPathById($originOwnerId, 'system');
2323
        $attachmentsDirectory = $userDirectory.'portfolio_attachments/';
2324
        $attachmentFilename = $attachmentsDirectory.$attachment->getPath();
2325
2326
        $fs->remove($attachmentFilename);
2327
2328
        if ($httpRequest->isXmlHttpRequest()) {
2329
            echo Display::return_message(get_lang('AttachmentFileDeleteSuccess'), 'success');
2330
        } else {
2331
            Display::addFlash(
2332
                Display::return_message(get_lang('AttachmentFileDeleteSuccess'), 'success')
2333
            );
2334
2335
            header('Location: '.$this->baseUrl.http_build_query(['action' => 'view', 'id' => $itemId]));
2336
        }
2337
2338
        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...
2339
    }
2340
2341
    /**
2342
     * @param bool $showHeader
2343
     */
2344
    private function renderView(string $content, string $toolName, array $actions = [], $showHeader = true)
2345
    {
2346
        global $this_section;
2347
2348
        $this_section = $this->course ? SECTION_COURSES : SECTION_SOCIAL;
2349
2350
        $view = new Template($toolName);
2351
2352
        if ($showHeader) {
2353
            $view->assign('header', $toolName);
2354
        }
2355
2356
        $actionsStr = '';
2357
2358
        if ($this->course) {
2359
            $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...
2360
        }
2361
2362
        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...
2363
            $actions = implode(PHP_EOL, $actions);
2364
2365
            $actionsStr .= Display::toolbarAction('portfolio-toolbar', [$actions]);
2366
        }
2367
2368
        $view->assign('baseurl', $this->baseUrl);
2369
        $view->assign('actions', $actionsStr);
2370
2371
        $view->assign('content', $content);
2372
        $view->display_one_col_template();
2373
    }
2374
2375
    private function categoryBelongToOwner(PortfolioCategory $category): bool
2376
    {
2377
        if ($category->getUser()->getId() != $this->owner->getId()) {
2378
            return false;
2379
        }
2380
2381
        return true;
2382
    }
2383
2384
    private function addAttachmentsFieldToForm(FormValidator $form)
2385
    {
2386
        $form->addButton('add_attachment', get_lang('AddAttachment'), 'plus');
2387
        $form->addHtml('<div id="container-attachments" style="display: none;">');
2388
        $form->addFile('attachment_file[]', get_lang('FilesAttachment'));
2389
        $form->addText('attachment_comment[]', get_lang('Description'), false);
2390
        $form->addHtml('</div>');
2391
2392
        $script = "$(function () {
2393
            var attachmentsTemplate = $('#container-attachments').html();
2394
            var \$btnAdd = $('[name=\"add_attachment\"]');
2395
            var \$reference = \$btnAdd.parents('.form-group');
2396
2397
            \$btnAdd.on('click', function (e) {
2398
                e.preventDefault();
2399
2400
                $(attachmentsTemplate).insertBefore(\$reference);
2401
            });
2402
        })";
2403
2404
        $form->addHtml("<script>$script</script>");
2405
    }
2406
2407
    private function processAttachments(
2408
        FormValidator $form,
2409
        User $user,
2410
        int $originId,
2411
        int $originType
2412
    ) {
2413
        $em = Database::getManager();
2414
        $fs = new Filesystem();
2415
2416
        $comments = $form->getSubmitValue('attachment_comment');
2417
2418
        foreach ($_FILES['attachment_file']['error'] as $i => $attachmentFileError) {
2419
            if ($attachmentFileError != UPLOAD_ERR_OK) {
2420
                continue;
2421
            }
2422
2423
            $_file = [
2424
                'name' => $_FILES['attachment_file']['name'][$i],
2425
                'type' => $_FILES['attachment_file']['type'][$i],
2426
                'tmp_name' => $_FILES['attachment_file']['tmp_name'][$i],
2427
                'size' => $_FILES['attachment_file']['size'][$i],
2428
            ];
2429
2430
            if (empty($_file['type'])) {
2431
                $_file['type'] = DocumentManager::file_get_mime_type($_file['name']);
2432
            }
2433
2434
            $newFileName = add_ext_on_mime(stripslashes($_file['name']), $_file['type']);
2435
2436
            if (!filter_extension($newFileName)) {
2437
                Display::addFlash(Display::return_message(get_lang('UplUnableToSaveFileFilteredExtension'), 'error'));
2438
                continue;
2439
            }
2440
2441
            $newFileName = uniqid();
2442
            $attachmentsDirectory = UserManager::getUserPathById($user->getId(), 'system').'portfolio_attachments/';
2443
2444
            if (!$fs->exists($attachmentsDirectory)) {
2445
                $fs->mkdir($attachmentsDirectory, api_get_permissions_for_new_directories());
2446
            }
2447
2448
            $attachmentFilename = $attachmentsDirectory.$newFileName;
2449
2450
            if (is_uploaded_file($_file['tmp_name'])) {
2451
                $moved = move_uploaded_file($_file['tmp_name'], $attachmentFilename);
2452
2453
                if (!$moved) {
2454
                    Display::addFlash(Display::return_message(get_lang('UplUnableToSaveFile'), 'error'));
2455
                    continue;
2456
                }
2457
            }
2458
2459
            $attachment = new PortfolioAttachment();
2460
            $attachment
2461
                ->setFilename($_file['name'])
2462
                ->setComment($comments[$i])
2463
                ->setPath($newFileName)
2464
                ->setOrigin($originId)
2465
                ->setOriginType($originType)
2466
                ->setSize($_file['size']);
2467
2468
            $em->persist($attachment);
2469
            $em->flush();
2470
        }
2471
    }
2472
2473
    private function itemBelongToOwner(Portfolio $item): bool
2474
    {
2475
        if ($item->getUser()->getId() != $this->owner->getId()) {
2476
            return false;
2477
        }
2478
2479
        return true;
2480
    }
2481
2482
    private function createFormTagFilter(bool $listByUser = false): FormValidator
2483
    {
2484
        $em = Database::getManager();
2485
        $tagTepo = $em->getRepository(Tag::class);
2486
2487
        $frmTagList = new FormValidator(
2488
            'frm_tag_list',
2489
            'get',
2490
            $this->baseUrl.($listByUser ? 'user='.$this->owner->getId() : ''),
2491
            '',
2492
            [],
2493
            FormValidator::LAYOUT_BOX
2494
        );
2495
2496
        $frmTagList->addDatePicker('date', get_lang('CreationDate'));
2497
2498
        /** @var SelectAjax $txtTags */
2499
        $txtTags = $frmTagList->addSelectAjax(
2500
            'tags',
2501
            get_lang('Tags'),
2502
            [],
2503
            [
2504
                'multiple' => 'multiple',
2505
                'url' => api_get_path(WEB_AJAX_PATH)."extra_field.ajax.php?a=search_tags&field_id=29&type=portfolio&byid=1",
2506
            ]
2507
        );
2508
        $selectedTags = $txtTags->getValue();
2509
2510
        if (!empty($selectedTags)) {
2511
            foreach ($tagTepo->findById($selectedTags) as $tag) {
2512
                $txtTags->addOption($tag->getTag(), $tag->getId());
2513
            }
2514
        }
2515
2516
        $frmTagList->addText('text', get_lang('Search'), false)->setIcon('search');
2517
        $frmTagList->applyFilter('text', 'trim');
2518
        $frmTagList->addHtml('<br>');
2519
        $frmTagList->addButtonFilter(get_lang('Filter'));
2520
2521
        if ($this->course) {
2522
            $frmTagList->addHidden('cidReq', $this->course->getCode());
2523
            $frmTagList->addHidden('id_session', $this->session ? $this->session->getId() : 0);
2524
            $frmTagList->addHidden('gidReq', 0);
2525
            $frmTagList->addHidden('gradebook', 0);
2526
            $frmTagList->addHidden('origin', '');
2527
            $frmTagList->addHidden('categoryId', 0);
2528
            $frmTagList->addHidden('subCategoryIds', '');
2529
2530
            if ($listByUser) {
2531
                $frmTagList->addHidden('user', $this->owner->getId());
2532
            }
2533
        }
2534
2535
        return $frmTagList;
2536
    }
2537
2538
    /**
2539
     * @throws \Exception
2540
     *
2541
     * @return \FormValidator
2542
     */
2543
    private function createFormStudentFilter(bool $listByUser = false): FormValidator
2544
    {
2545
        $frmStudentList = new FormValidator(
2546
            'frm_student_list',
2547
            'get',
2548
            $this->baseUrl,
2549
            '',
2550
            [],
2551
            FormValidator::LAYOUT_BOX
2552
        );
2553
        $slctStudentOptions = [];
2554
2555
        if ($listByUser) {
2556
            $slctStudentOptions[$this->owner->getId()] = $this->owner->getCompleteName();
2557
        }
2558
2559
        $urlParams = http_build_query(
2560
            [
2561
                'a' => 'search_user_by_course',
2562
                'course_id' => $this->course->getId(),
2563
                'session_id' => $this->session ? $this->session->getId() : 0,
2564
            ]
2565
        );
2566
2567
        $frmStudentList->addSelectAjax(
2568
            'user',
2569
            get_lang('SelectLearnerPortfolio'),
2570
            $slctStudentOptions,
2571
            [
2572
                'url' => api_get_path(WEB_AJAX_PATH)."course.ajax.php?$urlParams",
2573
                'placeholder' => get_lang('SearchStudent'),
2574
            ]
2575
        );
2576
2577
        if ($listByUser) {
2578
            $link = Display::url(
2579
                get_lang('BackToMainPortfolio'),
2580
                $this->baseUrl
2581
            );
2582
        } else {
2583
            $link = Display::url(
2584
                get_lang('SeeMyPortfolio'),
2585
                $this->baseUrl.http_build_query(['user' => api_get_user_id()])
2586
            );
2587
        }
2588
2589
        $frmStudentList->addHtml($link);
2590
2591
        return $frmStudentList;
2592
    }
2593
2594
    private function getCategoriesForIndex(?int $currentUserId = null, ?int $parentId = null): array
2595
    {
2596
        $categoriesCriteria = [];
2597
        if (isset($currentUserId)) {
2598
            $categoriesCriteria['user'] = $this->owner;
2599
        }
2600
        if (!api_is_platform_admin() && $currentUserId !== $this->owner->getId()) {
2601
            $categoriesCriteria['isVisible'] = true;
2602
        }
2603
        if (isset($parentId)) {
2604
            $categoriesCriteria['parentId'] = $parentId;
2605
        }
2606
2607
        return $this->em
2608
            ->getRepository(PortfolioCategory::class)
2609
            ->findBy($categoriesCriteria);
2610
    }
2611
2612
    private function getItemsForIndex(
2613
        bool $listByUser = false,
2614
        FormValidator $frmFilterList = null
2615
    ) {
2616
        $currentUserId = api_get_user_id();
2617
2618
        if ($this->course) {
2619
            $queryBuilder = $this->em->createQueryBuilder();
2620
            $queryBuilder
2621
                ->select('pi')
2622
                ->from(Portfolio::class, 'pi')
2623
                ->where('pi.course = :course');
2624
2625
            $queryBuilder->setParameter('course', $this->course);
2626
2627
            if ($this->session) {
2628
                $queryBuilder->andWhere('pi.session = :session');
2629
                $queryBuilder->setParameter('session', $this->session);
2630
            } else {
2631
                $queryBuilder->andWhere('pi.session IS NULL');
2632
            }
2633
2634
            if ($frmFilterList && $frmFilterList->validate()) {
2635
                $values = $frmFilterList->exportValues();
2636
2637
                if (!empty($values['date'])) {
2638
                    $queryBuilder
2639
                        ->andWhere('pi.creationDate >= :date')
2640
                        ->setParameter(':date', api_get_utc_datetime($values['date'], false, true))
2641
                    ;
2642
                }
2643
2644
                if (!empty($values['tags'])) {
2645
                    $queryBuilder
2646
                        ->innerJoin(ExtraFieldRelTag::class, 'efrt', Join::WITH, 'efrt.itemId = pi.id')
2647
                        ->innerJoin(ExtraFieldEntity::class, 'ef', Join::WITH, 'ef.id = efrt.fieldId')
2648
                        ->andWhere('ef.extraFieldType = :efType')
2649
                        ->andWhere('ef.variable = :variable')
2650
                        ->andWhere('efrt.tagId IN (:tags)');
2651
2652
                    $queryBuilder->setParameter('efType', ExtraFieldEntity::PORTFOLIO_TYPE);
2653
                    $queryBuilder->setParameter('variable', 'tags');
2654
                    $queryBuilder->setParameter('tags', $values['tags']);
2655
                }
2656
2657
                if (!empty($values['text'])) {
2658
                    $queryBuilder->andWhere(
2659
                        $queryBuilder->expr()->orX(
2660
                            $queryBuilder->expr()->like('pi.title', ':text'),
2661
                            $queryBuilder->expr()->like('pi.content', ':text')
2662
                        )
2663
                    );
2664
2665
                    $queryBuilder->setParameter('text', '%'.$values['text'].'%');
2666
                }
2667
2668
                // Filters by category level 0
2669
                $searchCategories = [];
2670
                if (!empty($values['categoryId'])) {
2671
                    $searchCategories[] = $values['categoryId'];
2672
                    $subCategories = $this->getCategoriesForIndex(null, $values['categoryId']);
2673
                    if (count($subCategories) > 0) {
2674
                        foreach ($subCategories as $subCategory) {
2675
                            $searchCategories[] = $subCategory->getId();
2676
                        }
2677
                    }
2678
                    $queryBuilder->andWhere('pi.category IN('.implode(',', $searchCategories).')');
2679
                }
2680
2681
                // Filters by sub-category, don't show the selected values
2682
                $diff = [];
2683
                if (!empty($values['subCategoryIds']) && !('all' === $values['subCategoryIds'])) {
2684
                    $subCategoryIds = explode(',', $values['subCategoryIds']);
2685
                    $diff = array_diff($searchCategories, $subCategoryIds);
2686
                } else {
2687
                    if (trim($values['subCategoryIds']) === '') {
2688
                        $diff = $searchCategories;
2689
                    }
2690
                }
2691
                if (!empty($diff)) {
2692
                    unset($diff[0]);
2693
                    if (!empty($diff)) {
2694
                        $queryBuilder->andWhere('pi.category NOT IN('.implode(',', $diff).')');
2695
                    }
2696
                }
2697
            }
2698
2699
            if ($listByUser) {
2700
                $queryBuilder
2701
                    ->andWhere('pi.user = :user')
2702
                    ->setParameter('user', $this->owner);
2703
            }
2704
2705
            $visibilityCriteria = [Portfolio::VISIBILITY_VISIBLE];
2706
2707
            if (api_is_allowed_to_edit()) {
2708
                $visibilityCriteria[] = Portfolio::VISIBILITY_HIDDEN_EXCEPT_TEACHER;
2709
            }
2710
2711
            $queryBuilder
2712
                ->andWhere(
2713
                    $queryBuilder->expr()->orX(
2714
                        'pi.user = :current_user',
2715
                        $queryBuilder->expr()->andX(
2716
                            'pi.user != :current_user',
2717
                            $queryBuilder->expr()->in('pi.visibility', $visibilityCriteria)
2718
                        )
2719
                    )
2720
                )
2721
                ->setParameter('current_user', $currentUserId);
2722
2723
            $queryBuilder->orderBy('pi.creationDate', 'DESC');
2724
2725
            $items = $queryBuilder->getQuery()->getResult();
2726
        } else {
2727
            $itemsCriteria = [];
2728
            $itemsCriteria['category'] = null;
2729
            $itemsCriteria['user'] = $this->owner;
2730
2731
            if ($currentUserId !== $this->owner->getId()) {
2732
                $itemsCriteria['visibility'] = Portfolio::VISIBILITY_VISIBLE;
2733
            }
2734
2735
            $items = $this->em
2736
                ->getRepository(Portfolio::class)
2737
                ->findBy($itemsCriteria, ['creationDate' => 'DESC']);
2738
        }
2739
2740
        return $items;
2741
    }
2742
2743
    /**
2744
     * @throws \Doctrine\ORM\ORMException
2745
     * @throws \Doctrine\ORM\OptimisticLockException
2746
     * @throws \Doctrine\ORM\TransactionRequiredException
2747
     */
2748
    private function createCommentForm(Portfolio $item): string
2749
    {
2750
        $formAction = $this->baseUrl.http_build_query(['action' => 'view', 'id' => $item->getId()]);
2751
2752
        $form = new FormValidator('frm_comment', 'post', $formAction);
2753
        $form->addHtmlEditor('content', get_lang('Comments'), true, false, ['ToolbarSet' => 'Minimal']);
2754
        $form->addHidden('item', $item->getId());
2755
        $form->addHidden('parent', 0);
2756
        $form->applyFilter('content', 'trim');
2757
2758
        $this->addAttachmentsFieldToForm($form);
2759
2760
        $form->addButtonSave(get_lang('Save'));
2761
2762
        if ($form->validate()) {
2763
            $values = $form->exportValues();
2764
2765
            $parentComment = $this->em->find(PortfolioComment::class, $values['parent']);
2766
2767
            $comment = new PortfolioComment();
2768
            $comment
2769
                ->setAuthor($this->owner)
2770
                ->setParent($parentComment)
2771
                ->setContent($values['content'])
2772
                ->setDate(api_get_utc_datetime(null, false, true))
2773
                ->setItem($item);
2774
2775
            $this->em->persist($comment);
2776
            $this->em->flush();
2777
2778
            $this->processAttachments(
2779
                $form,
2780
                $comment->getAuthor(),
2781
                $comment->getId(),
2782
                PortfolioAttachment::TYPE_COMMENT
2783
            );
2784
2785
            $hook = HookPortfolioItemCommented::create();
2786
            $hook->setEventData(['comment' => $comment]);
2787
            $hook->notifyItemCommented();
2788
2789
            Display::addFlash(
2790
                Display::return_message(get_lang('CommentAdded'), 'success')
2791
            );
2792
2793
            header("Location: $formAction");
2794
            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...
2795
        }
2796
2797
        return $form->returnForm();
2798
    }
2799
2800
    private function generateAttachmentList($post, bool $includeHeader = true): string
2801
    {
2802
        $attachmentsRepo = $this->em->getRepository(PortfolioAttachment::class);
2803
2804
        $postOwnerId = 0;
2805
2806
        if ($post instanceof Portfolio) {
2807
            $attachments = $attachmentsRepo->findFromItem($post);
2808
2809
            $postOwnerId = $post->getUser()->getId();
2810
        } elseif ($post instanceof PortfolioComment) {
2811
            $attachments = $attachmentsRepo->findFromComment($post);
2812
2813
            $postOwnerId = $post->getAuthor()->getId();
2814
        }
2815
2816
        if (empty($attachments)) {
2817
            return '';
2818
        }
2819
2820
        $currentUserId = api_get_user_id();
2821
2822
        $listItems = '<ul class="fa-ul">';
2823
2824
        $deleteIcon = Display::return_icon(
2825
            'delete.png',
2826
            get_lang('DeleteAttachment'),
2827
            ['style' => 'display: inline-block'],
2828
            ICON_SIZE_TINY
2829
        );
2830
        $deleteAttrs = ['class' => 'btn-portfolio-delete'];
2831
2832
        /** @var PortfolioAttachment $attachment */
2833
        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...
2834
            $downloadParams = http_build_query(['action' => 'download_attachment', 'file' => $attachment->getPath()]);
2835
            $deleteParams = http_build_query(['action' => 'delete_attachment', 'file' => $attachment->getPath()]);
2836
2837
            $listItems .= '<li>'
2838
                .'<span class="fa-li fa fa-paperclip" aria-hidden="true"></span>'
2839
                .Display::url(
2840
                    Security::remove_XSS($attachment->getFilename()),
2841
                    $this->baseUrl.$downloadParams
2842
                );
2843
2844
            if ($currentUserId === $postOwnerId) {
2845
                $listItems .= PHP_EOL.Display::url($deleteIcon, $this->baseUrl.$deleteParams, $deleteAttrs);
2846
            }
2847
2848
            if ($attachment->getComment()) {
2849
                $listItems .= PHP_EOL.Display::span(
2850
                        Security::remove_XSS($attachment->getComment()),
2851
                        ['class' => 'text-muted']
2852
                    );
2853
            }
2854
2855
            $listItems .= '</li>';
2856
        }
2857
2858
        $listItems .= '</ul>';
2859
2860
        if ($includeHeader) {
2861
            $listItems = Display::page_subheader(get_lang('AttachmentFiles'), null, 'h5', ['class' => 'h4'])
2862
                .$listItems;
2863
        }
2864
2865
        return $listItems;
2866
    }
2867
2868
    private function generateItemContent(Portfolio $item): string
2869
    {
2870
        $originId = $item->getOrigin();
2871
2872
        if (empty($originId)) {
2873
            return $item->getContent();
2874
        }
2875
2876
        $em = Database::getManager();
2877
2878
        $originContent = '';
2879
        $originContentFooter = '';
2880
2881
        if (Portfolio::TYPE_ITEM === $item->getOriginType()) {
2882
            $origin = $em->find(Portfolio::class, $item->getOrigin());
2883
2884
            if ($origin) {
2885
                $originContent = $origin->getContent();
2886
                $originContentFooter = vsprintf(
2887
                    get_lang('OriginallyPublishedAsXTitleByYUser'),
2888
                    [
2889
                        "<cite>{$origin->getTitle(true)}</cite>",
2890
                        $origin->getUser()->getCompleteName(),
2891
                    ]
2892
                );
2893
            }
2894
        } elseif (Portfolio::TYPE_COMMENT === $item->getOriginType()) {
2895
            $origin = $em->find(PortfolioComment::class, $item->getOrigin());
2896
2897
            if ($origin) {
2898
                $originContent = $origin->getContent();
2899
                $originContentFooter = vsprintf(
2900
                    get_lang('OriginallyCommentedByXUserInYItem'),
2901
                    [
2902
                        $origin->getAuthor()->getCompleteName(),
2903
                        "<cite>{$origin->getItem()->getTitle(true)}</cite>",
2904
                    ]
2905
                );
2906
            }
2907
        }
2908
2909
        if ($originContent) {
2910
            return "<figure>
2911
                    <blockquote>$originContent</blockquote>
2912
                    <figcaption style=\"margin-bottom: 10px;\">$originContentFooter</figcaption>
2913
                </figure>
2914
                <div class=\"clearfix\">".Security::remove_XSS($item->getContent()).'</div>'
2915
            ;
2916
        }
2917
2918
        return Security::remove_XSS($item->getContent());
2919
    }
2920
2921
    private function getItemsInHtmlFormatted(array $items): array
2922
    {
2923
        $itemsHtml = [];
2924
2925
        /** @var Portfolio $item */
2926
        foreach ($items as $item) {
2927
            $creationDate = api_convert_and_format_date($item->getCreationDate());
2928
            $updateDate = api_convert_and_format_date($item->getUpdateDate());
2929
2930
            $metadata = '<ul class="list-unstyled text-muted">';
2931
2932
            if ($item->getSession()) {
2933
                $metadata .= '<li>'.get_lang('Course').': '.$item->getSession()->getName().' ('
2934
                    .$item->getCourse()->getTitle().') </li>';
2935
            } elseif (!$item->getSession() && $item->getCourse()) {
2936
                $metadata .= '<li>'.get_lang('Course').': '.$item->getCourse()->getTitle().'</li>';
2937
            }
2938
2939
            $metadata .= '<li>'.sprintf(get_lang('CreationDateXDate'), $creationDate).'</li>';
2940
            $metadata .= '<li>'.sprintf(get_lang('UpdateDateXDate'), $updateDate).'</li>';
2941
2942
            if ($item->getCategory()) {
2943
                $metadata .= '<li>'.sprintf(get_lang('CategoryXName'), $item->getCategory()->getTitle()).'</li>';
2944
            }
2945
2946
            $metadata .= '</ul>';
2947
2948
            $itemContent = $this->generateItemContent($item);
2949
2950
            $itemsHtml[] = Display::panel($itemContent, Security::remove_XSS($item->getTitle()), '', 'info', $metadata);
2951
        }
2952
2953
        return $itemsHtml;
2954
    }
2955
2956
    private function getCommentsInHtmlFormatted(array $comments): array
2957
    {
2958
        $commentsHtml = [];
2959
2960
        /** @var PortfolioComment $comment */
2961
        foreach ($comments as $comment) {
2962
            $item = $comment->getItem();
2963
            $date = api_convert_and_format_date($comment->getDate());
2964
2965
            $metadata = '<ul class="list-unstyled text-muted">';
2966
            $metadata .= '<li>'.sprintf(get_lang('DateXDate'), $date).'</li>';
2967
            $metadata .= '<li>'.sprintf(get_lang('PortfolioItemTitleXName'), Security::remove_XSS($item->getTitle()))
2968
                .'</li>';
2969
            $metadata .= '</ul>';
2970
2971
            $commentsHtml[] = Display::panel(
2972
                Security::remove_XSS($comment->getContent()),
2973
                '',
2974
                '',
2975
                'default',
2976
                $metadata
2977
            );
2978
        }
2979
2980
        return $commentsHtml;
2981
    }
2982
2983
    private function fixImagesSourcesToHtml(string $htmlContent): string
2984
    {
2985
        $doc = new DOMDocument();
2986
        @$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

2986
        /** @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...
2987
2988
        $elements = $doc->getElementsByTagName('img');
2989
2990
        if (empty($elements->length)) {
2991
            return $htmlContent;
2992
        }
2993
2994
        $webCoursePath = api_get_path(WEB_COURSE_PATH);
2995
        $webUploadPath = api_get_path(WEB_UPLOAD_PATH);
2996
2997
        /** @var \DOMElement $element */
2998
        foreach ($elements as $element) {
2999
            $src = trim($element->getAttribute('src'));
3000
3001
            if (strpos($src, 'http') === 0) {
3002
                continue;
3003
            }
3004
3005
            if (strpos($src, '/app/upload/') === 0) {
3006
                $element->setAttribute(
3007
                    'src',
3008
                    preg_replace('/\/app/upload\//', $webUploadPath, $src, 1)
3009
                );
3010
3011
                continue;
3012
            }
3013
3014
            if (strpos($src, '/courses/') === 0) {
3015
                $element->setAttribute(
3016
                    'src',
3017
                    preg_replace('/\/courses\//', $webCoursePath, $src, 1)
3018
                );
3019
3020
                continue;
3021
            }
3022
        }
3023
3024
        return $doc->saveHTML();
3025
    }
3026
3027
    private function formatZipIndexFile(HTML_Table $tblItems, HTML_Table $tblComments): string
3028
    {
3029
        $htmlContent = Display::page_header($this->owner->getCompleteNameWithUsername());
3030
        $htmlContent .= Display::page_subheader2(get_lang('PortfolioItems'));
3031
3032
        $htmlContent .= $tblItems->getRowCount() > 0
3033
            ? $tblItems->toHtml()
3034
            : Display::return_message(get_lang('NoItemsInYourPortfolio'), 'warning');
3035
3036
        $htmlContent .= Display::page_subheader2(get_lang('PortfolioCommentsMade'));
3037
3038
        $htmlContent .= $tblComments->getRowCount() > 0
3039
            ? $tblComments->toHtml()
3040
            : Display::return_message(get_lang('YouHaveNotCommented'), 'warning');
3041
3042
        $webAssetsPath = api_get_path(WEB_PUBLIC_PATH).'assets/';
3043
3044
        $doc = new DOMDocument();
3045
        @$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

3045
        /** @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...
3046
3047
        $stylesheet1 = $doc->createElement('link');
3048
        $stylesheet1->setAttribute('rel', 'stylesheet');
3049
        $stylesheet1->setAttribute('href', $webAssetsPath.'bootstrap/dist/css/bootstrap.min.css');
3050
        $stylesheet2 = $doc->createElement('link');
3051
        $stylesheet2->setAttribute('rel', 'stylesheet');
3052
        $stylesheet2->setAttribute('href', $webAssetsPath.'fontawesome/css/font-awesome.min.css');
3053
        $stylesheet3 = $doc->createElement('link');
3054
        $stylesheet3->setAttribute('rel', 'stylesheet');
3055
        $stylesheet3->setAttribute('href', ChamiloApi::getEditorDocStylePath());
3056
3057
        $head = $doc->createElement('head');
3058
        $head->appendChild($stylesheet1);
3059
        $head->appendChild($stylesheet2);
3060
        $head->appendChild($stylesheet3);
3061
3062
        $doc->documentElement->insertBefore(
3063
            $head,
3064
            $doc->getElementsByTagName('body')->item(0)
3065
        );
3066
3067
        return $doc->saveHTML();
3068
    }
3069
3070
    /**
3071
     * It parsers a title for a variable in lang.
3072
     *
3073
     * @param $defaultDisplayText
3074
     *
3075
     * @return string
3076
     */
3077
    private function getLanguageVariable($defaultDisplayText)
3078
    {
3079
        $variableLanguage = api_replace_dangerous_char(strtolower($defaultDisplayText));
3080
        $variableLanguage = preg_replace('/[^A-Za-z0-9\_]/', '', $variableLanguage); // Removes special chars except underscore.
3081
        if (is_numeric($variableLanguage[0])) {
3082
            $variableLanguage = '_'.$variableLanguage;
3083
        }
3084
        $variableLanguage = api_underscore_to_camel_case($variableLanguage);
3085
3086
        return $variableLanguage;
3087
    }
3088
3089
    /**
3090
     * It translates the text as parameter.
3091
     *
3092
     * @param $defaultDisplayText
3093
     *
3094
     * @return mixed
3095
     */
3096
    private function translateDisplayName($defaultDisplayText)
3097
    {
3098
        $variableLanguage = $this->getLanguageVariable($defaultDisplayText);
3099
3100
        return isset($GLOBALS[$variableLanguage]) ? $GLOBALS[$variableLanguage] : $defaultDisplayText;
3101
    }
3102
}
3103