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

PortfolioController::getLanguageVariable()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 10
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Component\Utils\ChamiloApi;
6
use Chamilo\CoreBundle\Entity\Course as CourseEntity;
7
use Chamilo\CoreBundle\Entity\ExtraField as ExtraFieldEntity;
8
use Chamilo\CoreBundle\Entity\ExtraFieldRelTag;
9
use Chamilo\CoreBundle\Entity\Portfolio;
10
use Chamilo\CoreBundle\Entity\PortfolioAttachment;
11
use Chamilo\CoreBundle\Entity\PortfolioCategory;
12
use Chamilo\CoreBundle\Entity\PortfolioComment;
13
use Chamilo\CoreBundle\Entity\Tag;
14
use Chamilo\UserBundle\Entity\User;
15
use Doctrine\ORM\Query\Expr\Join;
16
use Symfony\Component\Filesystem\Filesystem;
17
use Symfony\Component\HttpFoundation\Request as HttpRequest;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, HttpRequest. Consider defining an alias.

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

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

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

// Bar.php
namespace OtherDir;

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

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

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

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

// Bar.php
namespace OtherDir;

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

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

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

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

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

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

2948
        /** @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...
2949
2950
        $elements = $doc->getElementsByTagName('img');
2951
2952
        if (empty($elements->length)) {
2953
            return $htmlContent;
2954
        }
2955
2956
        $webCoursePath = api_get_path(WEB_COURSE_PATH);
2957
        $webUploadPath = api_get_path(WEB_UPLOAD_PATH);
2958
2959
        /** @var \DOMElement $element */
2960
        foreach ($elements as $element) {
2961
            $src = trim($element->getAttribute('src'));
2962
2963
            if (strpos($src, 'http') === 0) {
2964
                continue;
2965
            }
2966
2967
            if (strpos($src, '/app/upload/') === 0) {
2968
                $element->setAttribute(
2969
                    'src',
2970
                    preg_replace('/\/app/upload\//', $webUploadPath, $src, 1)
2971
                );
2972
2973
                continue;
2974
            }
2975
2976
            if (strpos($src, '/courses/') === 0) {
2977
                $element->setAttribute(
2978
                    'src',
2979
                    preg_replace('/\/courses\//', $webCoursePath, $src, 1)
2980
                );
2981
2982
                continue;
2983
            }
2984
        }
2985
2986
        return $doc->saveHTML();
2987
    }
2988
2989
    private function formatZipIndexFile(HTML_Table $tblItems, HTML_Table $tblComments): string
2990
    {
2991
        $htmlContent = Display::page_header($this->owner->getCompleteNameWithUsername());
2992
        $htmlContent .= Display::page_subheader2(get_lang('PortfolioItems'));
2993
2994
        $htmlContent .= $tblItems->getRowCount() > 0
2995
            ? $tblItems->toHtml()
2996
            : Display::return_message(get_lang('NoItemsInYourPortfolio'), 'warning');
2997
2998
        $htmlContent .= Display::page_subheader2(get_lang('PortfolioCommentsMade'));
2999
3000
        $htmlContent .= $tblComments->getRowCount() > 0
3001
            ? $tblComments->toHtml()
3002
            : Display::return_message(get_lang('YouHaveNotCommented'), 'warning');
3003
3004
        $webAssetsPath = api_get_path(WEB_PUBLIC_PATH).'assets/';
3005
3006
        $doc = new DOMDocument();
3007
        @$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

3007
        /** @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...
3008
3009
        $stylesheet1 = $doc->createElement('link');
3010
        $stylesheet1->setAttribute('rel', 'stylesheet');
3011
        $stylesheet1->setAttribute('href', $webAssetsPath.'bootstrap/dist/css/bootstrap.min.css');
3012
        $stylesheet2 = $doc->createElement('link');
3013
        $stylesheet2->setAttribute('rel', 'stylesheet');
3014
        $stylesheet2->setAttribute('href', $webAssetsPath.'fontawesome/css/font-awesome.min.css');
3015
        $stylesheet3 = $doc->createElement('link');
3016
        $stylesheet3->setAttribute('rel', 'stylesheet');
3017
        $stylesheet3->setAttribute('href', ChamiloApi::getEditorDocStylePath());
3018
3019
        $head = $doc->createElement('head');
3020
        $head->appendChild($stylesheet1);
3021
        $head->appendChild($stylesheet2);
3022
        $head->appendChild($stylesheet3);
3023
3024
        $doc->documentElement->insertBefore(
3025
            $head,
3026
            $doc->getElementsByTagName('body')->item(0)
3027
        );
3028
3029
        return $doc->saveHTML();
3030
    }
3031
3032
    /**
3033
     * It parsers a title for a variable in lang.
3034
     *
3035
     * @param $defaultDisplayText
3036
     *
3037
     * @return string
3038
     */
3039
    private function getLanguageVariable($defaultDisplayText)
3040
    {
3041
        $variableLanguage = api_replace_dangerous_char(strtolower($defaultDisplayText));
3042
        $variableLanguage = preg_replace('/[^A-Za-z0-9\_]/', '', $variableLanguage); // Removes special chars except underscore.
3043
        if (is_numeric($variableLanguage[0])) {
3044
            $variableLanguage = '_'.$variableLanguage;
3045
        }
3046
        $variableLanguage = api_underscore_to_camel_case($variableLanguage);
3047
3048
        return $variableLanguage;
3049
    }
3050
3051
    /**
3052
     * It translates the text as parameter.
3053
     *
3054
     * @param $defaultDisplayText
3055
     *
3056
     * @return mixed
3057
     */
3058
    private function translateDisplayName($defaultDisplayText)
3059
    {
3060
        $variableLanguage = $this->getLanguageVariable($defaultDisplayText);
3061
3062
        return isset($GLOBALS[$variableLanguage]) ? $GLOBALS[$variableLanguage] : $defaultDisplayText;
3063
    }
3064
}
3065