Passed
Push — 1.11.x ( c147ce...1b80ca )
by
unknown
09:38 queued 12s
created

PortfolioController::editItem()   D

Complexity

Conditions 12
Paths 256

Size

Total Lines 171
Code Lines 94

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 12
eloc 94
nc 256
nop 1
dl 0
loc 171
rs 4.4775
c 2
b 0
f 0

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

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

2845
        /** @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...
2846
2847
        $elements = $doc->getElementsByTagName('img');
2848
2849
        if (empty($elements->length)) {
2850
            return $htmlContent;
2851
        }
2852
2853
        $webCoursePath = api_get_path(WEB_COURSE_PATH);
2854
        $webUploadPath = api_get_path(WEB_UPLOAD_PATH);
2855
2856
        /** @var \DOMElement $element */
2857
        foreach ($elements as $element) {
2858
            $src = trim($element->getAttribute('src'));
2859
2860
            if (strpos($src, 'http') === 0) {
2861
                continue;
2862
            }
2863
2864
            if (strpos($src, '/app/upload/') === 0) {
2865
                $element->setAttribute(
2866
                    'src',
2867
                    preg_replace('/\/app/upload\//', $webUploadPath, $src, 1)
2868
                );
2869
2870
                continue;
2871
            }
2872
2873
            if (strpos($src, '/courses/') === 0) {
2874
                $element->setAttribute(
2875
                    'src',
2876
                    preg_replace('/\/courses\//', $webCoursePath, $src, 1)
2877
                );
2878
2879
                continue;
2880
            }
2881
        }
2882
2883
        return $doc->saveHTML();
2884
    }
2885
2886
    private function formatZipIndexFile(HTML_Table $tblItems, HTML_Table $tblComments): string
2887
    {
2888
        $htmlContent = Display::page_header($this->owner->getCompleteNameWithUsername());
2889
        $htmlContent .= Display::page_subheader2(get_lang('PortfolioItems'));
2890
2891
        $htmlContent .= $tblItems->getRowCount() > 0
2892
            ? $tblItems->toHtml()
2893
            : Display::return_message(get_lang('NoItemsInYourPortfolio'), 'warning');
2894
2895
        $htmlContent .= Display::page_subheader2(get_lang('PortfolioCommentsMade'));
2896
2897
        $htmlContent .= $tblComments->getRowCount() > 0
2898
            ? $tblComments->toHtml()
2899
            : Display::return_message(get_lang('YouHaveNotCommented'), 'warning');
2900
2901
        $webAssetsPath = api_get_path(WEB_PUBLIC_PATH).'assets/';
2902
2903
        $doc = new DOMDocument();
2904
        @$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

2904
        /** @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...
2905
2906
        $stylesheet1 = $doc->createElement('link');
2907
        $stylesheet1->setAttribute('rel', 'stylesheet');
2908
        $stylesheet1->setAttribute('href', $webAssetsPath.'bootstrap/dist/css/bootstrap.min.css');
2909
        $stylesheet2 = $doc->createElement('link');
2910
        $stylesheet2->setAttribute('rel', 'stylesheet');
2911
        $stylesheet2->setAttribute('href', $webAssetsPath.'fontawesome/css/font-awesome.min.css');
2912
        $stylesheet3 = $doc->createElement('link');
2913
        $stylesheet3->setAttribute('rel', 'stylesheet');
2914
        $stylesheet3->setAttribute('href', ChamiloApi::getEditorDocStylePath());
2915
2916
        $head = $doc->createElement('head');
2917
        $head->appendChild($stylesheet1);
2918
        $head->appendChild($stylesheet2);
2919
        $head->appendChild($stylesheet3);
2920
2921
        $doc->documentElement->insertBefore(
2922
            $head,
2923
            $doc->getElementsByTagName('body')->item(0)
2924
        );
2925
2926
        return $doc->saveHTML();
2927
    }
2928
2929
    /**
2930
     * It parsers a title for a variable in lang.
2931
     *
2932
     * @param $defaultDisplayText
2933
     *
2934
     * @return string
2935
     */
2936
    private function getLanguageVariable($defaultDisplayText)
2937
    {
2938
        $variableLanguage = api_replace_dangerous_char(strtolower($defaultDisplayText));
2939
        $variableLanguage = preg_replace('/[^A-Za-z0-9\_]/', '', $variableLanguage); // Removes special chars except underscore.
2940
        if (is_numeric($variableLanguage[0])) {
2941
            $variableLanguage = '_'.$variableLanguage;
2942
        }
2943
        $variableLanguage = api_underscore_to_camel_case($variableLanguage);
2944
2945
        return $variableLanguage;
2946
    }
2947
2948
    /**
2949
     * It translates the text as parameter.
2950
     *
2951
     * @param $defaultDisplayText
2952
     *
2953
     * @return mixed
2954
     */
2955
    private function translateDisplayName($defaultDisplayText)
2956
    {
2957
        $variableLanguage = $this->getLanguageVariable($defaultDisplayText);
2958
2959
        return isset($GLOBALS[$variableLanguage]) ? $GLOBALS[$variableLanguage] : $defaultDisplayText;
2960
    }
2961
}
2962