Passed
Push — 1.11.x ( 80e6f3...6f3dc2 )
by
unknown
14:13
created

PortfolioController::addItem()   C

Complexity

Conditions 9
Paths 32

Size

Total Lines 173
Code Lines 98

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 98
c 0
b 0
f 0
nc 32
nop 0
dl 0
loc 173
rs 6.488

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

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

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

2895
        /** @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...
2896
2897
        $stylesheet1 = $doc->createElement('link');
2898
        $stylesheet1->setAttribute('rel', 'stylesheet');
2899
        $stylesheet1->setAttribute('href', $webAssetsPath.'bootstrap/dist/css/bootstrap.min.css');
2900
        $stylesheet2 = $doc->createElement('link');
2901
        $stylesheet2->setAttribute('rel', 'stylesheet');
2902
        $stylesheet2->setAttribute('href', $webAssetsPath.'fontawesome/css/font-awesome.min.css');
2903
        $stylesheet3 = $doc->createElement('link');
2904
        $stylesheet3->setAttribute('rel', 'stylesheet');
2905
        $stylesheet3->setAttribute('href', ChamiloApi::getEditorDocStylePath());
2906
2907
        $head = $doc->createElement('head');
2908
        $head->appendChild($stylesheet1);
2909
        $head->appendChild($stylesheet2);
2910
        $head->appendChild($stylesheet3);
2911
2912
        $doc->documentElement->insertBefore(
2913
            $head,
2914
            $doc->getElementsByTagName('body')->item(0)
2915
        );
2916
2917
        return $doc->saveHTML();
2918
    }
2919
2920
    /**
2921
     * It parsers a title for a variable in lang.
2922
     *
2923
     * @param $defaultDisplayText
2924
     * @return string
2925
     */
2926
    private function getLanguageVariable($defaultDisplayText)
2927
    {
2928
        $variableLanguage = api_replace_dangerous_char(strtolower($defaultDisplayText));
2929
        $variableLanguage = str_replace('-', '_', $variableLanguage);
2930
        $variableLanguage = api_underscore_to_camel_case($variableLanguage);
2931
2932
        return $variableLanguage;
2933
    }
2934
2935
    /**
2936
     * It translates the text as parameter.
2937
     *
2938
     * @param $defaultDisplayText
2939
     * @return mixed
2940
     */
2941
    private function translateDisplayName($defaultDisplayText)
2942
    {
2943
        $variableLanguage = $this->getLanguageVariable($defaultDisplayText);
2944
2945
        return isset($GLOBALS[$variableLanguage]) ? $GLOBALS[$variableLanguage] : $defaultDisplayText;
2946
    }
2947
}
2948