Passed
Pull Request — master (#6989)
by
unknown
08:41
created

postIsEditableByStudent()   C

Complexity

Conditions 13
Paths 50

Size

Total Lines 61
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 13
eloc 31
nc 50
nop 2
dl 0
loc 61
rs 6.6166
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\AbstractResource;
6
use Chamilo\CoreBundle\Entity\Course;
7
use Chamilo\CoreBundle\Entity\GradebookLink;
8
use Chamilo\CoreBundle\Entity\ResourceLink;
9
use Chamilo\CoreBundle\Entity\Session as SessionEntity;
10
use Chamilo\CoreBundle\Entity\User;
11
use Chamilo\CoreBundle\Enums\ActionIcon;
12
use Chamilo\CoreBundle\Framework\Container;
13
use Chamilo\CoreBundle\Repository\TrackEDefaultRepository;
14
use Chamilo\CourseBundle\Entity\CForum;
15
use Chamilo\CourseBundle\Entity\CForumAttachment;
16
use Chamilo\CourseBundle\Entity\CForumCategory;
17
use Chamilo\CourseBundle\Entity\CForumNotification;
18
use Chamilo\CourseBundle\Entity\CForumPost;
19
use Chamilo\CourseBundle\Entity\CForumThread;
20
use Chamilo\CourseBundle\Entity\CGroup;
21
use Chamilo\CourseBundle\Entity\CLp;
22
use Chamilo\CourseBundle\Entity\CLpItem;
23
use ChamiloSession as Session;
24
use Doctrine\Common\Collections\Criteria;
25
use Symfony\Component\HttpFoundation\File\UploadedFile;
26
27
28
/**
29
 * @todo convert this library into a class
30
 */
31
function handleForum($url)
32
{
33
    $id = isset($_REQUEST['id']) ? (int) $_REQUEST['id'] : null;
34
    $lp_id = isset($_REQUEST['lp_id']) ? (int) $_REQUEST['lp_id'] : null;
35
    $content = $_REQUEST['content'] ?? '';
36
    $action = $_REQUEST['action'] ?? null;
37
    $isAllowedToEdit = api_is_allowed_to_edit(false, true);
38
    if (!$isAllowedToEdit) {
39
        $isAllowedToEdit = ('notify' === $action && !api_is_anonymous() && api_is_allowed_to_session_edit(false, true));
40
    }
41
42
    if ($isAllowedToEdit) {
43
        $course = api_get_course_entity();
44
        $session = api_get_session_entity();
45
        $linksRepo = Database::getManager()->getRepository(ResourceLink::class);
46
        $repo = null;
47
        switch ($content) {
48
            case 'forumcategory':
49
                $repo = Container::getForumCategoryRepository();
50
                break;
51
            case 'forum':
52
                $repo = Container::getForumRepository();
53
                break;
54
            case 'thread':
55
                $repo = Container::getForumThreadRepository();
56
                break;
57
        }
58
59
        /** @var AbstractResource|null $resource */
60
        $resource = null;
61
        if ($repo && $id) {
62
            $resource = $repo->find($id);
63
        }
64
65
        switch ($action) {
66
            case 'add_forum':
67
                $formContent = forumForm(null, $lp_id);
68
69
                return $formContent;
70
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
71
            case 'edit_forum':
72
                $repo = Container::getForumRepository();
73
                $resource = $repo->find($id);
74
                $formContent = forumForm($resource, $lp_id);
75
76
                return $formContent;
77
                break;
78
            case 'add_category':
79
                $formContent = getForumCategoryAddForm($lp_id);
80
81
                return $formContent;
82
                break;
83
            case 'edit_category':
84
                $repo = Container::getForumCategoryRepository();
85
                $category = $repo->find($id);
86
                $formContent = editForumCategoryForm($category);
87
88
                return $formContent;
89
                break;
90
            case 'notify':
91
                $message = set_notification($content, $id);
92
                Display::addFlash(Display::return_message($message, 'confirm', false));
93
94
                header('Location: '.$url);
95
                exit;
96
                break;
97
            case 'lock':
98
            case 'unlock':
99
                if (null !== $resource) {
100
                    if ('lock' === $action) {
101
                        $locked = 1;
102
                        $message = get_lang('Locked: students can no longer post new messages in this forum category, forum or thread but they can still read the messages that were already posted');
103
                    } else {
104
                        $locked = 0;
105
                        $message = get_lang('Unlocked: learners can post new messages in this forum category, forum or thread');
106
                    }
107
108
                    $resource->setLocked($locked);
109
                    $repo->update($resource);
110
111
                    Display::addFlash(
112
                        Display::return_message($message, 'confirmation', false)
113
                    );
114
                }
115
116
                header('Location: '.$url);
117
                exit;
118
                break;
119
            case 'move':
120
                moveUpDown($content, $_REQUEST['direction'] ?? '', $id);
121
                header('Location: '.$url);
122
                exit;
123
                break;
124
            case 'move_thread':
125
                $message = move_thread_form();
126
127
                return $message;
128
                break;
129
            case 'visible':
130
            case 'invisible':
131
                if (null !== $resource) {
132
                    if ('visible' === $action) {
133
                        $repo->setVisibilityPublished($resource, $course, $session);
134
                    } else {
135
                        $repo->setVisibilityPending($resource, $course, $session);
136
                    }
137
138
                    if ('visible' === $action) {
139
                        handle_mail_cue($content, $id);
140
                    }
141
142
                    Display::addFlash(
143
                        Display::return_message(get_lang('Updated'), 'confirmation', false)
144
                    );
145
                }
146
                header('Location: '.$url);
147
                exit;
148
                break;
149
            case 'delete_category':
150
                if ($resource) {
151
                    $linksRepo->removeByResourceInContext($resource, $course, $session);
152
153
                    // Manually register thread deletion event because the resource is not removed via Doctrine
154
                    $trackRepo = Container::$container->get(TrackEDefaultRepository::class);
155
                    $node = $resource->getResourceNode();
156
                    if ($node) {
157
                        $trackRepo->registerResourceEvent($node, 'deletion', api_get_user_id(), api_get_course_int_id(), api_get_session_id());
158
                    }
159
160
                    Display::addFlash(
161
                        Display::return_message(get_lang('Forum category deleted'), 'confirmation', false)
162
                    );
163
                }
164
                header('Location: '.$url);
165
                exit;
166
                break;
167
            case 'delete_forum':
168
                if ($resource) {
169
                    $linksRepo->removeByResourceInContext($resource, $course, $session);
170
171
                    // Register forum deletion manually as it's not deleted via Doctrine
172
                    $trackRepo = Container::$container->get(TrackEDefaultRepository::class);
173
                    $node = $resource->getResourceNode();
174
                    if ($node) {
175
                        $trackRepo->registerResourceEvent($node, 'deletion', api_get_user_id(), api_get_course_int_id(), api_get_session_id());
176
                    }
177
178
                    Display::addFlash(Display::return_message(get_lang('Forum deleted'), 'confirmation', false));
179
                }
180
181
                header('Location: '.$url);
182
                exit;
183
                break;
184
            case 'delete_thread':
185
                $locked = api_resource_is_locked_by_gradebook($id, LINK_FORUM_THREAD);
186
                if ($resource && false === $locked) {
187
                    $linksRepo->removeByResourceInContext($resource, $course, $session);
188
189
                    SkillModel::deleteSkillsFromItem($id, ITEM_TYPE_FORUM_THREAD);
190
                    $link_info = GradebookUtils::isResourceInCourseGradebook(
191
                        api_get_course_int_id(),
192
                        5,
193
                        $id,
194
                        api_get_session_id()
195
                    );
196
197
                    if (false !== $link_info) {
198
                        $link_id = $link_info['id'];
199
                        GradebookUtils::remove_resource_from_course_gradebook($link_id);
200
                    }
201
202
                    // Manually register thread deletion event because the resource is not removed via Doctrine
203
                    $trackRepo = Container::$container->get(TrackEDefaultRepository::class);
204
                    $node = $resource->getResourceNode();
205
                    if ($node) {
206
                        $trackRepo->registerResourceEvent($node, 'deletion', api_get_user_id(), api_get_course_int_id(), api_get_session_id());
207
                    }
208
209
                    Display::addFlash(Display::return_message(get_lang('Thread deleted'), 'confirmation', false));
210
                }
211
212
                header('Location: '.$url);
213
                exit;
214
                break;
215
        }
216
    }
217
}
218
219
/**
220
 * This function displays the form that is used to add a forum category
221
 * or validates the form input, depending on the context.
222
 *
223
 * @author Patrick Cool <[email protected]>, Ghent University
224
 * @author Juan Carlos Raña Trabado (return to lp_id)
225
 *
226
 * @version may 2011, Chamilo 1.8.8
227
 * @throws Exception
228
 */
229
function getForumCategoryAddForm(int $lp_id = null): string
230
{
231
    $form = new FormValidator(
232
        'forumcategory',
233
        'post',
234
        'index.php?'.api_get_cidreq().'&action=add_category'
235
    );
236
    // hidden field if from learning path
237
    $form->addHidden('lp_id', $lp_id);
238
    $form->addHidden('action', 'add_category');
239
    // Setting the form elements.
240
    $form->addHeader(get_lang('Add forum category'));
241
    $form->addText('forum_category_title', get_lang('Title'), true, ['autofocus']);
242
    $form->applyFilter('forum_category_title', 'html_filter');
243
    $form->addHtmlEditor(
244
        'forum_category_comment',
245
        get_lang('Description'),
246
        false,
247
        false,
248
        ['ToolbarSet' => 'Forum', 'Width' => '98%', 'Height' => '200']
249
    );
250
251
    $extraField = new ExtraField('forum_category');
252
    $extraField->addElements(
253
        $form,
254
        null,
255
        [], //exclude
256
        false, // filter
257
        false, // tag as select
258
        [], //show only fields
259
        [], // order fields
260
        [] // extra data
261
    );
262
263
    $form->addButtonCreate(get_lang('Create category'), 'SubmitForumCategory');
264
265
    // Setting the rules.
266
    $form->addRule('forum_category_title', get_lang('Required field'), 'required');
267
268
    // The validation or display
269
    if ($form->validate()) {
270
        $check = Security::check_token('post');
271
        if ($check) {
272
            $values = $form->exportValues();
273
            saveForumCategory($values);
274
        }
275
        Security::clear_token();
276
        return '';
277
    } else {
278
        $token = Security::get_token();
279
        $form->addElement('hidden', 'sec_token');
280
        $form->setConstants(['sec_token' => $token]);
281
282
        return $form->returnForm();
283
    }
284
}
285
286
/**
287
 * Generates a forum creation or edition form
288
 */
289
function forumForm(CForum $forum = null, int $lp_id = null): string
290
{
291
    $_course = api_get_course_info();
292
    // The header for the form
293
    $form_title = get_lang('Add a forum');
294
    $action = 'add_forum';
295
    $id = 0;
296
    if ($forum) {
297
        $id = $forum->getIid();
298
        $action = 'edit_forum';
299
        $form_title = get_lang('Edit forum');
300
    }
301
302
    $form = new FormValidator(
303
        'forumcategory',
304
        'post',
305
        'index.php?'.api_get_cidreq().'&action='.$action.'&id='.$id
306
    );
307
    $form->addHidden('action', $action);
308
    $form->addHeader($form_title);
309
310
    // We have a hidden field if we are editing.
311
    if ($forum) {
312
        $form->addHidden('forum_id', $id);
313
    }
314
315
    // hidden field if from learning path
316
    $form->addHidden('lp_id', $lp_id);
317
318
    // The title of the forum
319
    $form->addText('forum_title', get_lang('Title'), true, ['autofocus']);
320
    $form->applyFilter('forum_title', 'html_filter');
321
322
    // The comment of the forum.
323
    $form->addHtmlEditor(
324
        'forum_comment',
325
        get_lang('Description'),
326
        false,
327
        false,
328
        ['ToolbarSet' => 'Forum', 'Width' => '98%', 'Height' => '200']
329
    );
330
331
    // Dropdown list: Forum categories
332
    $forum_categories = get_forum_categories();
333
    $forum_categories_titles = [];
334
    foreach ($forum_categories as $value) {
335
        $forum_categories_titles[$value->getIid()] = $value->getTitle();
336
    }
337
    $form->addSelect(
338
        'forum_category',
339
        get_lang('Create in category'),
340
        $forum_categories_titles
341
    );
342
    $form->applyFilter('forum_category', 'html_filter');
343
344
    if (COURSE_VISIBILITY_OPEN_WORLD == $_course['visibility']) {
345
        // This is for horizontal
346
        $group = [];
347
        $group[] = $form->createElement('radio', 'allow_anonymous', null, get_lang('Yes'), 1);
348
        $group[] = $form->createElement('radio', 'allow_anonymous', null, get_lang('No'), 0);
349
        $form->addGroup($group, 'allow_anonymous_group', get_lang('Allow anonymous posts?'));
350
    }
351
352
    $form->addButtonAdvancedSettings('advanced_params');
353
    $form->addHtml('<div id="advanced_params_options" style="display:none">');
354
355
    $form->addDateTimePicker(
356
        'start_time',
357
        [
358
            get_lang('Publication date'),
359
            get_lang('The forum will be visible starting from this date'),
360
        ],
361
        ['id' => 'start_time']
362
    );
363
364
    $form->addDateTimePicker(
365
        'end_time',
366
        [get_lang('Closing date'), get_lang('Once this date has passed, the forum will be closed')],
367
        ['id' => 'end_time']
368
    );
369
370
    $form->addRule(
371
        ['start_time', 'end_time'],
372
        get_lang('Start date must be before the end date'),
373
        'compare_datetime_text',
374
        '< allow_empty'
375
    );
376
377
    $group = [];
378
    $group[] = $form->createElement('radio', 'moderated', null, get_lang('Yes'), 1);
379
    $group[] = $form->createElement('radio', 'moderated', null, get_lang('No'), 0);
380
    $form->addGroup($group, 'moderated', get_lang('Moderated forum'));
381
382
    $group = [];
383
    $group[] = $form->createElement('radio', 'students_can_edit', null, get_lang('Yes'), 1);
384
    $group[] = $form->createElement('radio', 'students_can_edit', null, get_lang('No'), 0);
385
    $form->addGroup($group, 'students_can_edit_group', get_lang('Can learners edit their own posts?'));
386
387
    $group = [];
388
    $group[] = $form->createElement('radio', 'approval_direct', null, get_lang('Approval'), 1);
389
    $group[] = $form->createElement('radio', 'approval_direct', null, get_lang('Direct'), 0);
390
391
    $group = [];
392
    $group[] = $form->createElement('radio', 'allow_attachments', null, get_lang('Yes'), 1);
393
    $group[] = $form->createElement('radio', 'allow_attachments', null, get_lang('No'), 0);
394
395
    $group = [];
396
    $group[] = $form->createElement('radio', 'allow_new_threads', null, get_lang('Yes'), 1);
397
    $group[] = $form->createElement('radio', 'allow_new_threads', null, get_lang('No'), 0);
398
    $form->addGroup($group, 'allow_new_threads_group', get_lang('Allow users to start new threads'));
399
400
    $group = [];
401
    $group[] = $form->createElement('radio', 'default_view_type', null, get_lang('Flat'), 'flat');
402
    $group[] = $form->createElement('radio', 'default_view_type', null, get_lang('Threaded'), 'threaded');
403
    $group[] = $form->createElement('radio', 'default_view_type', null, get_lang('Nested'), 'nested');
404
    $form->addGroup($group, 'default_view_type_group', get_lang('Default view type'));
405
406
    // Drop down list: Groups
407
    $groups = GroupManager::get_group_list();
408
    $groups_titles[0] = get_lang('Not a group forum');
409
    foreach ($groups as $value) {
410
        $groups_titles[$value['iid']] = $value['name'];
411
    }
412
    $form->addSelect('group_forum', get_lang('For Group'), $groups_titles);
413
414
    // Public or private group forum
415
    $group = [];
416
    $group[] = $form->createElement(
417
        'radio',
418
        'public_private_group_forum',
419
        null,
420
        get_lang('Public access (access authorized to any member of the course)'),
421
        'public'
422
    );
423
    $group[] = $form->createElement(
424
        'radio',
425
        'public_private_group_forum',
426
        null,
427
        get_lang('Private access (access authorized to group members only)'),
428
        'private'
429
    );
430
    $form->addGroup(
431
        $group,
432
        'public_private_group_forum_group',
433
        get_lang(
434
            'Public access (access authorized to any member of the course)Private access (access authorized to group members only)GroupForum'
435
        )
436
    );
437
438
    // Forum image
439
    $form->addProgress();
440
441
    $form->addElement('html', '</div>');
442
443
    // The OK button
444
    if ($forum) {
445
        $form->addButtonUpdate(get_lang('Edit forum'), 'SubmitForum');
446
    } else {
447
        $form->addButtonCreate(get_lang('Create forum'), 'SubmitForum');
448
    }
449
450
    // setting the rules
451
    $form->addRule('forum_title', get_lang('Required field'), 'required');
452
    $form->addRule('forum_category', get_lang('Required field'), 'required');
453
454
    // Settings the defaults
455
    if (null === $forum) {
456
        $defaults['moderated']['moderated'] = 0;
457
        $defaults['allow_anonymous_group']['allow_anonymous'] = 0;
458
        $defaults['students_can_edit_group']['students_can_edit'] = 0;
459
        $defaults['approval_direct_group']['approval_direct'] = 0;
460
        $defaults['allow_attachments_group']['allow_attachments'] = 1;
461
        $defaults['allow_new_threads_group']['allow_new_threads'] = 1;
462
        $defaults['default_view_type_group']['default_view_type'] = api_get_setting('default_forum_view');
463
        $defaults['public_private_group_forum_group']['public_private_group_forum'] = 'public';
464
        if (isset($_GET['forumcategory'])) {
465
            $defaults['forum_category'] = Security::remove_XSS($_GET['forumcategory']);
466
        }
467
    } else {
468
        // the default values when editing = the data in the table
469
        $defaults['forum_id'] = $forum->getIid();
470
        $defaults['forum_title'] = prepare4display($forum->getTitle());
471
        $defaults['forum_comment'] = prepare4display($forum->getForumComment());
472
        $defaults['start_time'] = api_get_local_time($forum->getStartTime());
473
        $defaults['end_time'] = api_get_local_time($forum->getEndTime());
474
        $defaults['moderated']['moderated'] = $forum->isModerated();
475
        $defaults['forum_category'] = $forum->getForumCategory()->getIid();
476
        $defaults['allow_anonymous_group']['allow_anonymous'] = $forum->getAllowAnonymous();
477
        $defaults['students_can_edit_group']['students_can_edit'] = $forum->getAllowEdit();
478
        $defaults['approval_direct_group']['approval_direct'] = $forum->getApprovalDirectPost();
479
        $defaults['allow_attachments_group']['allow_attachments'] = $forum->getAllowAttachments();
480
        $defaults['allow_new_threads_group']['allow_new_threads'] = $forum->getAllowNewThreads();
481
        $defaults['default_view_type_group']['default_view_type'] = $forum->getDefaultView();
482
        $defaults['public_private_group_forum_group']['public_private_group_forum'] = $forum->getForumGroupPublicPrivate();
483
        $defaults['group_forum'] = $forum->getForumOfGroup();
484
    }
485
486
    $form->setDefaults($defaults);
487
    // Validation or display
488
    if ($form->validate()) {
489
        $check = Security::check_token('post');
490
        if ($check) {
491
            $values = $form->getSubmitValues();
492
            $forumId = store_forum($values, [], true);
493
            if ($forumId) {
494
                // SkillModel::saveSkills($form, ITEM_TYPE_FORUM, $forumId);
495
                if (isset($values['forum_id'])) {
496
                    Display::addFlash(Display::return_message(get_lang('The forum has been modified'), 'confirmation'));
497
                } else {
498
                    Display::addFlash(Display::return_message(get_lang('The forum has been added'), 'confirmation'));
499
                }
500
            }
501
            $url = api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq();
502
            header('Location: '.$url);
503
            exit;
504
        }
505
        Security::clear_token();
506
    } else {
507
        $token = Security::get_token();
508
        $form->addElement('hidden', 'sec_token');
509
        $form->setConstants(['sec_token' => $token]);
510
511
        return $form->returnForm();
512
    }
513
514
    return '';
515
}
516
517
/**
518
 * This function deletes the forum image if exists.
519
 * @author Julio Montoya <[email protected]>
520
 * @version february 2006, dokeos 1.8
521
 * @todo Implement
522
 * @throws Exception
523
 */
524
function deleteForumImage(CForum $forum): bool
525
{
526
    throw new Exception('delete_forum_image');
527
528
    return false;
0 ignored issues
show
Unused Code introduced by
return false is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
529
}
530
531
/**
532
 * Generates an HTML form to edit the forum category
533
 * @throws Exception
534
 */
535
function editForumCategoryForm(CForumCategory $category): string
536
{
537
    $categoryId = $category->getIid();
538
    $form = new FormValidator(
539
        'forumcategory',
540
        'post',
541
        'index.php?action=edit_category&'.api_get_cidreq().'&id='.$categoryId
542
    );
543
    // Setting the form elements.
544
    $form->addElement('header', '', get_lang('Edit forum category'));
545
546
    $form->addElement('hidden', 'action', 'edit_category');
547
    $form->addElement('hidden', 'forum_category_id');
548
    $form->addElement('text', 'forum_category_title', get_lang('Title'));
549
    $form->applyFilter('forum_category_title', 'html_filter');
550
551
    $form->addElement(
552
        'html_editor',
553
        'forum_category_comment',
554
        get_lang('Comment'),
555
        null,
556
        ['ToolbarSet' => 'Forum', 'Width' => '98%', 'Height' => '200']
557
    );
558
559
    $extraField = new ExtraField('forum_category');
560
    $extraField->addElements(
561
        $form,
562
        $categoryId,
563
        [], //exclude
564
        false, // filter
565
        false, // tag as select
566
        [], //show only fields
567
        [], // order fields
568
        [] // extra data
569
    );
570
571
    $form->addButtonUpdate(get_lang('Edit category'), 'SubmitEdit forumCategory');
572
573
    // Setting the default values.
574
    $defaultvalues['forum_category_id'] = $categoryId;
575
    $defaultvalues['forum_category_title'] = $category->getTitle();
576
    $defaultvalues['forum_category_comment'] = $category->getCatComment();
577
    $form->setDefaults($defaultvalues);
578
579
    // Setting the rules.
580
    $form->addRule('forum_category_title', get_lang('Required field'), 'required');
581
582
    // Validation or display
583
    if ($form->validate()) {
584
        $check = Security::check_token('post');
585
        if ($check) {
586
            $values = $form->exportValues();
587
            saveForumCategory($values);
588
        }
589
        Security::clear_token();
590
    } else {
591
        $token = Security::get_token();
592
        $form->addElement('hidden', 'sec_token');
593
        $form->setConstants(['sec_token' => $token]);
594
595
        return $form->returnForm();
596
    }
597
598
    return '';
599
}
600
601
/**
602
 * This function stores the forum category in the database.
603
 * The new category is added to the end.
604
 * @throws Exception
605
 */
606
function saveForumCategory(array $values, array $courseInfo = [], bool $showMessage = true): CForumCategory
607
{
608
    $courseInfo = empty($courseInfo) ? api_get_course_info() : $courseInfo;
609
    $course_id = $courseInfo['real_id'];
610
    $session_id = api_get_session_id();
611
    $clean_cat_title = $values['forum_category_title'];
612
    $repo = Container::getForumCategoryRepository();
613
614
    $message = '';
615
    if (isset($values['forum_category_id'])) {
616
        /** @var CForumCategory $category */
617
        $category = $repo->find($values['forum_category_id']);
618
        $category
619
            ->setCatComment($values['forum_category_comment'] ?? '')
620
            ->setTitle($values['forum_category_title'])
621
        ;
622
        $repo->update($category);
623
        $message = get_lang('The forum category has been modified');
624
625
        $logInfo = [
626
            'tool' => TOOL_FORUM,
627
            'action' => 'update-forumcategory',
628
            'action_details' => 'forumcategory',
629
            'info' => $clean_cat_title,
630
        ];
631
        Event::registerLog($logInfo);
632
633
        $values['item_id'] = $values['forum_category_id'];
634
    } else {
635
        $course = api_get_course_entity($course_id);
636
        $session = api_get_session_entity($session_id);
637
638
        $category = new CForumCategory();
639
        $category
640
            ->setTitle($clean_cat_title)
641
            ->setCatComment($values['forum_category_comment'] ?? '')
642
            ->setParent($course)
643
            ->addCourseLink($course, $session)
644
        ;
645
        $repo->create($category);
646
647
        $last_id = $category->getIid();
648
        if ($last_id > 0) {
649
            $message = get_lang('The forum category has been added');
650
        }
651
652
        $logInfo = [
653
            'tool' => TOOL_FORUM,
654
            'action' => 'new-forumcategory',
655
            'action_details' => 'forumcategory',
656
            'info' => $clean_cat_title,
657
        ];
658
        Event::registerLog($logInfo);
659
660
        $values['item_id'] = $last_id;
661
    }
662
663
    $extraFieldValue = new ExtraFieldValue('forum_category');
664
    $extraFieldValue->saveFieldValues($values);
665
666
    if ($showMessage) {
667
        Display::addFlash(Display::return_message($message, 'confirmation'));
668
    }
669
670
    return $category;
671
}
672
673
/**
674
 * Stores a new or edited forum in the database.
675
 * The new forum is added to the end of the others.
676
 * Returns an ID if the option 'returnId' was set to true, otherwise returns a confirmation message
677
 */
678
function store_forum(array $values, array $courseInfo = [], bool $returnId = false): string|int
679
{
680
    $courseInfo = empty($courseInfo) ? api_get_course_info() : $courseInfo;
681
    $courseId = $courseInfo['real_id'];
682
    $session_id = api_get_session_id();
683
684
    // Find the max forum_order for the given category. The new forum is added at the end => max cat_order + &
685
    if (null === $values['forum_category']) {
686
        $new_max = null;
687
    } else {
688
        /*$sql = "SELECT MAX(forum_order) as sort_max
689
                FROM $table_forums
690
                WHERE
691
                    c_id = $courseId AND
692
                    forum_category='".Database::escape_string($values['forum_category'])."'";
693
        $result = Database::query($sql);
694
        $row = Database::fetch_array($result);
695
        $new_max = $row['sort_max'] + 1;*/
696
    }
697
698
    // Forum images
699
    $has_attachment = false;
700
    $image_moved = true;
701
    if (!empty($_FILES['picture']['name'])) {
702
        $upload_ok = process_uploaded_file($_FILES['picture']);
703
        $has_attachment = true;
704
    }
705
706
    // Remove existing picture if it was requested.
707
    if (!empty($_POST['remove_picture'])) {
708
        //deleteForumImage($values['forum_id']);
709
    }
710
711
    $new_file_name = '';
712
    if (isset($upload_ok)) {
713
        if ($has_attachment) {
714
            throw new Exception('$has_attachment');
715
            /*$course_dir = $courseInfo['path'].'/upload/forum/images';
716
            $sys_course_path = api_get_path(SYS_COURSE_PATH);
717
            $updir = $sys_course_path.$course_dir;
718
            // Try to add an extension to the file if it hasn't one.
719
            $new_file_name = add_ext_on_mime(
720
                Database::escape_string($_FILES['picture']['name']),
721
                $_FILES['picture']['type']
722
            );
723
            if (!filter_extension($new_file_name)) {
724
                //Display::addFlash(Display::return_message(get_lang('File upload failed: this file extension or file type is prohibited'), 'error'));
725
                $image_moved = false;
726
            } else {
727
                $file_extension = explode('.', $_FILES['picture']['name']);
728
                $file_extension = strtolower($file_extension[count($file_extension) - 1]);
729
                $new_file_name = uniqid('').'.'.$file_extension;
730
                $new_path = $updir.'/'.$new_file_name;
731
                $result = @move_uploaded_file($_FILES['picture']['tmp_name'], $new_path);
732
                // Storing the attachments if any
733
                if ($result) {
734
                    $image_moved = true;
735
                }
736
            }*/
737
        }
738
    }
739
740
    $repo = Container::getForumRepository();
741
742
    if (!isset($values['forum_id'])) {
743
        $forum = new CForum();
744
    } else {
745
        /** @var CForum $forum */
746
        $forum = $repo->find($values['forum_id']);
747
    }
748
749
    $forumCategory = null;
750
    if (!empty($values['forum_category'])) {
751
        $repoForumCategory = Container::getForumCategoryRepository();
752
        $forumCategory = $repoForumCategory->find($values['forum_category']);
753
    }
754
755
    $lpId = $values['lp_id'] ?? 0;
756
    $lpRepo = Container::getLpRepository();
757
    $lp = null;
758
    if (!empty($lpId)) {
759
        /** @var CLp $lp */
760
        $lp = $lpRepo->find($lpId);
761
    }
762
763
    $forum
764
        ->setTitle($values['forum_title'])
765
        ->setForumComment($values['forum_comment'] ?? '')
766
        ->setForumCategory($forumCategory)
767
        ->setAllowAnonymous($values['allow_anonymous_group']['allow_anonymous'] ?? 0)
768
        ->setAllowEdit($values['students_can_edit_group']['students_can_edit'] ?? 0)
769
        ->setApprovalDirectPost((string) ($values['approval_direct_group']['approval_direct'] ?? '0'))
770
        ->setAllowAttachments($values['allow_attachments_group']['allow_attachments'] ?? 0)
771
        ->setAllowNewThreads((int) ($values['allow_new_threads_group']['allow_new_threads'] ?? 0))
772
        ->setDefaultView($values['default_view_type_group']['default_view_type'] ?? '')
773
        ->setForumOfGroup((string) ($values['group_forum'] ?? 0))
774
        ->setForumGroupPublicPrivate($values['public_private_group_forum_group']['public_private_group_forum'] ?? 'public')
775
        ->setModerated((bool) ($values['moderated']['moderated'] ?? false))
776
        ->setStartTime(!empty($values['start_time']) ? api_get_utc_datetime($values['start_time'], true, true) : null)
777
        ->setEndTime(!empty($values['end_time']) ? api_get_utc_datetime($values['end_time'], true, true) : null)
778
        ->setLp($lp)
779
    ;
780
781
    $course = api_get_course_entity($courseId);
782
    $session = api_get_session_entity($session_id);
783
784
    if (isset($values['forum_id'])) {
785
        // Update.
786
        $repo->update($forum);
787
788
        // Move groups from one group to another
789
        if (isset($values['group_forum']) && false) {
790
            $forumData = get_forums($values['forum_id']);
791
            $currentGroupId = $forumData['forum_of_group'];
792
        }
793
794
        $return_message = get_lang('The forum has been modified');
795
        $forumId = $forum->getIid();
796
797
        $logInfo = [
798
            'tool' => TOOL_FORUM,
799
            'tool_id' => $values['forum_id'],
800
            'action' => 'update-forum',
801
            'action_details' => 'forum',
802
            'info' => $values['forum_title'],
803
        ];
804
        Event::registerLog($logInfo);
805
    } else {
806
        $forum
807
            ->setParent($forumCategory)
808
            ->addCourseLink($course, $session);
809
        $repo->create($forum);
810
811
        $forumId = $forum->getIid();
812
        if ($forumId > 0) {
813
            $courseCode = $courseInfo['code'];
814
            $subscribe = (int) api_get_course_setting('subscribe_users_to_forum_notifications');
815
816
            $status = STUDENT;
817
            if (!empty($session_id)) {
818
                $status = 0;
819
            }
820
            if (1 === $subscribe) {
821
                $userList = CourseManager::get_user_list_from_course_code(
822
                    $courseCode,
823
                    $session_id,
824
                    null,
825
                    null,
826
                    $status
827
                );
828
                foreach ($userList as $userInfo) {
829
                    set_notification('forum', $forumId, false, $userInfo, $courseInfo);
830
                }
831
            }
832
833
            $logInfo = [
834
                'tool' => TOOL_FORUM,
835
                'tool_id' => $forumId,
836
                'action' => 'new-forum',
837
                'action_details' => 'forum',
838
                'info' => $values['forum_title'],
839
            ];
840
            Event::registerLog($logInfo);
841
        }
842
        $return_message = get_lang('The forum has been added');
843
    }
844
845
    if ($returnId) {
846
        return $forumId;
847
    }
848
849
    return $return_message;
850
}
851
852
function deletePost(CForumPost $post): void
853
{
854
    $table_threads = Database::get_course_table(TABLE_FORUM_THREAD);
855
    $em = Database::getManager();
856
    $em
857
        ->createQuery('
858
            UPDATE ChamiloCourseBundle:CForumPost p
859
            SET p.postParent = :parent_of_deleted_post
860
            WHERE
861
                p.postParent = :post AND
862
                p.thread = :thread_of_deleted_post AND
863
                p.forum = :forum_of_deleted_post
864
        ')
865
        ->execute([
866
            'parent_of_deleted_post' => $post->getPostParent(),
867
            'post' => $post->getIid(),
868
            'thread_of_deleted_post' => $post->getThread() ? $post->getThread()->getIid() : 0,
869
            'forum_of_deleted_post' => $post->getForum(),
870
        ]);
871
872
    $attachments = $post->getAttachments();
873
    if (!empty($attachments)) {
874
        foreach ($attachments as $attachment) {
875
            $em->remove($attachment);
876
        }
877
    }
878
879
    $em->remove($post);
880
    $em->flush();
881
882
    $threadId = $post->getThread()->getIid();
883
    $lastPostOfTheTread = getLastPostOfThread($threadId);
884
885
    if (!empty($lastPostOfTheTread)) {
886
        // Decreasing the number of replies only if > 0
887
        $threadReplies = Database::fetch_array(Database::query("SELECT thread_replies FROM $table_threads WHERE iid = $threadId"));
888
        $replyCount = (int) $threadReplies['thread_replies'];
889
890
        if ($replyCount > 0) {
891
            $sql = "UPDATE $table_threads
892
                    SET
893
                        thread_replies = thread_replies - 1,
894
                        thread_last_post = ".$lastPostOfTheTread['iid'].",
895
                        thread_date = '".$lastPostOfTheTread['post_date']."'
896
                    WHERE iid = $threadId";
897
        } else {
898
            $sql = "UPDATE $table_threads
899
                    SET
900
                        thread_last_post = ".$lastPostOfTheTread['iid'].",
901
                        thread_date = '".$lastPostOfTheTread['post_date']."'
902
                    WHERE iid = $threadId";
903
        }
904
905
        Database::query($sql);
906
        Display::addFlash(Display::return_message(get_lang('Post has been deleted')));
907
    } else {
908
        // We deleted the very last post of the thread, so we need to delete the thread as well.
909
        $sql = "DELETE FROM $table_threads WHERE iid = $threadId";
910
        Database::query($sql);
911
912
        Display::addFlash(Display::return_message(get_lang('Thread deleted')));
913
    }
914
}
915
916
/**
917
 * This function gets the all information of the last (=most recent) post of the thread
918
 * This can be done by sorting the posts that have the field threadId=$threadId and sort them by post_date.
919
 * @author Patrick Cool <[email protected]>, Ghent University
920
 * @version february 2006, dokeos 1.8
921
 */
922
function getLastPostOfThread(int $threadId): array
923
{
924
    $post = Container::getForumPostRepository()->findOneBy(['thread' => $threadId], ['postDate' => 'DESC']);
925
926
    if (null === $post) {
927
        return [];
928
    }
929
930
    return [
931
        'iid' => $post->getIid(),
932
        'post_date' => $post->getPostDate()->format('Y-m-d H:i:s'),
933
    ];
934
}
935
936
/**
937
 * @param string $content                   Type of content forum category, forum, thread, post
938
 * @param int    $id                        the id of the content we want to make invisible
939
 * @param int    $current_visibility_status what is the current status of the visibility (0 = invisible, 1 = visible)
940
 * @param array  $additional_url_parameters
941
 *
942
 * @return string HTML
943
 */
944
function returnVisibleInvisibleIcon(
945
    string $content,
946
    int $id,
947
    int $current_visibility_status,
948
    array $additional_url_parameters = []
949
): string
950
{
951
    $html = '';
952
953
    if (1 == $current_visibility_status) {
954
        $html .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&';
955
        if (is_array($additional_url_parameters)) {
956
            foreach ($additional_url_parameters as $key => $value) {
957
                $html .= $key.'='.$value.'&';
958
            }
959
        }
960
        $html .= 'action=invisible&content='.$content.'&id='.$id.'">'.
961
            Display::getMdiIcon(ActionIcon::VISIBLE, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Make invisible')).'</a>';
962
    }
963
    if (0 == $current_visibility_status) {
964
        $html .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&';
965
        if (is_array($additional_url_parameters)) {
966
            foreach ($additional_url_parameters as $key => $value) {
967
                $html .= $key.'='.$value.'&';
968
            }
969
        }
970
        $html .= 'action=visible&content='.$content.'&id='.$id.'">'.
971
            Display::getMdiIcon(ActionIcon::INVISIBLE, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Make visible')).'</a>';
972
    }
973
974
    return $html;
975
}
976
977
/**
978
 * Returns an HTML string with the appropriate icon
979
 * @param string $content                   Type of content forum category, forum, thread, post
980
 * @param int    $id                        the id of the content we want to make invisible
981
 * @param int    $current_lock_status what is the current status of the visibility (0 = unlocked, 1 = locked)
982
 * @param array  $additional_url_parameters
983
 */
984
function returnLockUnlockIcon(
985
    string $content,
986
    int $id,
987
    int $current_lock_status,
988
    array $additional_url_parameters = []
989
): string
990
{
991
    $html = '';
992
    //check if the forum is blocked due
993
    if ('thread' === $content) {
994
        if (api_resource_is_locked_by_gradebook($id, LINK_FORUM_THREAD)) {
995
            return $html.Display::getMdiIcon(
996
                    ActionIcon::LOCK,
997
                    'ch-tool-icon-disabled',
998
                    '',
999
                    ICON_SIZE_SMALL,
1000
                    get_lang(
1001
                        'This option is not available because this activity is contained by an assessment, which is currently locked. To unlock the assessment, ask your platform administrator.'
1002
                    )
1003
                );
1004
        }
1005
    }
1006
    if ('1' == $current_lock_status) {
1007
        $html .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&';
1008
        if (is_array($additional_url_parameters)) {
1009
            foreach ($additional_url_parameters as $key => $value) {
1010
                $html .= $key.'='.$value.'&';
1011
            }
1012
        }
1013
        $html .= 'action=unlock&content='.$content.'&id='.$id.'">'.
1014
            Display::getMdiIcon(ActionIcon::LOCK, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Unlock')).'</a>';
1015
    }
1016
    if ('0' == $current_lock_status) {
1017
        $html .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&';
1018
        if (is_array($additional_url_parameters)) {
1019
            foreach ($additional_url_parameters as $key => $value) {
1020
                $html .= $key.'='.$value.'&';
1021
            }
1022
        }
1023
        $html .= 'action=lock&content='.$content.'&id='.$id.'">'.
1024
            Display::getMdiIcon(ActionIcon::UNLOCK, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Lock')).'</a>';
1025
    }
1026
1027
    return $html;
1028
}
1029
1030
/**
1031
 * Generates HTML for up and down icons with links to move an item up or down in a list.
1032
 */
1033
function returnUpDownIcon(string $content, int $id, array $list): string {
1034
    $forumCategory = isset($_GET['forumcategory']) ? Security::remove_XSS($_GET['forumcategory']) : null;
1035
    $totalItemsOfType = 0;
1036
    $position = 0;
1037
    foreach ($list as $key => $item) {
1038
        if (($content === 'forumcategory' && $item instanceof CForumCategory) ||
1039
            ($content === 'forum' && $item instanceof CForum)) {
1040
            $totalItemsOfType++;
1041
            if ($id == $item->getIid()) {
1042
                $position = $key + 1;
1043
            }
1044
        }
1045
    }
1046
1047
    $upIcon = $position > 1
1048
        ? '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=move&direction=up&content='.$content.'&id='.$id.'" title="'.get_lang('Move up').'">'.Display::getMdiIcon(ActionIcon::UP, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Move up')).'</a>'
1049
        : '<span class="ch-tool-icon-disabled">'.Display::getMdiIcon(ActionIcon::UP, 'ch-tool-icon-disabled', null, ICON_SIZE_SMALL, '').'</span>';
1050
1051
    $downIcon = $position < $totalItemsOfType
1052
        ? '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=move&direction=down&content='.$content.'&id='.$id.'" title="'.get_lang('Move down').'">'.Display::getMdiIcon(ActionIcon::DOWN, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Move down')).'</a>'
1053
        : '<span class="ch-tool-icon-disabled">'.Display::getMdiIcon(ActionIcon::DOWN, 'ch-tool-icon-disabled', null, ICON_SIZE_SMALL, '').'</span>';
1054
1055
1056
    return $upIcon . $downIcon;
1057
}
1058
1059
/**
1060
 * Moves a forum or forum category up or down in the display order.
1061
 */
1062
function moveUpDown(string $content, string $direction, int $id): string
1063
{
1064
    $em = Database::getManager();
1065
1066
    if ('forumcategory' === $content) {
1067
        $entityRepo = $em->getRepository(CForumCategory::class);
1068
    } elseif ('forum' === $content) {
1069
        $entityRepo = $em->getRepository(CForum::class);
1070
    }
1071
1072
    if (null === $entityRepo) {
1073
        return false;
1074
    }
1075
1076
    $entity = $entityRepo->find($id);
1077
    if (null === $entity) {
1078
        return false;
1079
    }
1080
1081
    $resourceNode = $entity->getResourceNode();
1082
    if (null === $resourceNode) {
1083
        return false;
1084
    }
1085
1086
    $course = api_get_course_entity();
1087
    $session = api_get_session_entity();
1088
1089
    $link = $resourceNode->getResourceLinkByContext($course, $session);
1090
1091
    if (!$link) {
1092
        return false;
1093
    }
1094
1095
    if ('down' === $direction) {
1096
        $link->moveDownPosition();
1097
    } else {
1098
        $link->moveUpPosition();
1099
    }
1100
1101
    $em->flush();
1102
1103
    Display::addFlash(Display::return_message(get_lang('Updated')));
1104
1105
    return true;
1106
}
1107
1108
/**
1109
 * Retrieve all the information off the forum categories (or one specific) for the current course.
1110
 * The categories are sorted according to their sorting order (cat_order.
1111
 *
1112
 * @param int $courseId  Optional. The course ID
1113
 * @param int $sessionId Optional. The session ID
1114
 *
1115
 * @return CForumCategory[]
1116
 */
1117
function get_forum_categories(int $courseId = 0, int $sessionId = 0): Array
1118
{
1119
    $repo = Container::getForumCategoryRepository();
1120
1121
    $course = api_get_course_entity($courseId);
1122
    $session = api_get_session_entity($sessionId);
1123
1124
    $qb = $repo->getResourcesByCourse($course, $session, null, $course->getResourceNode(), true, true);
1125
1126
    return $qb->getQuery()->getResult();
1127
}
1128
1129
/**
1130
 * This function retrieves all the fora in a given forum category.
1131
 *
1132
 * @todo: Fixes error if there forums with no category.
1133
 *
1134
 * @param int $categoryId the id of the forum category
1135
 * @param int $courseId   Optional. The course ID
1136
 *
1137
 * @return CForum[] containing all the information about the forums (regardless of their category)
1138
 *
1139
 * @author Patrick Cool <[email protected]>, Ghent University
1140
 *
1141
 * @version february 2006, dokeos 1.8
1142
 */
1143
function get_forums_in_category(int $categoryId, int $courseId = 0, int $sessionId = 0)
1144
{
1145
    $repo = Container::getForumRepository();
1146
    $course = api_get_course_entity($courseId);
1147
    $session = api_get_session_entity($sessionId);
1148
1149
    $qb = $repo->getResourcesByCourse($course, $session, null, null, true, true);
1150
    $qb
1151
        ->andWhere('resource.forumCategory = :catId')
1152
        ->setParameter('catId', $categoryId)
1153
    ;
1154
1155
    return $qb->getQuery()->getResult();
1156
}
1157
1158
/**
1159
 * Retrieve all the forums (regardless of their category) or of only one.
1160
 * The forums are sorted according to the forum_order.
1161
 * Since it does not take the forum category into account there probably
1162
 * will be two or more forums that have forum_order=1, ...
1163
 *
1164
 * @param bool $includeGroupsForum
1165
 * @param int  $sessionId
1166
 *
1167
 * @return CForum[]
1168
 */
1169
function get_forums(
1170
    int $courseId = null,
1171
    int $sessionId = 0
1172
) {
1173
    $repo = Container::getForumRepository();
1174
    $courseId = empty($courseId) ? api_get_course_int_id() : $courseId;
1175
    $course = api_get_course_entity($courseId);
1176
    $session = api_get_session_entity($sessionId);
1177
1178
    $qb = $repo->getResourcesByCourse($course, $session);
1179
1180
    /*$qb->andWhere('resource.forumCategory = :catId')
1181
        ->setParameter('catId', $cat_id);
1182
    */
1183
    return $qb->getQuery()->getResult();
1184
}
1185
1186
/**
1187
 * Returns the given forum ID's forum instance
1188
 */
1189
function getForum(
1190
    int $forumId = null
1191
): CForum|bool {
1192
    if (!empty($forumId)) {
1193
        $repo = Container::getForumRepository();
1194
        $qb = $repo->find($forumId);
1195
1196
        return $qb->getQuery()->getResult();
1197
    }
1198
1199
    return false;
1200
}
1201
1202
/**
1203
 * Retrieves all the threads for a given forum or counts them.
1204
 */
1205
function get_threads(int $forumId, int $courseId = null, int $sessionId = null, bool $count = false): array|int
1206
{
1207
    $repo = Container::getForumThreadRepository();
1208
    $courseId = empty($courseId) ? api_get_course_int_id() : $courseId;
1209
    $course = api_get_course_entity($courseId);
1210
    $session = api_get_session_entity($sessionId);
1211
1212
    $qb = $repo->getResourcesByCourse($course, $session);
1213
    $qb->andWhere('resource.forum = :forum')->setParameter('forum', $forumId);
1214
1215
    if ($count) {
1216
        $qb->select('COUNT(resource.iid)');
1217
        return (int) $qb->getQuery()->getSingleScalarResult();
1218
    } else {
1219
        return $qb->getQuery()->getResult();
1220
    }
1221
}
1222
1223
/**
1224
 * Get a thread by Id and course id.
1225
 *
1226
 * @param int $threadId the thread Id
1227
 *
1228
 * @return array containing all the information about the thread
1229
 */
1230
function getThreadInfo(int $threadId): Array
1231
{
1232
    $repo = Database::getManager()->getRepository(CForumThread::class);
1233
    /** @var CForumThread $forumThread */
1234
    $forumThread = $repo->findOneBy(['iid' => $threadId]);
1235
1236
    $thread = [];
1237
    if ($forumThread) {
1238
        $thread['iid'] = $forumThread->getIid();
1239
        $thread['threadId'] = $forumThread->getIid();
1240
        $thread['threadTitle'] = $forumThread->getTitle();
1241
        $thread['forumId'] = $forumThread->getForum() ? $forumThread->getForum()->getIid() : 0;
1242
        //$thread['sessionId'] = $forumThread->getSessionId();
1243
        $thread['threadSticky'] = $forumThread->getThreadSticky();
1244
        $thread['locked'] = $forumThread->getLocked();
1245
        $thread['threadTitleQualify'] = $forumThread->getThreadTitleQualify();
1246
        $thread['threadQualifyMax'] = $forumThread->getThreadQualifyMax();
1247
        $thread['threadCloseDate'] = $forumThread->getThreadCloseDate();
1248
        $thread['threadWeight'] = $forumThread->getThreadWeight();
1249
        $thread['threadPeerQualify'] = $forumThread->isThreadPeerQualify();
1250
    }
1251
1252
    return $thread;
1253
}
1254
1255
/**
1256
 * Retrieve all posts of a given thread.
1257
 *
1258
 * @param int    $threadId       The thread ID
1259
 * @param string $orderDirection Optional. The direction for sort the posts
1260
 * @param bool   $recursive      Optional. If the list is recursive
1261
 * @param int    $postId         Optional. The post ID for recursive list
1262
 * @param int    $depth          Optional. The depth to indicate the indent
1263
 *
1264
 * @todo move to a repository
1265
 *
1266
 * @return array containing all the information about the posts of a given thread
1267
 */
1268
function getPosts(
1269
    CForum $forum,
1270
    int $threadId,
1271
    string $orderDirection = 'ASC',
1272
    bool $recursive = false,
1273
    int $postId = null,
1274
    int $depth = -1
1275
): Array
1276
{
1277
    $em = Database::getManager();
1278
1279
    $orderDirection = strtoupper($orderDirection);
1280
    if (!in_array($orderDirection, ['ASC', 'DESC'], true)) {
1281
        $orderDirection = 'ASC';
1282
    }
1283
1284
    // Build visibility criteria based on permissions
1285
    if (api_is_allowed_to_edit(false, true)) {
1286
        // Entity maps 'visible' as boolean; keeping legacy behavior as requested.
1287
        $visibleCriteria = Criteria::expr()->neq('visible', 2);
1288
    } else {
1289
        $visibleCriteria = Criteria::expr()->eq('visible', 1);
1290
    }
1291
1292
    $threadRef = $em->getReference(CForumThread::class, $threadId);
1293
1294
    $criteria = Criteria::create();
1295
    $criteria
1296
        ->where(Criteria::expr()->eq('thread', $threadRef))
1297
        ->andWhere($visibleCriteria);
1298
1299
    $groupId = api_get_group_id();
1300
    $filterModerated = true;
1301
1302
    if (empty($groupId)) {
1303
        if (api_is_allowed_to_edit()) {
1304
            $filterModerated = false;
1305
        }
1306
    } else {
1307
        $groupEntity = api_get_group_entity($groupId);
1308
        if (GroupManager::isTutorOfGroup(api_get_user_id(), $groupEntity) ||
1309
            api_is_allowed_to_edit(false, true)
1310
        ) {
1311
            $filterModerated = false;
1312
        }
1313
    }
1314
1315
    if ($recursive) {
1316
        // Compare association with entity reference when filtering by parent
1317
        $parentRef = $postId ? $em->getReference(CForumPost::class, $postId) : null;
1318
        $criteria->andWhere(Criteria::expr()->eq('postParent', $parentRef));
1319
    }
1320
1321
    $qb = $em->getRepository(CForumPost::class)->createQueryBuilder('p');
1322
    $qb->select('p')
1323
        ->addCriteria($criteria)
1324
        ->addOrderBy('p.iid', $orderDirection);
1325
1326
    // Apply moderation filter if forum is moderated and user is not editor
1327
    if ($filterModerated && 1 == $forum->isModerated()) {
1328
        if (!api_is_allowed_to_edit(false, true)) {
1329
            $userId = api_get_user_id();
1330
1331
            // instead of a non-existent scalar field like p.posterId
1332
            $qb->andWhere(
1333
                '(p.status = :st_valid)
1334
                 OR (p.status IN (:st_own) AND IDENTITY(p.user) = :uid)
1335
                 OR (p.status IS NULL AND IDENTITY(p.user) = :uid)'
1336
            )
1337
                ->setParameter('st_valid', CForumPost::STATUS_VALIDATED)
1338
                ->setParameter('st_own', [
1339
                    CForumPost::STATUS_WAITING_MODERATION,
1340
                    CForumPost::STATUS_REJECTED,
1341
                ])
1342
                ->setParameter('uid', $userId);
1343
        }
1344
    }
1345
1346
    $posts = $qb->getQuery()->getResult();
1347
    $depth++;
1348
1349
    $list = [];
1350
    /** @var CForumPost $post */
1351
    foreach ($posts as $post) {
1352
        $postInfo = [
1353
            'iid' => $post->getIid(),
1354
            'post_id' => $post->getIid(),
1355
            'post_title' => $post->getTitle(),
1356
            'post_text' => $post->getPostText(),
1357
            'thread_id' => $post->getThread() ? $post->getThread()->getIid() : 0,
1358
            'forum_id' => $post->getForum()->getIid(),
1359
            'post_date' => $post->getPostDate(),
1360
            'post_notification' => $post->getPostNotification(),
1361
            'post_parent_id' => $post->getPostParent() ? $post->getPostParent()->getIid() : 0,
1362
            'visible' => $post->getVisible(),
1363
            'status' => $post->getStatus(),
1364
            'indent_cnt' => $depth,
1365
            'entity' => $post,
1366
        ];
1367
1368
        // Fill user info if available
1369
        $user = $post->getUser();
1370
        if ($user) {
1371
            $postInfo['user_id'] = $user->getId();
1372
            $postInfo['username'] = $user->getUsername();
1373
            $postInfo['username_canonical'] = $user->getUsernameCanonical();
1374
            $postInfo['lastname'] = $user->getLastname();
1375
            $postInfo['firstname'] = $user->getFirstname();
1376
            $postInfo['complete_name'] = UserManager::formatUserFullName($user);
1377
        }
1378
1379
        $list[] = $postInfo;
1380
1381
        if (!$recursive) {
1382
            continue;
1383
        }
1384
1385
        // Recursive fetch of children when requested
1386
        $list = array_merge(
1387
            $list,
1388
            getPosts(
1389
                $forum,
1390
                $threadId,
1391
                $orderDirection,
1392
                $recursive,
1393
                $post->getIid(),
1394
                $depth
1395
            )
1396
        );
1397
    }
1398
1399
    return $list;
1400
}
1401
1402
/**
1403
 * This function retrieves forum thread users details.
1404
 *
1405
 * @return Doctrine\DBAL\Driver\Statement|null array Array of type
1406
 *                                             ([user_id=>w,lastname=>x,firstname=>y,threadId=>z],[])
1407
 *
1408
 * @author Christian Fasanando <[email protected]>,
1409
 *
1410
 * @todo     this function needs to be improved
1411
 *
1412
 * @version October 2008, dokeos 1.8
1413
 */
1414
function get_thread_users_details(int $thread_id)
1415
{
1416
    $t_posts = Database::get_course_table(TABLE_FORUM_POST);
1417
    $t_users = Database::get_main_table(TABLE_MAIN_USER);
1418
    $t_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1419
    $t_session_rel_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
1420
1421
    $course_id = api_get_course_int_id();
1422
1423
    $is_western_name_order = api_is_western_name_order();
1424
    if ($is_western_name_order) {
1425
        $orderby = 'ORDER BY user.firstname, user.lastname ';
1426
    } else {
1427
        $orderby = 'ORDER BY user.lastname, user.firstname';
1428
    }
1429
1430
    $session = api_get_session_entity();
1431
1432
    if ($session) {
1433
        $generalCoachesId = $session->getGeneralCoaches()->map(fn(User $coach) => $coach->getId())->getValues();
1434
        $sessionAdminsId = $session->getSessionAdmins()->map(fn(User $admin) => $admin->getId())->getValues();
1435
        $coachesId = array_merge($generalCoachesId, $sessionAdminsId);
1436
        $user_to_avoid = implode(', ', $coachesId);
1437
        //not showing coaches
1438
        $sql = "SELECT DISTINCT user.id, user.lastname, user.firstname, thread_id
1439
                FROM $t_posts p, $t_users user, $t_session_rel_user session_rel_user_rel_course
1440
                WHERE
1441
                    p.poster_id = user.id AND
1442
                    user.id = session_rel_user_rel_course.user_id AND
1443
                    session_rel_user_rel_course.status = ".SessionEntity::STUDENT." AND
1444
                    session_rel_user_rel_course.user_id NOT IN ($user_to_avoid) AND
1445
                    p.thread_id = $thread_id AND
1446
                    session_id = ".api_get_session_id()." AND
1447
                    p.c_id = $course_id AND
1448
                    session_rel_user_rel_course.c_id = $course_id $orderby ";
1449
    } else {
1450
        $sql = "SELECT DISTINCT user.id, user.lastname, user.firstname, thread_id
1451
                FROM $t_posts p, $t_users user, $t_course_user course_user
1452
                WHERE
1453
                    p.poster_id = user.id
1454
                    AND user.id = course_user.user_id
1455
                    AND course_user.relation_type <> ".COURSE_RELATION_TYPE_RRHH."
1456
                    AND p.thread_id = $thread_id
1457
                    AND course_user.status != '1' AND
1458
                    course_user.c_id = $course_id $orderby";
1459
    }
1460
1461
    return Database::query($sql);
1462
}
1463
1464
/**
1465
 * This function retrieves forum thread users qualify.
1466
 *
1467
 * @return Doctrine\DBAL\Driver\Statement|null Array of type ([user_id=>w,lastname=>x,firstname=>y,threadId=>z],[])
1468
 * @author Jhon Hinojosa
1469
 * @todo     this function needs to be improved
1470
 */
1471
function get_thread_users_qualify(int $thread_id)
1472
{
1473
    $t_posts = Database::get_course_table(TABLE_FORUM_POST);
1474
    $t_qualify = Database::get_course_table(TABLE_FORUM_THREAD_QUALIFY);
1475
    $t_users = Database::get_main_table(TABLE_MAIN_USER);
1476
    $t_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1477
    $t_session_rel_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
1478
1479
    $course_id = api_get_course_int_id();
1480
    $sessionId = api_get_session_id();
1481
1482
    $is_western_name_order = api_is_western_name_order();
1483
    if ($is_western_name_order) {
1484
        $orderby = 'ORDER BY user.firstname, user.lastname ';
1485
    } else {
1486
        $orderby = 'ORDER BY user.lastname, user.firstname';
1487
    }
1488
1489
    $session = api_get_session_entity();
1490
1491
    if ($session) {
1492
        $generalCoachesId = $session->getGeneralCoaches()->map(fn(User $coach) => $coach->getId())->getValues();
1493
        $sessionAdminsId = $session->getSessionAdmins()->map(fn(User $admin) => $admin->getId())->getValues();
1494
        $coachesId = array_merge($generalCoachesId, $sessionAdminsId);
1495
        $user_to_avoid = implode(', ', $coachesId);
1496
        //not showing coaches
1497
        $sql = "SELECT DISTINCT post.poster_id, user.lastname, user.firstname, post.thread_id,user.id,qualify.qualify
1498
                FROM $t_posts post , $t_users user, $t_session_rel_user scu, $t_qualify qualify
1499
                WHERE poster_id = user.id
1500
                    AND post.poster_id = qualify.user_id
1501
                    AND user.id = scu.user_id
1502
                    AND scu.status = ".SessionEntity::STUDENT."
1503
                    AND scu.user_id NOT IN ($user_to_avoid)
1504
                    AND qualify.thread_id = $thread_id
1505
                    AND post.thread_id = $thread_id
1506
                    AND scu.session_id = $sessionId
1507
                    AND scu.c_id = $course_id AND
1508
                    qualify.c_id = $course_id AND
1509
                    post.c_id = $course_id
1510
                $orderby ";
1511
    } else {
1512
        $sql = "SELECT DISTINCT post.poster_id, user.lastname, user.firstname, post.thread_id,user.id,qualify.qualify
1513
                FROM $t_posts post,
1514
                     $t_qualify qualify,
1515
                     $t_users user,
1516
                     $t_course_user course_user
1517
                WHERE
1518
                     post.poster_id = user.id
1519
                     AND post.poster_id = qualify.user_id
1520
                     AND user.id = course_user.user_id
1521
                     AND course_user.relation_type<>".COURSE_RELATION_TYPE_RRHH."
1522
                     AND qualify.thread_id = $thread_id
1523
                     AND post.thread_id = $thread_id
1524
                     AND course_user.status not in('1')
1525
                     AND course_user.c_id = $course_id
1526
                     AND qualify.c_id = $course_id
1527
                     AND post.c_id = $course_id
1528
                 $orderby ";
1529
    }
1530
1531
    return Database::query($sql);
1532
}
1533
1534
/**
1535
 * This function retrieves forum thread users not qualify.
1536
 *
1537
 * @param int $threadId Thread ID
1538
 * @param   string  Course DB name (optional)
1539
 *
1540
 * @return Doctrine\DBAL\Driver\Statement|null Array of type ([user_id=>w,lastname=>x,firstname=>y,threadId=>z],[])
1541
 *
1542
 * @author   Jhon Hinojosa<[email protected]>,
1543
 *
1544
 * @version oct 2008, dokeos 1.8
1545
 */
1546
function get_thread_users_not_qualify($thread_id)
1547
{
1548
    $t_posts = Database::get_course_table(TABLE_FORUM_POST);
1549
    $t_qualify = Database::get_course_table(TABLE_FORUM_THREAD_QUALIFY);
1550
    $t_users = Database::get_main_table(TABLE_MAIN_USER);
1551
    $t_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
1552
    $t_session_rel_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
1553
1554
    $is_western_name_order = api_is_western_name_order();
1555
    if ($is_western_name_order) {
1556
        $orderby = 'ORDER BY user.firstname, user.lastname ';
1557
    } else {
1558
        $orderby = 'ORDER BY user.lastname, user.firstname';
1559
    }
1560
1561
    $course_id = api_get_course_int_id();
1562
1563
    $sql1 = "SELECT user_id FROM  $t_qualify
1564
             WHERE thread_id = '".$thread_id."'";
1565
    $result1 = Database::query($sql1);
1566
    $cad = '';
1567
    while ($row = Database::fetch_array($result1)) {
1568
        $cad .= $row['user_id'].',';
1569
    }
1570
    if ('' == $cad) {
1571
        $cad = '0';
1572
    } else {
1573
        $cad = substr($cad, 0, strlen($cad) - 1);
1574
    }
1575
1576
    $session = api_get_session_entity();
1577
1578
    if ($session) {
1579
        $generalCoachesId = $session->getGeneralCoaches()->map(fn(User $coach) => $coach->getId())->getValues();
1580
        $sessionAdminsId = $session->getSessionAdmins()->map(fn(User $admin) => $admin->getId())->getValues();
1581
        $coachesId = array_merge($generalCoachesId, $sessionAdminsId);
1582
        $user_to_avoid = implode(', ', $coachesId);
1583
        //not showing coaches
1584
        $sql = "SELECT DISTINCT user.id, user.lastname, user.firstname, post.thread_id
1585
                FROM $t_posts post , $t_users user, $t_session_rel_user session_rel_user_rel_course
1586
                WHERE poster_id = user.id
1587
                    AND user.id NOT IN (".$cad.")
1588
                    AND user.id = session_rel_user_rel_course.user_id
1589
                    AND session_rel_user_rel_course.status = ".SessionEntity::STUDENT."
1590
                    AND session_rel_user_rel_course.user_id NOT IN ($user_to_avoid)
1591
                    AND post.thread_id = ".(int) $thread_id.'
1592
                    AND session_id = '.api_get_session_id()."
1593
                    AND session_rel_user_rel_course.c_id = $course_id AND post.c_id = $course_id $orderby ";
1594
    } else {
1595
        $sql = "SELECT DISTINCT user.id, user.lastname, user.firstname, post.thread_id
1596
                FROM $t_posts post, $t_users user,$t_course_user course_user
1597
                WHERE post.poster_id = user.id
1598
                AND user.id NOT IN (".$cad.')
1599
                AND user.id = course_user.user_id
1600
                AND course_user.relation_type<>'.COURSE_RELATION_TYPE_RRHH.'
1601
                AND post.thread_id = '.(int) $thread_id."
1602
                AND course_user.status not in('1')
1603
                AND course_user.c_id = $course_id AND post.c_id = $course_id  $orderby";
1604
    }
1605
1606
    return Database::query($sql);
1607
}
1608
1609
/**
1610
 * This function counts the number of forums inside a given category.
1611
 *
1612
 * @param int $cat_id the id of the forum category
1613
 *
1614
 * @todo an additional parameter that takes the visibility into account. For instance $countinvisible=0 would return
1615
 *       the number of visible forums, $countinvisible=1 would return the number of visible and invisible forums
1616
 *
1617
 * @return int the number of forums inside the given category
1618
 *
1619
 * @author Patrick Cool <[email protected]>, Ghent University
1620
 *
1621
 * @version february 2006, dokeos 1.8
1622
 */
1623
function count_number_of_forums_in_category($cat_id)
1624
{
1625
    $table_forums = Database::get_course_table(TABLE_FORUM);
1626
    $course_id = api_get_course_int_id();
1627
    $cat_id = (int) $cat_id;
1628
    $sql = "SELECT count(*) AS number_of_forums
1629
            FROM $table_forums
1630
            WHERE forum_category = $cat_id";
1631
    $result = Database::query($sql);
1632
    $row = Database::fetch_array($result);
1633
1634
    return $row['number_of_forums'];
1635
}
1636
1637
/**
1638
 * This function update a thread.
1639
 *
1640
 * @param array $values - The form Values
1641
 */
1642
function updateThread($values)
1643
{
1644
    if (!api_is_allowed_to_edit()) {
1645
        return '';
1646
    }
1647
1648
    $logInfo = [
1649
        'tool' => TOOL_FORUM,
1650
        'tool_id' => $values['forum_id'],
1651
        'tool_id_detail' => $values['thread_id'],
1652
        'action' => 'edit-thread',
1653
        'action_details' => 'thread',
1654
        'info' => $values['thread_title'],
1655
    ];
1656
    Event::registerLog($logInfo);
1657
1658
    $threadTable = Database::get_course_table(TABLE_FORUM_THREAD);
1659
    $courseId = api_get_course_int_id();
1660
    $courseCode = api_get_course_id();
1661
    $sessionId = api_get_session_id();
1662
1663
    // Simple update + set gradebook values to null
1664
    $params = [
1665
        'title' => $values['thread_title'],
1666
        'thread_sticky' => $values['thread_sticky'] ?? 0,
1667
    ];
1668
    $where = ['iid = ?' => [$values['thread_id']]];
1669
    Database::update($threadTable, $params, $where);
1670
1671
    $id = $values['thread_id'];
1672
    $linkInfo = GradebookUtils::isResourceInCourseGradebook(
1673
        $courseId,
1674
        LINK_FORUM_THREAD,
1675
        $id,
1676
        $sessionId
1677
    );
1678
1679
    $gradebookLink = null;
1680
    $em = Database::getManager();
1681
    if (!empty($linkInfo) && isset($linkInfo['id'])) {
1682
        $gradebookLink = $em->getRepository(GradebookLink::class)->find($linkInfo['id']);
1683
    }
1684
1685
    // values 1 or 0
1686
    $check = isset($values['thread_qualify_gradebook']) ? $values['thread_qualify_gradebook'] : false;
1687
    if ($check) {
1688
        $title = Security::remove_XSS(stripslashes($values['calification_notebook_title']));
1689
        $value = isset($values['numeric_calification']) ? (int) ($values['numeric_calification']) : 0;
1690
        $weight = isset($values['weight_calification']) ? (float) ($values['weight_calification']) : 0;
1691
        $description = '';
1692
        // Update title
1693
        $params = [
1694
            'thread_title_qualify' => $values['calification_notebook_title'],
1695
            'thread_qualify_max' => api_float_val($values['numeric_calification']),
1696
            'thread_weight' => api_float_val($values['weight_calification']),
1697
            'thread_peer_qualify' => $values['thread_peer_qualify'],
1698
        ];
1699
        $where = ['iid = ?' => [$values['thread_id']]];
1700
        Database::update($threadTable, $params, $where);
1701
1702
        if (!$linkInfo) {
1703
            GradebookUtils::add_resource_to_course_gradebook(
1704
                $values['category_id'],
1705
                $courseId,
1706
                LINK_FORUM_THREAD,
1707
                $id,
1708
                $title,
1709
                $weight,
1710
                $value,
1711
                $description,
1712
                1,
1713
                $sessionId
1714
            );
1715
        } else {
1716
            if ($gradebookLink) {
1717
                $gradebookLink->setWeight($weight);
1718
                $em->persist($gradebookLink);
1719
                $em->flush();
1720
            }
1721
        }
1722
    } else {
1723
        $params = [
1724
            'thread_title_qualify' => '',
1725
            'thread_qualify_max' => 0,
1726
            'thread_weight' => 0,
1727
            'thread_peer_qualify' => 0,
1728
        ];
1729
        $where = ['iid = ?' => [$values['thread_id']]];
1730
        Database::update($threadTable, $params, $where);
1731
1732
        if (!empty($linkInfo)) {
1733
            if ($gradebookLink) {
1734
                $em->remove($gradebookLink);
1735
                $em->flush();
1736
            }
1737
        }
1738
    }
1739
1740
    $message = get_lang('The post has been modified').'<br />';
1741
    Display::addFlash(Display::return_message($message, 'confirmation', false));
1742
}
1743
1744
function saveThread(
1745
    CForum $forum,
1746
    array $values,
1747
    array $courseInfo = [],
1748
    $showMessage = true,
1749
    $userId = 0,
1750
    $sessionId = 0
1751
): ?CForumThread {
1752
    $courseInfo = empty($courseInfo) ? api_get_course_info() : $courseInfo;
1753
    $userId = $userId ?: api_get_user_id();
1754
    $course_id = $courseInfo['real_id'];
1755
    $courseCode = $courseInfo['code'];
1756
    $sessionId = $sessionId ?: api_get_session_id();
1757
    $table_threads = Database::get_course_table(TABLE_FORUM_THREAD);
1758
1759
    $post_date = new DateTime(api_get_utc_datetime(), new DateTimeZone('UTC'));
1760
    $visible = true;
1761
    if ('1' == $forum->getApprovalDirectPost() && !api_is_allowed_to_edit(null, true)) {
1762
        $visible = false; // The post has not been approved yet.
1763
    }
1764
    $clean_post_title = $values['post_title'];
1765
1766
    $user = api_get_user_entity(api_get_user_id());
1767
    $course = api_get_course_entity($course_id);
1768
    $session = api_get_session_entity($sessionId);
1769
1770
    // We first store an entry in the forum_thread table because the threadId is used in the forum_post table.
1771
    $thread = new CForumThread();
1772
    $thread
1773
        ->setTitle($clean_post_title)
1774
        ->setForum($forum)
1775
        ->setUser($user)
1776
        ->setThreadDate($post_date)
1777
        ->setThreadSticky((bool) ($values['thread_sticky'] ?? false))
1778
        ->setThreadTitleQualify($values['calification_notebook_title'] ?? '')
1779
        ->setThreadQualifyMax(api_float_val($values['numeric_calification'] ?? 0))
1780
        ->setThreadWeight(api_float_val($values['weight_calification'] ?? 0))
1781
        ->setThreadPeerQualify(isset($values['thread_peer_qualify']) ? (bool) $values['thread_peer_qualify'] : false)
1782
        ->setParent($forum)
1783
        ->addCourseLink($course, $session)
1784
    ;
1785
    $em = Database::getManager();
1786
    $itemId = isset($values['lp_item_id']) ? (int) $values['lp_item_id'] : 0;
1787
    if (!empty($itemId)) {
1788
        $item = $em->getRepository(CLpItem::class)->find($itemId);
1789
        $thread->setItem($item);
1790
    }
1791
1792
    $repo = Container::getForumThreadRepository();
1793
    $repo->create($thread);
1794
1795
    if (!$thread->getIid()) {
1796
        return null;
1797
    }
1798
1799
    // Add option gradebook qualify.
1800
    if (isset($values['thread_qualify_gradebook']) &&
1801
        1 == $values['thread_qualify_gradebook']
1802
    ) {
1803
        // Add function gradebook.
1804
        $resourcename = stripslashes($values['calification_notebook_title']);
1805
        GradebookUtils::add_resource_to_course_gradebook(
1806
            $values['category_id'],
1807
            $course_id,
1808
            5,
1809
            $thread->getIid(),
1810
            $resourcename,
1811
            $values['weight_calification'],
1812
            $values['numeric_calification'],
1813
            '',
1814
            0,
1815
            $sessionId
1816
        );
1817
    }
1818
1819
    $logInfo = [
1820
        'tool' => TOOL_FORUM,
1821
        'tool_id' => $values['forum_id'],
1822
        'tool_id_detail' => $thread->getIid(),
1823
        'action' => 'new-thread',
1824
        'info' => $clean_post_title,
1825
    ];
1826
    Event::registerLog($logInfo);
1827
1828
    // We now store the content in the table_post table.
1829
    $post = new CForumPost();
1830
    $post
1831
        ->setTitle($clean_post_title)
1832
        ->setPostText($values['post_text'])
1833
        ->setThread($thread)
1834
        ->setForum($forum)
1835
        ->setUser(api_get_user_entity($userId))
1836
        ->setPostDate($post_date)
1837
        ->setPostNotification(isset($values['post_notification']) ? (bool) $values['post_notification'] : false)
1838
        ->setVisible($visible)
1839
        ->setStatus(CForumPost::STATUS_VALIDATED)
1840
        ->setParent($thread)
1841
        ->addCourseLink($course, $session)
1842
    ;
1843
1844
    if ($forum->isModerated()) {
1845
        $post->setStatus(
1846
            api_is_course_admin() ? CForumPost::STATUS_VALIDATED : CForumPost::STATUS_WAITING_MODERATION
1847
        );
1848
    }
1849
1850
    $repo = Container::getForumPostRepository();
1851
    $repo->create($post);
1852
    $thread->setThreadLastPost($post);
1853
    $em = Database::getManager();
1854
    $em->persist($thread);
1855
    $em->flush();
1856
1857
    $postId = $post->getIid();
1858
1859
    $logInfo = [
1860
        'tool' => TOOL_FORUM,
1861
        'tool_id' => $values['forum_id'],
1862
        'tool_id_detail' => $thread->getIid(),
1863
        'action' => 'new-post',
1864
        'info' => $clean_post_title,
1865
    ];
1866
    Event::registerLog($logInfo);
1867
1868
    // Now we have to update the thread table to fill the thread_last_post
1869
    // field (so that we know when the thread has been updated for the last time).
1870
    $sql = "UPDATE $table_threads
1871
            SET thread_last_post = '".$postId."'
1872
            WHERE
1873
                iid = '".$thread->getIid()."'";
1874
    Database::query($sql);
1875
1876
    $message = '';
1877
    if ($showMessage) {
1878
        Display::addFlash(Display::return_message(get_lang('The new thread has been added'), 'success', false));
1879
    }
1880
1881
    // Overwrite default message.
1882
    if ($forum->isModerated() &&
1883
        !api_is_allowed_to_edit(null, true)
1884
    ) {
1885
        if ($showMessage) {
1886
            Display::addFlash(Display::return_message(get_lang('Your message has to be approved before people can view it.'), 'success', false));
1887
        }
1888
    }
1889
1890
    add_forum_attachment_file(
1891
        null,
1892
        $post
1893
    );
1894
1895
    if ('1' == $forum->getApprovalDirectPost() &&
1896
        !api_is_allowed_to_edit(null, true)
1897
    ) {
1898
        $message .= get_lang('Your message has to be approved before people can view it.').'<br />';
1899
        $message .= get_lang('You can now return to the').
1900
            ' <a href="viewforum.php?'.api_get_cidreq(true, true, false).'&forum='.$values['forum_id'].'">'.
1901
            get_lang('Forum').'</a><br />';
1902
    } else {
1903
        $message .= get_lang('You can now return to the').
1904
            ' <a href="viewforum.php?'.api_get_cidreq(true, true, false).'&forum='.$values['forum_id'].'">'.
1905
            get_lang('Forum').'</a><br />';
1906
        $message .= get_lang('You can now return to the').
1907
            ' <a href="viewthread.php?'.api_get_cidreq(true, true, false).'&forum='.$values['forum_id'].'&thread='.$thread->getIid().'">'.
1908
            get_lang('Message').'</a>';
1909
    }
1910
    $reply_info['new_post_id'] = $postId;
1911
    $my_post_notification = isset($values['post_notification']) ? $values['post_notification'] : null;
1912
1913
    if (1 == $my_post_notification) {
1914
        set_notification('thread', $thread->getIid(), true);
1915
    }
1916
1917
    send_notification_mails(
1918
        $forum,
1919
        $thread,
1920
        $reply_info,
1921
        $course
1922
    );
1923
1924
    Session::erase('formelements');
1925
    Session::erase('origin');
1926
    Session::erase('breadcrumbs');
1927
    Session::erase('addedresource');
1928
    Session::erase('addedresourceid');
1929
1930
    if ($showMessage) {
1931
        Display::addFlash(Display::return_message($message, 'success', false));
1932
    }
1933
1934
    return $thread;
1935
}
1936
1937
/**
1938
 * This function displays the form that is used to add a post. This can be a new thread or a reply.
1939
 *
1940
 * @param string $action
1941
 *                            is the parameter that determines if we are
1942
 *                            2. replythread: Replying to a thread ($action = replythread) => I-frame with the complete
1943
 *                            thread (if enabled)
1944
 *                            3. replymessage: Replying to a message ($action =replymessage) => I-frame with the
1945
 *                            complete thread (if enabled)
1946
 *                            (I first thought to put and I-frame with the message only)
1947
 *                            4. quote: Quoting a message ($action= quotemessage) => I-frame with the complete thread
1948
 *                            (if enabled). The message will be in the reply. (I first thought not to put an I-frame
1949
 *                            here)
1950
 * @param array  $form_values
1951
 * @param bool   $showPreview
1952
 *
1953
 * @return FormValidator
1954
 */
1955
function show_add_post_form(CForum $forum, CForumThread $thread, CForumPost $post = null, $action, $form_values, $showPreview = true)
1956
{
1957
    $_user = api_get_user_info();
1958
    $action = isset($action) ? Security::remove_XSS($action) : '';
1959
    $threadId = $thread->getIid();
1960
    $forumId = $forum->getIid();
1961
    $giveRevision = isset($_GET['give_revision']) && 1 == $_GET['give_revision'];
1962
    $postId = $post ? $post->getIid() : 0;
1963
1964
    $url = api_get_self().'?'.http_build_query(
1965
        [
1966
            'action' => $action,
1967
            'forum' => $forumId,
1968
            'thread' => $threadId,
1969
            'post' => $postId,
1970
        ]
1971
    ).'&'.api_get_cidreq();
1972
1973
    $form = new FormValidator(
1974
        'thread',
1975
        'post',
1976
        $url
1977
    );
1978
    $form->setConstants(['forum' => '5']);
1979
1980
    // Setting the form elements.
1981
    $form->addElement('hidden', 'forum_id', $forumId);
1982
    $form->addElement('hidden', 'thread_id', $threadId);
1983
    $form->addElement('hidden', 'action', $action);
1984
1985
    // If anonymous posts are allowed we also display a form to allow the user to put his name or username in.
1986
    if (1 == $forum->getAllowAnonymous() && !isset($_user['user_id'])) {
1987
        $form->addElement('text', 'poster_name', get_lang('Name'));
1988
        $form->applyFilter('poster_name', 'html_filter');
1989
    }
1990
1991
    $form->addElement('text', 'post_title', get_lang('Title'));
1992
    $form->applyFilter('post_title', 'html_filter');
1993
    $form->addHtmlEditor(
1994
        'post_text',
1995
        get_lang('Text'),
1996
        true,
1997
        false,
1998
        api_is_allowed_to_edit(null, true) ? [
1999
            'ToolbarSet' => 'Forum',
2000
            'Width' => '100%',
2001
            'Height' => '300',
2002
        ] : [
2003
            'ToolbarSet' => 'ForumStudent',
2004
            'Width' => '100%',
2005
            'Height' => '300',
2006
            'UserStatus' => 'student',
2007
        ]
2008
    );
2009
    $form->addRule('post_text', get_lang('Required field'), 'required');
2010
2011
    if (in_array($action, ['replythread', 'replymessage', 'quote'])) {
2012
        $extraFields = new ExtraField('forum_post');
2013
        $extraFields->addElements(
2014
            $form,
2015
            null,
2016
            [], //exclude
2017
            false, // filter
2018
            false, // tag as select
2019
            ['ask_for_revision'], //show only fields
2020
            [], // order fields
2021
            [] // extra data);
2022
        );
2023
    }
2024
2025
    if (in_array($action, ['quote', 'replymessage'])) {
2026
        $form->addFile('user_upload[]', get_lang('Attachment'));
2027
        $form->addButton(
2028
            'add_attachment',
2029
            get_lang('Add attachment'),
2030
            'paperclip',
2031
            'plain',
2032
            'plain',
2033
            null,
2034
            ['id' => 'reply-add-attachment']
2035
        );
2036
    } else {
2037
        $form->addFile('user_upload', get_lang('Attachment'));
2038
    }
2039
2040
    if ($giveRevision) {
2041
        $hide = ('true' === api_get_setting('forum.hide_forum_post_revision_language'));
2042
        $form->addHidden('give_revision', 1);
2043
        if (false === $hide) {
2044
            $extraField = new ExtraField('forum_post');
2045
            $extraField->addElements(
2046
                $form,
2047
                null,
2048
                [], //exclude
2049
                false, // filter
2050
                false, // tag as select
2051
                ['revision_language'], //show only fields
2052
                [], // order fields
2053
                [] // extra data
2054
            );
2055
        } else {
2056
            $form->addHidden('extra_revision_language', 1);
2057
        }
2058
    }
2059
2060
    // Setting the class and text of the form title and submit button.
2061
    if ('quote' === $action) {
2062
        $form->addButtonCreate(get_lang('Quote this message'), 'SubmitPost');
2063
    } elseif ('replythread' === $action) {
2064
        $form->addButtonCreate(get_lang('Reply to this thread'), 'SubmitPost');
2065
    } elseif ('replymessage' === $action) {
2066
        $form->addButtonCreate(get_lang('Reply to this message'), 'SubmitPost');
2067
    }
2068
2069
    $defaults['thread_peer_qualify'] = 0;
2070
    if (!empty($form_values)) {
2071
        $defaults['post_title'] = prepare4display($form_values['post_title']);
2072
        $defaults['post_text'] = prepare4display($form_values['post_text']);
2073
        $defaults['post_notification'] = (int) $form_values['post_notification'];
2074
        $defaults['thread_sticky'] = (int) $form_values['thread_sticky'];
2075
        $defaults['thread_peer_qualify'] = (int) $form_values['thread_peer_qualify'];
2076
    }
2077
2078
    // If we are quoting a message we have to retrieve the information of the post we are quoting so that
2079
    // we can add this as default to the textarea.
2080
    // We also need to put the parent_id of the post in a hidden form when
2081
    if (('quote' === $action || 'replymessage' === $action || $giveRevision) && !empty($postId)) {
2082
        // we are quoting or replying to a message (<> reply to a thread !!!)
2083
        $form->addHidden('post_parent_id', $post->getIid());
2084
        // If we are replying or are quoting then we display a default title.
2085
        $posterName = UserManager::formatUserFullName($post->getUser());
2086
        $defaults['post_title'] = get_lang('Re:').api_html_entity_decode($post->getTitle(), ENT_QUOTES);
2087
        // When we are quoting a message then we have to put that message into the wysiwyg editor.
2088
        // Note: The style has to be hardcoded here because using class="quote" didn't work.
2089
        if ('quote' === $action) {
2090
            $defaults['post_text'] = '<div>&nbsp;</div>
2091
                <div style="margin: 5px;">
2092
                    <div style="font-size: 90%; font-style: italic;">'.
2093
                get_lang('Quoting').' '.$posterName.':</div>
2094
                        <div style="color: #006600; font-size: 90%;  font-style: italic; background-color: #FAFAFA; border: #D1D7DC 1px solid; padding: 3px;">'.
2095
                prepare4display($post->getPostText()).'
2096
                        </div>
2097
                    </div>
2098
                <div>&nbsp;</div>
2099
                <div>&nbsp;</div>
2100
            ';
2101
        }
2102
        if ($giveRevision) {
2103
            $defaults['post_text'] = prepare4display($post->getPostText());
2104
        }
2105
    }
2106
2107
    $form->setDefaults(isset($defaults) ? $defaults : []);
2108
2109
    // The course admin can make a thread sticky (=appears with special icon and always on top).
2110
    $form->addRule('post_title', get_lang('Required field'), 'required');
2111
    if (1 == $forum->getAllowAnonymous() && !isset($_user['user_id'])) {
2112
        $form->addRule(
2113
            'poster_name',
2114
            get_lang('Required field'),
2115
            'required'
2116
        );
2117
    }
2118
2119
    if ($showPreview && 'newthread' !== $action && !empty($threadId)) {
2120
        $previewHtml = render_thread_preview_html((int) $forumId, (int) $threadId, (int) $postId, 20);
2121
        $form->addElement('html', '<div class="mt-4">'.$previewHtml.'</div>');
2122
    }
2123
2124
    // Validation or display
2125
    if ($form->validate()) {
2126
        $check = Security::check_token('post');
2127
        if ($check) {
2128
            $values = $form->getSubmitValues();
2129
            if (isset($values['thread_qualify_gradebook']) &&
2130
                '1' == $values['thread_qualify_gradebook'] &&
2131
                empty($values['weight_calification'])
2132
            ) {
2133
                Display::addFlash(
2134
                    Display::return_message(
2135
                        get_lang('You must assign a score to this activity').'&nbsp;<a href="javascript:window.history.go(-1);">'.get_lang('Back').'</a>',
2136
                        'error',
2137
                        false
2138
                    )
2139
                );
2140
2141
                return false;
2142
            }
2143
2144
            $postId = 0;
2145
            $threadId = 0;
2146
2147
            switch ($action) {
2148
                case 'quote':
2149
                case 'replythread':
2150
                case 'replymessage':
2151
                    $postId = store_reply($forum, $thread, $values);
2152
2153
                    break;
2154
            }
2155
2156
            if ($postId) {
2157
                if (isset($values['give_revision']) && 1 == $values['give_revision']) {
2158
                    $extraFieldValues = new ExtraFieldValue('forum_post');
2159
                    $revisionLanguage = isset($values['extra_revision_language']) ? $values['extra_revision_language'] : '';
2160
                    $params = [
2161
                        'item_id' => $postId,
2162
                        'extra_revision_language' => $revisionLanguage,
2163
                    ];
2164
2165
                    $extraFieldValues->saveFieldValues(
2166
                        $params,
2167
                        false,
2168
                        false,
2169
                        ['revision_language']
2170
                    );
2171
                }
2172
2173
                if (in_array($action, ['replythread', 'replymessage', 'quote'])) {
2174
                    $extraFieldValues = new ExtraFieldValue('forum_post');
2175
                    $params = [
2176
                        'item_id' => $postId,
2177
                        'extra_ask_for_revision' => isset($values['extra_ask_for_revision']) ? $values['extra_ask_for_revision'] : '',
2178
                    ];
2179
                    $extraFieldValues->saveFieldValues(
2180
                        $params,
2181
                        false,
2182
                        false,
2183
                        ['ask_for_revision']
2184
                    );
2185
                }
2186
            }
2187
2188
            $url = api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&'.http_build_query(
2189
                [
2190
                    'forum' => $forumId,
2191
                    'thread' => $thread->getIid(),
2192
                ]
2193
            );
2194
2195
            Security::clear_token();
2196
            header('Location: '.$url);
2197
            exit;
2198
        }
2199
    } else {
2200
        $token = Security::get_token();
2201
        $form->addElement('hidden', 'sec_token');
2202
        $form->setConstants(['sec_token' => $token]);
2203
2204
        // Delete from $_SESSION forum attachment from other posts
2205
        // and keep only attachments for new post
2206
        //clearAttachedFiles(FORUM_NEW_POST);
2207
        // Get forum attachment ajax table to add it to form
2208
        $attachmentAjaxTable = getAttachmentsAjaxTable(0, $forum->getIid());
2209
        $ajaxHtml = $attachmentAjaxTable;
2210
        $form->addElement('html', $ajaxHtml);
2211
2212
        return $form;
2213
    }
2214
}
2215
2216
/**
2217
 * Build a lightweight Tailwind-based preview for a thread (no iframe).
2218
 * Security: keep XSS protection as in the original iframe.
2219
 */
2220
function render_thread_preview_html(
2221
    int $forumId,
2222
    int $threadId,
2223
    int $highlightPostId = 0,
2224
    int $limit = 20,
2225
    string $heightClass = 'h-72'
2226
): string {
2227
    $tablePosts = Database::get_course_table(TABLE_FORUM_POST); // c_forum_post
2228
    $tableUsers = Database::get_main_table(TABLE_MAIN_USER);    // user
2229
2230
    $sql = "SELECT
2231
                p.iid,
2232
                p.post_date,
2233
                p.title      AS post_title,
2234
                p.post_text,
2235
                u.id         AS user_id,
2236
                u.username,
2237
                u.firstname,
2238
                u.lastname
2239
            FROM $tablePosts p
2240
            INNER JOIN $tableUsers u ON u.id = p.poster_id
2241
            WHERE p.thread_id = ".(int)$threadId."
2242
              AND p.visible = 1
2243
              AND (p.status IS NULL OR p.status = ".(int) CForumPost::STATUS_VALIDATED.")
2244
            ORDER BY p.post_date ASC
2245
            LIMIT ".(int)$limit;
2246
2247
    $res  = Database::query($sql);
2248
    $rows = [];
2249
    while ($r = Database::fetch_array($res)) {
2250
        $rows[] = $r;
2251
    }
2252
2253
    ob_start(); ?>
2254
    <div class="mt-6 w-full">
2255
        <div
2256
            class="forum-preview <?= $heightClass ?> overflow-y-auto border border-gray-25 rounded-xl p-3 bg-white"
2257
            role="region"
2258
            aria-label="<?= get_lang('Discussion thread'); ?>"
2259
        >
2260
            <div class="sticky top-0 z-10 bg-white pb-2 text-gray-90 font-bold shadow-[0_1px_0_0_#e4e9ed]">
2261
                <?= get_lang('Discussion thread'); ?>
2262
            </div>
2263
2264
            <?php if (empty($rows)): ?>
2265
                <div class="text-gray-50 text-body-2 mt-2"><?= get_lang('No posts yet'); ?></div>
2266
            <?php else: foreach ($rows as $row):
2267
                $postId  = (int) $row['iid'];
2268
                $isFocus = $highlightPostId && $postId === (int) $highlightPostId;
2269
2270
                // Compute display name (fallback to username)
2271
                $isAnon      = ((int)$row['user_id'] === (int)\Chamilo\CoreBundle\Entity\User::ANONYMOUS);
2272
                $displayName = $isAnon
2273
                    ? get_lang('Anonymous')
2274
                    : (api_get_person_name($row['firstname'], $row['lastname']) ?: $row['username']);
2275
2276
                $userTitle = api_htmlentities(sprintf(get_lang('Login: %s'), $row['username']), ENT_QUOTES);
2277
                ?>
2278
                <article id="post-<?= $postId ?>"
2279
                         class="mb-2 border border-gray-25 rounded-lg p-3 <?= $isFocus ? 'bg-gray-15 ring-1 ring-primary' : 'bg-white' ?>">
2280
                    <div class="text-caption text-gray-50" title="<?= $userTitle ?>">
2281
                        <?= Security::remove_XSS($displayName) ?> • <?= api_convert_and_format_date($row['post_date']) ?>
2282
                    </div>
2283
2284
                    <div class="font-bold mt-1 mb-1 text-gray-90">
2285
                        <?= Security::remove_XSS($row['post_title']) ?>
2286
                    </div>
2287
2288
                    <div class="text-body-2 leading-4">
2289
                        <?= Security::remove_XSS($row['post_text'], STUDENT) ?>
2290
                    </div>
2291
                </article>
2292
            <?php endforeach; endif; ?>
2293
        </div>
2294
2295
        <div class="mt-2">
2296
            <a class="text-body-2 underline text-primary"
2297
               href="<?= api_get_path(WEB_CODE_PATH) . 'forum/viewthread.php?' . api_get_cidreq()
2298
               . '&forum='.(int)$forumId.'&thread='.(int)$threadId ?>">
2299
                <?= get_lang('View full thread') ?>
2300
            </a>
2301
        </div>
2302
    </div>
2303
    <?php
2304
    if ($highlightPostId) {
2305
        // Try to bring the highlighted post into view.
2306
        echo '<script>try{document.getElementById("post-'.$highlightPostId.'")?.scrollIntoView({behavior:"instant",block:"start"});}catch(e){}</script>';
2307
    }
2308
2309
    return (string) ob_get_clean();
2310
}
2311
2312
function newThread(CForum $forum, $form_values = '', $showPreview = true)
2313
{
2314
    $_user = api_get_user_info();
2315
    $forumId = $forum->getIid();
2316
    $my_post = isset($_GET['post']) ? (int) $_GET['post'] : '';
2317
    $giveRevision = isset($_GET['give_revision']) && 1 == $_GET['give_revision'];
2318
    $action = 'new_thread';
2319
2320
    $url = api_get_self().'?'.http_build_query(
2321
            [
2322
                'action' => $action,
2323
                'forum' => $forumId,
2324
                'post' => $my_post,
2325
            ]
2326
        ).'&'.api_get_cidreq();
2327
2328
    $form = new FormValidator(
2329
        'thread',
2330
        'post',
2331
        $url
2332
    );
2333
2334
    // Setting the form elements.
2335
    $form->addElement('hidden', 'forum_id', $forumId);
2336
    $form->addElement('hidden', 'thread_id', 0);
2337
    $form->addElement('hidden', 'action', $action);
2338
2339
    // If anonymous posts are allowed we also display a form to allow the user to put his name or username in.
2340
    if (1 == $forum->getAllowAnonymous() && !isset($_user['user_id'])) {
2341
        $form->addElement('text', 'poster_name', get_lang('Name'));
2342
        $form->applyFilter('poster_name', 'html_filter');
2343
    }
2344
2345
    $form->addElement('text', 'post_title', get_lang('Title'));
2346
    $form->applyFilter('post_title', 'html_filter');
2347
    $form->addHtmlEditor(
2348
        'post_text',
2349
        get_lang('Text'),
2350
        true,
2351
        false,
2352
        api_is_allowed_to_edit(null, true) ? [
2353
            'ToolbarSet' => 'Forum',
2354
            'Width' => '100%',
2355
            'Height' => '300',
2356
        ] : [
2357
            'ToolbarSet' => 'ForumStudent',
2358
            'Width' => '100%',
2359
            'Height' => '300',
2360
            'UserStatus' => 'student',
2361
        ]
2362
    );
2363
    $form->addRule('post_text', get_lang('Required field'), 'required');
2364
2365
    $extraFields = new ExtraField('forum_post');
2366
    $extraFields->addElements(
2367
        $form,
2368
        null,
2369
        [], //exclude
2370
        false, // filter
2371
        false, // tag as select
2372
        ['ask_for_revision'], //show only fields
2373
        [], // order fields
2374
        [] // extra data);
2375
    );
2376
2377
    if (Gradebook::is_active() &&
2378
        (api_is_course_admin() || api_is_session_general_coach() || api_is_course_tutor())
2379
    ) {
2380
        $form->addElement('advanced_settings', 'advanced_params', get_lang('Advanced settings'));
2381
        $form->addElement('html', '<div id="advanced_params_options" style="display:none">');
2382
2383
        // Thread qualify
2384
        if (Gradebook::is_active()) {
2385
            //Loading gradebook select
2386
            GradebookUtils::load_gradebook_select_in_tool($form);
2387
            $form->addCheckBox(
2388
                'thread_qualify_gradebook',
2389
                '',
2390
                get_lang('Grade this thread'),
2391
                ['onclick' => 'javascript:if(this.checked==true){document.getElementById(\'options_field\').style.display = \'block\';}else{document.getElementById(\'options_field\').style.display = \'none\';}']
2392
            );
2393
        } else {
2394
            $form->addElement('hidden', 'thread_qualify_gradebook', false);
2395
        }
2396
2397
        $form->addElement('html', '<div id="options_field" style="display:none">');
2398
        $form->addElement('text', 'numeric_calification', get_lang('Maximum score'));
2399
        $form->applyFilter('numeric_calification', 'html_filter');
2400
        $form->addElement('text', 'calification_notebook_title', get_lang('Column header in Competences Report'));
2401
        $form->applyFilter('calification_notebook_title', 'html_filter');
2402
2403
        $form->addElement(
2404
            'text',
2405
            'weight_calification',
2406
            get_lang('Weight in Report'),
2407
            ['value' => '0.00', 'onfocus' => 'javascript: this.select();']
2408
        );
2409
        $form->applyFilter('weight_calification', 'html_filter');
2410
2411
        $group = [];
2412
        $group[] = $form->createElement('radio', 'thread_peer_qualify', null, get_lang('Yes'), 1);
2413
        $group[] = $form->createElement('radio', 'thread_peer_qualify', null, get_lang('No'), 0);
2414
        $form->addGroup(
2415
            $group,
2416
            '',
2417
            [
2418
                get_lang('Thread scored by peers'),
2419
                get_lang('If selected, this option will require each student to qualify at least 2 other students in order to get his score greater than 0 in the gradebook.'),
2420
            ]
2421
        );
2422
        $form->addElement('html', '</div>');
2423
        $form->addElement('html', '</div>');
2424
    }
2425
2426
    SkillModel::addSkillsToForm($form, ITEM_TYPE_FORUM_THREAD, 0);
2427
    $form->addElement('checkbox', 'thread_sticky', '', get_lang('This is a sticky message (appears always on top and has a special sticky icon)'));
2428
2429
    $form->addFile('user_upload', get_lang('Attachment'));
2430
2431
    if ($giveRevision) {
2432
        $hide = ('true' === api_get_setting('forum.hide_forum_post_revision_language'));
2433
        $form->addHidden('give_revision', 1);
2434
        if (false === $hide) {
2435
            $extraField = new ExtraField('forum_post');
2436
            $extraField->addElements(
2437
                $form,
2438
                null,
2439
                [], //exclude
2440
                false, // filter
2441
                false, // tag as select
2442
                ['revision_language'], //show only fields
2443
                [], // order fields
2444
                [] // extra data
2445
            );
2446
        } else {
2447
            $form->addHidden('extra_revision_language', 1);
2448
        }
2449
    }
2450
    $form->addButtonCreate(get_lang('Create thread'), 'SubmitPost');
2451
2452
    $defaults['thread_peer_qualify'] = 0;
2453
    if (!empty($form_values)) {
2454
        $defaults['post_title'] = prepare4display($form_values['post_title']);
2455
        $defaults['post_text'] = prepare4display($form_values['post_text']);
2456
        $defaults['post_notification'] = (int) $form_values['post_notification'];
2457
        $defaults['thread_sticky'] = (int) $form_values['thread_sticky'];
2458
        $defaults['thread_peer_qualify'] = (int) $form_values['thread_peer_qualify'];
2459
    }
2460
2461
    $form->setDefaults(isset($defaults) ? $defaults : []);
2462
2463
    // The course admin can make a thread sticky (=appears with special icon and always on top).
2464
    $form->addRule('post_title', get_lang('Required field'), 'required');
2465
    if (1 == $forum->getAllowAnonymous() && !isset($_user['user_id'])) {
2466
        $form->addRule(
2467
            'poster_name',
2468
            get_lang('Required field'),
2469
            'required'
2470
        );
2471
    }
2472
2473
    // Validation or display
2474
    if ($form->validate()) {
2475
        $check = Security::check_token('post');
2476
        if ($check) {
2477
            $values = $form->getSubmitValues();
2478
            if (isset($values['thread_qualify_gradebook']) &&
2479
                '1' == $values['thread_qualify_gradebook'] &&
2480
                empty($values['weight_calification'])
2481
            ) {
2482
                Display::addFlash(
2483
                    Display::return_message(
2484
                        get_lang('You must assign a score to this activity').'&nbsp;<a href="javascript:window.history.go(-1);">'.get_lang('Back').'</a>',
2485
                        'error',
2486
                        false
2487
                    )
2488
                );
2489
2490
                return false;
2491
            }
2492
2493
            $newThread = saveThread($forum, $values);
2494
            if ($newThread) {
2495
                SkillModel::saveSkills($form, ITEM_TYPE_FORUM_THREAD, $newThread->getIid());
2496
                $post = $newThread->getThreadLastPost();
2497
2498
                if ($post) {
2499
                    $postId = $post->getIid();
2500
2501
                    if (isset($values['give_revision']) && 1 == $values['give_revision']) {
2502
                        $extraFieldValues = new ExtraFieldValue('forum_post');
2503
                        $revisionLanguage = isset($values['extra_revision_language']) ? $values['extra_revision_language'] : '';
2504
2505
                        $params = [
2506
                            'item_id' => $postId,
2507
                            'extra_revision_language' => $revisionLanguage,
2508
                        ];
2509
2510
                        $extraFieldValues->saveFieldValues(
2511
                            $params,
2512
                            false,
2513
                            false,
2514
                            ['revision_language']
2515
                        );
2516
                    }
2517
                    $extraFieldValues = new ExtraFieldValue('forum_post');
2518
                    $params = [
2519
                        'item_id' => $postId,
2520
                        'extra_ask_for_revision' => $values['extra_ask_for_revision'] ?? '',
2521
                    ];
2522
                    $extraFieldValues->saveFieldValues(
2523
                        $params,
2524
                        false,
2525
                        false,
2526
                        ['ask_for_revision']
2527
                    );
2528
                }
2529
            }
2530
2531
            $url = api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&'.http_build_query(
2532
                [
2533
                    'forum' => $forumId,
2534
                    'thread' => $newThread->getIid(),
2535
                ]
2536
            );
2537
2538
            Security::clear_token();
2539
            header('Location: '.$url);
2540
            exit;
2541
        }
2542
    } else {
2543
        $token = Security::get_token();
2544
        $form->addElement('hidden', 'sec_token');
2545
        $form->setConstants(['sec_token' => $token]);
2546
2547
        // Delete from $_SESSION forum attachment from other posts
2548
        // and keep only attachments for new post
2549
        //clearAttachedFiles(FORUM_NEW_POST);
2550
        // Get forum attachment ajax table to add it to form
2551
        $attachmentAjaxTable = getAttachmentsAjaxTable(0, $forum->getIid());
2552
        $ajaxHtml = $attachmentAjaxTable;
2553
        $form->addElement('html', $ajaxHtml);
2554
2555
        return $form;
2556
    }
2557
}
2558
2559
/**
2560
 * @param CForumThread $threadInfo
2561
 * @param int          $user_id
2562
 * @param int          $thread_id
2563
 * @param int          $thread_qualify
2564
 * @param int          $qualify_time
2565
 * @param int          $session_id
2566
 *
2567
 * @return string
2568
 *
2569
 * @author Isaac Flores <[email protected]>, U.N.A.S University
2570
 *
2571
 * @version October 2008, dokeos  1.8.6
2572
 */
2573
function saveThreadScore(
2574
    CForumThread $threadEntity,
2575
    $user_id,
2576
    $thread_id,
2577
    $thread_qualify,
2578
    $qualify_time,
2579
    $session_id
2580
) {
2581
    $table_threads_qualify = Database::get_course_table(TABLE_FORUM_THREAD_QUALIFY);
2582
2583
    $course_id = api_get_course_int_id();
2584
    $session_id = (int) $session_id;
2585
    $thread_id = (int) $thread_id;
2586
    $user_id = (int) $user_id;
2587
    $thread_qualify = (float) $thread_qualify;
2588
    $currentUserId = api_get_user_id();
2589
    $qualify_time = Database::escape_string($qualify_time);
2590
2591
    $max = $threadEntity->getThreadQualifyMax();
2592
    if ($thread_qualify <= $max) {
2593
        if ($threadEntity->isThreadPeerQualify()) {
2594
            $sql = "SELECT COUNT(*) FROM $table_threads_qualify
2595
                    WHERE
2596
                        user_id = $user_id AND
2597
                        qualify_user_id = $currentUserId AND
2598
                        thread_id = ".$thread_id;
2599
        } else {
2600
            $sql = "SELECT COUNT(*) FROM $table_threads_qualify
2601
                    WHERE
2602
                        user_id = $user_id AND
2603
                        thread_id = ".$thread_id;
2604
        }
2605
2606
        $result = Database::query($sql);
2607
        $row = Database::fetch_array($result);
2608
2609
        if (0 == $row[0]) {
2610
            $sql = "INSERT INTO $table_threads_qualify (c_id, user_id, thread_id,qualify,qualify_user_id,qualify_time,session_id)
2611
                    VALUES (".$course_id.", '".$user_id."','".$thread_id."',".$thread_qualify.", '".$currentUserId."','".$qualify_time."','".$session_id."')";
2612
            Database::query($sql);
2613
2614
            return 'insert';
2615
        } else {
2616
            saveThreadScoreHistory(
2617
                '1',
2618
                $course_id,
2619
                $user_id,
2620
                $thread_id
2621
            );
2622
2623
            // Update
2624
            $sql = "UPDATE $table_threads_qualify
2625
                    SET
2626
                        qualify = '".$thread_qualify."',
2627
                        qualify_time = '".$qualify_time."'
2628
                    WHERE
2629
                        user_id=".$user_id.' AND
2630
                        thread_id='.$thread_id." AND
2631
                        qualify_user_id = $currentUserId
2632
                    ";
2633
            Database::query($sql);
2634
2635
            return 'update';
2636
        }
2637
    }
2638
2639
    return '';
2640
}
2641
2642
/**
2643
 * This function shows qualify.
2644
 *
2645
 * @param string $option    contains the information of option to run
2646
 * @param int    $user_id   contains the information the current user id
2647
 * @param int    $thread_id contains the information the current thread id
2648
 *
2649
 * @return int qualify
2650
 *             <code> $option=1 obtained the qualification of the current thread</code>
2651
 *
2652
 * @author Isaac Flores <[email protected]>, U.N.A.S University
2653
 *
2654
 * @version October 2008, dokeos  1.8.6
2655
 */
2656
function showQualify($option, $user_id, $thread_id)
2657
{
2658
    $table_threads_qualify = Database::get_course_table(TABLE_FORUM_THREAD_QUALIFY);
2659
    $table_threads = Database::get_course_table(TABLE_FORUM_THREAD);
2660
2661
    $course_id = api_get_course_int_id();
2662
    $user_id = (int) $user_id;
2663
    $thread_id = (int) $thread_id;
2664
2665
    if (empty($user_id) || empty($thread_id)) {
2666
        return 0;
2667
    }
2668
2669
    $sql = '';
2670
    switch ($option) {
2671
        case 1:
2672
            $sql = "SELECT qualify FROM $table_threads_qualify
2673
                    WHERE
2674
                        c_id = $course_id AND
2675
                        user_id=".$user_id.' AND
2676
                        thread_id='.$thread_id;
2677
2678
            break;
2679
        case 2:
2680
            $sql = "SELECT thread_qualify_max FROM $table_threads
2681
                    WHERE c_id = $course_id AND iid=".$thread_id;
2682
2683
            break;
2684
    }
2685
2686
    if (!empty($sql)) {
2687
        $rs = Database::query($sql);
2688
        $row = Database::fetch_array($rs);
2689
        if ($row) {
2690
            return $row[0];
2691
        }
2692
    }
2693
2694
    return 0;
2695
}
2696
2697
/**
2698
 * This function gets qualify historical.
2699
 *
2700
 * @param int  $user_id   contains the information the current user id
2701
 * @param int  $thread_id contains the information the current thread id
2702
 * @param bool $opt       contains the information of option to run
2703
 *
2704
 * @return array
2705
 *
2706
 * @author Christian Fasanando <[email protected]>,
2707
 * @author Isaac Flores <[email protected]>,
2708
 *
2709
 * @version October 2008, dokeos  1.8.6
2710
 */
2711
function getThreadScoreHistory($user_id, $thread_id, $opt)
2712
{
2713
    $user_id = (int) $user_id;
2714
    $thread_id = (int) $thread_id;
2715
2716
    $table_threads_qualify_log = Database::get_course_table(TABLE_FORUM_THREAD_QUALIFY_LOG);
2717
    $course_id = api_get_course_int_id();
2718
2719
    if ('false' == $opt) {
2720
        $sql = "SELECT * FROM $table_threads_qualify_log
2721
                WHERE
2722
                    c_id = $course_id AND
2723
                    thread_id='".$thread_id."' AND
2724
                    user_id='".$user_id."'
2725
                ORDER BY qualify_time";
2726
    } else {
2727
        $sql = "SELECT * FROM $table_threads_qualify_log
2728
                WHERE
2729
                    c_id = $course_id AND
2730
                    thread_id='".$thread_id."' AND
2731
                    user_id='".$user_id."'
2732
                ORDER BY qualify_time DESC";
2733
    }
2734
    $rs = Database::query($sql);
2735
    $log = [];
2736
    while ($row = Database::fetch_assoc($rs)) {
2737
        $log[] = $row;
2738
    }
2739
2740
    return $log;
2741
}
2742
2743
/**
2744
 * This function stores qualify historical.
2745
 *
2746
 * @param bool contains the information of option to run
2747
 * @param string contains the information the current course id
2748
 * @param int contains the information the current forum id
2749
 * @param int contains the information the current user id
2750
 * @param int contains the information the current thread id
2751
 * @param int contains the information the current qualify
2752
 * @param string $option
2753
 * @param int    $course_id
2754
 * @param int    $user_id
2755
 * @param int    $thread_id
2756
 *
2757
 * @author Isaac Flores <[email protected]>, U.N.A.S University
2758
 *
2759
 * @version October 2008, dokeos  1.8.6
2760
 */
2761
function saveThreadScoreHistory(
2762
    $option,
2763
    $course_id,
2764
    $user_id,
2765
    $thread_id
2766
) {
2767
    $table_threads_qualify = Database::get_course_table(TABLE_FORUM_THREAD_QUALIFY);
2768
    $table_threads_qualify_log = Database::get_course_table(TABLE_FORUM_THREAD_QUALIFY_LOG);
2769
2770
    $thread_id = (int) $thread_id;
2771
    $course_id = (int) $course_id;
2772
    $user_id = (int) $user_id;
2773
    $qualify_user_id = api_get_user_id();
2774
2775
    if (1 == $option) {
2776
        // Extract information of thread_qualify.
2777
        $sql = "SELECT qualify, qualify_time
2778
                FROM $table_threads_qualify
2779
                WHERE
2780
                    c_id = $course_id AND
2781
                    user_id = ".$user_id.' AND
2782
                    thread_id = '.$thread_id." AND
2783
                    qualify_user_id = $qualify_user_id
2784
                ";
2785
        $rs = Database::query($sql);
2786
        $row = Database::fetch_array($rs);
2787
2788
        // Insert thread_historical.
2789
        $sql = "INSERT INTO $table_threads_qualify_log (c_id, user_id, thread_id, qualify, qualify_user_id,qualify_time,session_id)
2790
                VALUES(".$course_id.", '".$user_id."','".$thread_id."',".(float) $row[0].", '".$qualify_user_id."','".$row[1]."','')";
2791
        Database::query($sql);
2792
    }
2793
}
2794
2795
/**
2796
 * This function shows current thread qualify .
2797
 *
2798
 * @param int $threadId
2799
 * @param int $sessionId
2800
 * @param int $userId
2801
 *
2802
 * @return array or null if is empty
2803
 *
2804
 * @author Isaac Flores <[email protected]>, U.N.A.S University
2805
 *
2806
 * @version December 2008, dokeos  1.8.6
2807
 */
2808
function current_qualify_of_thread($threadId, $sessionId, $userId)
2809
{
2810
    $table_threads_qualify = Database::get_course_table(TABLE_FORUM_THREAD_QUALIFY);
2811
2812
    $course_id = api_get_course_int_id();
2813
    $currentUserId = api_get_user_id();
2814
    $sessionId = (int) $sessionId;
2815
    $threadId = (int) $threadId;
2816
2817
    $sql = "SELECT qualify FROM $table_threads_qualify
2818
            WHERE
2819
                c_id = $course_id AND
2820
                thread_id = $threadId AND
2821
                session_id = $sessionId AND
2822
                qualify_user_id = $currentUserId AND
2823
                user_id = $userId
2824
            ";
2825
    $res = Database::query($sql);
2826
    $row = Database::fetch_assoc($res);
2827
2828
    if ($row) {
2829
        return $row['qualify'];
2830
    }
2831
2832
    return 0;
2833
}
2834
2835
/**
2836
 * This function stores a reply in the forum_post table.
2837
 * It also updates the forum_threads table (thread_replies +1 , thread_last_post, thread_date).
2838
 *
2839
 * @param array $values
2840
 * @param int   $courseId Optional
2841
 * @param int   $userId   Optional
2842
 *
2843
 * @return int post id
2844
 */
2845
function store_reply(CForum $forum, CForumThread $thread, $values, $courseId = 0, $userId = 0)
2846
{
2847
    $courseId = !empty($courseId) ? $courseId : api_get_course_int_id();
2848
    $post_date = api_get_utc_datetime();
2849
    $userId = $userId ?: api_get_user_id();
2850
2851
    if (1 == $forum->getAllowAnonymous()) {
2852
        if (api_is_anonymous() && empty($userId)) {
2853
            $userId = api_get_anonymous_id();
2854
        }
2855
    }
2856
2857
    if (empty($userId)) {
2858
        return false;
2859
    }
2860
2861
    $visible = 1;
2862
    if ('1' == $forum->getApprovalDirectPost() &&
2863
        !api_is_allowed_to_edit(null, true)
2864
    ) {
2865
        $visible = 0;
2866
    }
2867
2868
    $upload_ok = 1;
2869
    $new_post_id = 0;
2870
2871
    if ($upload_ok) {
2872
        $course = api_get_course_entity($courseId);
2873
        $session = api_get_session_entity();
2874
2875
        $repo = Container::getForumPostRepository();
2876
        $post = new CForumPost();
2877
        $text = empty($values['post_text']) ? '' : $values['post_text'];
2878
        $post
2879
            ->setTitle($values['post_title'])
2880
            ->setPostText($text)
2881
            ->setThread($thread)
2882
            ->setForum($forum)
2883
            ->setUser(api_get_user_entity($userId))
2884
            ->setPostNotification(isset($values['post_notification']) ? (bool) $values['post_notification'] : false)
2885
            ->setVisible($visible)
2886
            ->setPostDate(api_get_utc_datetime(null, false, true))
2887
            ->setParent($thread)
2888
            ->addCourseLink($course, $session)
2889
        ;
2890
2891
        if (isset($values['post_parent_id']) && !empty($values['post_parent_id'])) {
2892
            $parent = $repo->find($values['post_parent_id']);
2893
            $post->setPostParent($parent);
2894
        }
2895
        $repo->create($post);
2896
2897
        $new_post_id = $post->getIid();
2898
        if ($new_post_id) {
2899
            $values['new_post_id'] = $new_post_id;
2900
            $message = get_lang('The reply has been added');
2901
2902
            if (!empty($_POST['file_ids']) && is_array($_POST['file_ids'])) {
2903
                foreach ($_POST['file_ids'] as $key => $id) {
2904
                    editAttachedFile(
2905
                        [
2906
                            'comment' => $_POST['file_comments'][$key],
2907
                            'post_id' => $new_post_id,
2908
                        ],
2909
                        $id
2910
                    );
2911
                }
2912
            }
2913
2914
            // Update the thread.
2915
            updateThreadInfo($values['thread_id'], $new_post_id, $post_date);
2916
2917
            if ('1' == $forum->getApprovalDirectPost() &&
2918
                !api_is_allowed_to_edit(null, true)
2919
            ) {
2920
                $message .= '<br />'.get_lang('Your message has to be approved before people can view it.').'<br />';
2921
            }
2922
2923
            if ($forum->isModerated() &&
2924
                !api_is_allowed_to_edit(null, true)
2925
            ) {
2926
                $message .= '<br />'.get_lang('Your message has to be approved before people can view it.').'<br />';
2927
            }
2928
2929
            // Setting the notification correctly.
2930
            $my_post_notification = isset($values['post_notification']) ? $values['post_notification'] : null;
2931
            if (1 == $my_post_notification) {
2932
                set_notification('thread', $values['thread_id'], true);
2933
            }
2934
2935
            send_notification_mails(
2936
                $forum,
2937
                $thread,
2938
                $values,
2939
                $course
2940
            );
2941
            add_forum_attachment_file('', $post);
2942
2943
            $logInfo = [
2944
                'tool' => TOOL_FORUM,
2945
                'tool_id' => $values['forum_id'],
2946
                'tool_id_detail' => $values['thread_id'],
2947
                'action' => 'new-post',
2948
                'action_details' => $values['action'],
2949
                'info' => $values['post_title'],
2950
            ];
2951
            Event::registerLog($logInfo);
2952
        }
2953
2954
        Session::erase('formelements');
2955
        Session::erase('origin');
2956
        Session::erase('breadcrumbs');
2957
        Session::erase('addedresource');
2958
        Session::erase('addedresourceid');
2959
2960
        Display::addFlash(Display::return_message($message, 'confirmation', false));
2961
    } else {
2962
        Display::addFlash(
2963
            Display::return_message(
2964
                get_lang('No file was uploaded.').' '.get_lang('Please select a file before pressing the upload button.'),
2965
                'error'
2966
            )
2967
        );
2968
    }
2969
2970
    return $new_post_id;
2971
}
2972
2973
/**
2974
 * This function displays the form that is used to edit a post. This can be a new thread or a reply.
2975
 *
2976
 * @param CForumPost   $post        contains all the information about the current post
2977
 * @param CForumThread $thread      contains all the information about the current thread
2978
 * @param CForum       $forum       contains all info about the current forum (to check if attachments are allowed)
2979
 * @param array        $form_values contains the default values to fill the form
2980
 *
2981
 * @author Patrick Cool <[email protected]>, Ghent University
2982
 *
2983
 * @version february 2006, dokeos 1.8
2984
 */
2985
function show_edit_post_form(
2986
    $post,
2987
    $thread,
2988
    $forum,
2989
    $form_values,
2990
    $id_attach = 0
2991
) {
2992
    // Initialize the object.
2993
    $form = new FormValidator(
2994
        'edit_post',
2995
        'post',
2996
        api_get_self().'?'.api_get_cidreq().'&forum='.(int) ($_GET['forum']).'&thread='.(int) ($_GET['thread']).'&post='.(int) ($_GET['post'])
2997
    );
2998
    $form->addElement('header', get_lang('Edit a post'));
2999
    // Setting the form elements.
3000
    $form->addElement('hidden', 'post_id', $post->getIid());
3001
    $form->addElement('hidden', 'thread_id', $thread->getIid());
3002
    $form->addElement('hidden', 'id_attach', $id_attach);
3003
3004
    if (null === $post->getPostParent()) {
3005
        $form->addElement('hidden', 'is_first_post_of_thread', '1');
3006
    }
3007
3008
    $form->addElement('text', 'post_title', get_lang('Title'));
3009
    $form->applyFilter('post_title', 'html_filter');
3010
    $form->addElement(
3011
        'html_editor',
3012
        'post_text',
3013
        get_lang('Text'),
3014
        null,
3015
        api_is_allowed_to_edit(null, true) ? [
3016
            'ToolbarSet' => 'Forum',
3017
            'Width' => '100%',
3018
            'Height' => '400',
3019
        ] : [
3020
            'ToolbarSet' => 'ForumStudent',
3021
            'Width' => '100%',
3022
            'Height' => '400',
3023
            'UserStatus' => 'student',
3024
        ]
3025
    );
3026
    $form->addRule('post_text', get_lang('Required field'), 'required');
3027
3028
    $extraFields = new ExtraField('forum_post');
3029
    $extraFields->addElements($form, $post->getIid());
3030
3031
    $form->addButtonAdvancedSettings('advanced_params');
3032
    $form->addElement('html', '<div id="advanced_params_options" style="display:none">');
3033
3034
    if ($forum->isModerated() && api_is_allowed_to_edit(null, true)) {
3035
        $group = [];
3036
        $group[] = $form->createElement(
3037
            'radio',
3038
            'status',
3039
            null,
3040
            get_lang('Validated'),
3041
            1
3042
        );
3043
        $group[] = $form->createElement(
3044
            'radio',
3045
            'status',
3046
            null,
3047
            get_lang('Waiting for moderation'),
3048
            2
3049
        );
3050
        $group[] = $form->createElement(
3051
            'radio',
3052
            'status',
3053
            null,
3054
            get_lang('Rejected'),
3055
            3
3056
        );
3057
        $form->addGroup($group, 'status', get_lang('Status'));
3058
    }
3059
3060
    $defaults['status']['status'] = $post->getStatus();
3061
3062
    $form->addElement(
3063
        'checkbox',
3064
        'post_notification',
3065
        '',
3066
        get_lang('Notify me by e-mail when somebody replies')
3067
    );
3068
3069
    if (api_is_allowed_to_edit(null, true) && null === $post->getPostParent()) {
3070
        // The sticky checkbox only appears when it is the first post of a thread.
3071
        $form->addElement(
3072
            'checkbox',
3073
            'thread_sticky',
3074
            '',
3075
            get_lang('This is a sticky message (appears always on top and has a special sticky icon)')
3076
        );
3077
        if (1 == $thread->getThreadSticky()) {
3078
            $defaults['thread_sticky'] = true;
3079
        }
3080
    }
3081
3082
    $form->addElement('html', '</div>');
3083
3084
    $form->addFile('user_upload[]', get_lang('Attachment'));
3085
    $form->addButton(
3086
        'add_attachment',
3087
        get_lang('Add attachment'),
3088
        'paperclip',
3089
        'default',
3090
        'default',
3091
        null,
3092
        ['id' => 'reply-add-attachment']
3093
    );
3094
3095
    $form->addButtonUpdate(get_lang('Edit'), 'SubmitPost');
3096
3097
    // Setting the default values for the form elements.
3098
    $defaults['post_title'] = $post->getTitle();
3099
    $defaults['post_text'] = $post->getPostText();
3100
3101
    if (1 == $post->getPostNotification()) {
3102
        $defaults['post_notification'] = true;
3103
    }
3104
3105
    if (!empty($form_values)) {
3106
        $defaults['post_notification'] = Security::remove_XSS($form_values['post_notification']);
3107
        $defaults['thread_sticky'] = Security::remove_XSS($form_values['thread_sticky']);
3108
    }
3109
3110
    $form->setDefaults($defaults);
3111
3112
    // The course admin can make a thread sticky (=appears with special icon and always on top).
3113
    $form->addRule('post_title', get_lang('Required field'), 'required');
3114
3115
    // Validation or display
3116
    if ($form->validate()) {
3117
        $values = $form->exportValues();
3118
        $values['item_id'] = $post->getIid();
3119
        $extraFieldValues = new ExtraFieldValue('forum_post');
3120
        $extraFieldValues->saveFieldValues($values);
3121
3122
        store_edit_post($forum, $values);
3123
    } else {
3124
        // Delete from $_SESSION forum attachment from other posts
3125
        clearAttachedFiles($post->getIid());
3126
        // Get forum attachment ajax table to add it to form
3127
        $fileData = getAttachmentsAjaxTable($post->getIid(), $forum->getIid());
3128
        $form->addElement('html', $fileData);
3129
        $form->display();
3130
    }
3131
}
3132
3133
/**
3134
 * This function stores the edit of a post in the forum_post table.
3135
 *
3136
 * @author Patrick Cool <[email protected]>, Ghent University
3137
 *
3138
 * @version february 2006, dokeos 1.8
3139
 */
3140
function store_edit_post(CForum $forum, $values)
3141
{
3142
    $logInfo = [
3143
        'tool' => TOOL_FORUM,
3144
        'tool_id' => $_GET['forum'],
3145
        'tool_id_detail' => $values['thread_id'],
3146
        'action' => 'edit-post',
3147
        'action_details' => 'post',
3148
        'info' => $values['post_title'],
3149
    ];
3150
    Event::registerLog($logInfo);
3151
3152
    $threadTable = Database::get_course_table(TABLE_FORUM_THREAD);
3153
3154
    //check if this post is the first of the thread
3155
    // First we check if the change affects the thread and if so we commit
3156
    // the changes (sticky and post_title=thread_title are relevant).
3157
    $posts = getPosts($forum, $values['thread_id']);
3158
    $first_post = null;
3159
    if (!empty($posts) && count($posts) > 0 && isset($posts[0])) {
3160
        $first_post = $posts[0];
3161
    }
3162
3163
    if (!empty($first_post) && $first_post['post_id'] == $values['post_id']) {
3164
        // Simple edit
3165
        $params = [
3166
            'title' => $values['post_title'],
3167
            'thread_sticky' => isset($values['thread_sticky']) ? $values['thread_sticky'] : 0,
3168
        ];
3169
        $where = ['iid = ?' => [$values['thread_id']]];
3170
        Database::update($threadTable, $params, $where);
3171
    }
3172
3173
    $status = '';
3174
    $updateStatus = false;
3175
    if ($forum->isModerated()) {
3176
        if (api_is_allowed_to_edit(null, true)) {
3177
            $status = $values['status']['status'];
3178
            $updateStatus = true;
3179
        } else {
3180
            $status = CForumPost::STATUS_WAITING_MODERATION;
3181
            $updateStatus = true;
3182
        }
3183
    }
3184
3185
    $postId = $values['post_id'];
3186
    $repo = Container::getForumPostRepository();
3187
    /** @var CForumPost $post */
3188
    $post = $repo->find($postId);
3189
    if ($post) {
3190
        $post
3191
            ->setTitle($values['post_title'])
3192
            ->setPostText($values['post_text'])
3193
            ->setPostNotification(isset($values['post_notification']))
3194
        ;
3195
3196
        if ($updateStatus) {
3197
            $post->setStatus($status);
3198
        }
3199
        $repo->update($post);
3200
    }
3201
3202
    // Update attached files
3203
    if (!empty($_POST['file_ids']) && is_array($_POST['file_ids'])) {
3204
        foreach ($_POST['file_ids'] as $key => $id) {
3205
            editAttachedFile(
3206
                [
3207
                    'comment' => $_POST['file_comments'][$key],
3208
                    'post_id' => $values['post_id'],
3209
                ],
3210
                $id
3211
            );
3212
        }
3213
    }
3214
3215
    if (!empty($values['remove_attach'])) {
3216
        throw new Exception('remove_attach');
3217
    }
3218
3219
    if (empty($values['id_attach'])) {
3220
        add_forum_attachment_file(
3221
            isset($values['file_comment']) ? $values['file_comment'] : null,
3222
            $post
3223
        );
3224
    }
3225
3226
    $message = get_lang('The post has been modified').'<br />';
3227
    $message .= get_lang('You can now return to the').
3228
        ' <a href="viewforum.php?'.api_get_cidreq().'&forum='.(int) ($_GET['forum']).'&">'.
3229
        get_lang('Forum').'</a><br />';
3230
    $message .= get_lang('You can now return to the').
3231
        ' <a
3232
            href="viewthread.php?'.api_get_cidreq().'&forum='.(int) ($_GET['forum']).'&thread='.$values['thread_id'].'&post='.Security::remove_XSS($_GET['post']).'">'.
3233
        get_lang('Message').'</a>';
3234
3235
    Session::erase('formelements');
3236
    Session::erase('origin');
3237
    Session::erase('breadcrumbs');
3238
    Session::erase('addedresource');
3239
    Session::erase('addedresourceid');
3240
3241
    echo Display::return_message($message, 'confirmation', false);
3242
}
3243
3244
function displayUserLink(User $user)
3245
{
3246
    return '<a href="'.$user->getProfileUrl().'">'.
3247
        Security::remove_XSS(UserManager::formatUserFullName($user)).'</a>';
3248
}
3249
3250
function displayUserImage(User $user)
3251
{
3252
    $url = Container::getIllustrationRepository()->getIllustrationUrl($user, '', ICON_SIZE_BIG);
3253
3254
    return '<div class="thumbnail"><img src="'.$url.'?w=100"/></div>';
3255
}
3256
3257
/**
3258
 * The relies counter gets increased every time somebody replies to the thread.
3259
 *
3260
 * @author Patrick Cool <[email protected]>, Ghent University
3261
 *
3262
 * @version february 2006, dokeos 1.8
3263
 *
3264
 * @param int    $threadId
3265
 * @param string $lastPostId
3266
 * @param string $post_date
3267
 */
3268
function updateThreadInfo($threadId, $lastPostId, $post_date)
3269
{
3270
    $table_threads = Database::get_course_table(TABLE_FORUM_THREAD);
3271
    $threadId = (int) $threadId;
3272
    $lastPostId = (int) $lastPostId;
3273
3274
    $sql = "UPDATE $table_threads SET
3275
            thread_replies = thread_replies+1,
3276
            thread_last_post = '".$lastPostId."',
3277
            thread_date = '".Database::escape_string($post_date)."'
3278
            WHERE
3279
                iid ='".$threadId."'"; // this needs to be cleaned first
3280
    Database::query($sql);
3281
}
3282
3283
function approvePost(CForumPost $post, $action): string
3284
{
3285
    if ('invisible' === $action) {
3286
        $visibility = 0;
3287
        $message = 'PostMadeInvisible';
3288
    }
3289
3290
    if ('visible' === $action) {
3291
        $visibility = 1;
3292
        handle_mail_cue('post', $post->getIid());
3293
        $message = 'PostMadeVisible';
3294
    }
3295
3296
    $post->setVisible($visibility);
3297
    Database::getManager()->persist($post);
3298
    Database::getManager()->flush();
3299
3300
    return $message;
3301
}
3302
3303
/**
3304
 * This function retrieves all the unapproved messages for a given forum
3305
 * This is needed to display the icon that there are unapproved messages in that thread (only the courseadmin can see
3306
 * this).
3307
 *
3308
 * @param int $forum_id forum where we want to know the unapproved messages of
3309
 *
3310
 * @return array returns
3311
 *
3312
 * @author Patrick Cool <[email protected]>, Ghent University
3313
 *
3314
 * @version february 2006, dokeos 1.8
3315
 */
3316
function get_unaproved_messages($forum_id)
3317
{
3318
    $table_posts = Database::get_course_table(TABLE_FORUM_POST);
3319
    $course_id = api_get_course_int_id();
3320
3321
    $return_array = [];
3322
    $sql = "SELECT DISTINCT thread_id FROM $table_posts
3323
            WHERE
3324
                c_id = $course_id AND
3325
                forum_id='".Database::escape_string($forum_id)."' AND
3326
                visible='0' ";
3327
    $result = Database::query($sql);
3328
    while ($row = Database::fetch_array($result)) {
3329
        $return_array[] = $row['thread_id'];
3330
    }
3331
3332
    return $return_array;
3333
}
3334
3335
/**
3336
 * This function sends the notification mails to everybody who stated that they wanted to be informed when a new post
3337
 * was added to a given thread.
3338
 */
3339
function send_notification_mails(CForum $forum, CForumThread $thread, $reply_info, ?Course $course = null)
3340
{
3341
    $courseEntity = api_get_course_entity();
3342
3343
    if (null !== $course) {
3344
        $courseEntity = $course;
3345
    }
3346
3347
    $courseId = $courseEntity->getId();
3348
3349
    $sessionId = api_get_session_id();
3350
    $sessionEntity = api_get_session_entity($sessionId);
3351
3352
    $table = Database::get_course_table(TABLE_FORUM_MAIL_QUEUE);
3353
3354
    // First we need to check if
3355
    // 1. the forum category is visible
3356
    // 2. the forum is visible
3357
    // 3. the thread is visible
3358
    // 4. the reply is visible (=when there is)
3359
3360
    $current_forum_category = null;
3361
    if ($forum->getForumCategory()) {
3362
        $current_forum_category = $forum->getForumCategory();
3363
    }
3364
3365
    $send_mails = false;
3366
    if ($thread->isVisible($courseEntity) &&
3367
        $forum->isVisible($courseEntity) &&
3368
        ($current_forum_category && $forum->getForumCategory()->isVisible($courseEntity)) &&
3369
        '1' != $forum->getApprovalDirectPost()
3370
    ) {
3371
        $send_mails = true;
3372
    }
3373
3374
    // The forum category, the forum, the thread and the reply are visible to the user
3375
    if ($send_mails && !empty($forum)) {
3376
        $postId = isset($reply_info['new_post_id']) ? $reply_info['new_post_id'] : 0;
3377
        send_notifications($forum, $thread, $postId);
3378
    } else {
3379
        $table_notification = Database::get_course_table(TABLE_FORUM_NOTIFICATION);
3380
        if ($forum) {
3381
            $sql = "SELECT * FROM $table_notification
3382
                    WHERE
3383
                        c_id = ".$courseId." AND
3384
                        (
3385
                            forum_id = '".$forum->getIid()."' OR
3386
                            thread_id = '".$thread->getIid()."'
3387
                        ) ";
3388
            $result = Database::query($sql);
3389
            $user_id = api_get_user_id();
3390
            while ($row = Database::fetch_array($result)) {
3391
                $sql = "INSERT INTO $table (c_id, thread_id, post_id, user_id)
3392
                        VALUES (".$courseId.", '".$thread->getIid()."', '".(int) ($reply_info['new_post_id'])."', '$user_id' )";
3393
                Database::query($sql);
3394
            }
3395
        }
3396
    }
3397
}
3398
3399
/**
3400
 * This function is called whenever something is made visible because there might
3401
 * be new posts and the user might have indicated that (s)he wanted to be
3402
 * informed about the new posts by mail.
3403
 *
3404
 * @param string $content Content type (post, thread, forum, forum_category)
3405
 * @param int    $id      Item DB ID of the corresponding content type
3406
 *
3407
 * @return string language variable
3408
 *
3409
 * @author Patrick Cool <[email protected]>, Ghent University
3410
 *
3411
 * @version february 2006, dokeos 1.8
3412
 */
3413
function handle_mail_cue($content, $id)
3414
{
3415
    $table_mailcue = Database::get_course_table(TABLE_FORUM_MAIL_QUEUE);
3416
    $table_forums = Database::get_course_table(TABLE_FORUM);
3417
    $table_threads = Database::get_course_table(TABLE_FORUM_THREAD);
3418
    $table_posts = Database::get_course_table(TABLE_FORUM_POST);
3419
    $table_users = Database::get_main_table(TABLE_MAIN_USER);
3420
3421
    $course_id = api_get_course_int_id();
3422
    $id = (int) $id;
3423
3424
    /* If the post is made visible we only have to send mails to the people
3425
     who indicated that they wanted to be informed for that thread.*/
3426
    if ('post' === $content) {
3427
        // Getting the information about the post (need the threadId).
3428
        /** @var CForumPost $post */
3429
        $post = Container::getForumPostRepository()->find($id);
3430
        $thread_id = $post->getThread()->getIid();
3431
3432
        // Sending the mail to all the users that wanted to be informed for replies on this thread.
3433
        $sql = "SELECT users.firstname, users.lastname, users.id as user_id, users.email
3434
                FROM $table_mailcue mailcue, $table_posts posts, $table_users users
3435
                WHERE
3436
                    mailcue.c_id = $course_id AND
3437
                    posts.thread_id = $thread_id AND
3438
                    posts.post_notification = '1' AND
3439
                    mailcue.thread_id = $thread_id AND
3440
                    users.id = posts.poster_id AND
3441
                    users.active = 1
3442
                GROUP BY users.email";
3443
3444
        $result = Database::query($sql);
3445
        $forum = Container::getForumRepository()->find($post->getForum()->getIid());
3446
3447
        while ($row = Database::fetch_array($result)) {
3448
            send_mail($row, $forum, $post->getThread(), $post);
3449
        }
3450
    } elseif ('thread' === $content) {
3451
        // Sending the mail to all the users that wanted to be informed for replies on this thread.
3452
        $sql = "SELECT users.firstname, users.lastname, users.id as user_id, users.email, posts.forum_id
3453
                FROM $table_mailcue mailcue, $table_posts posts, $table_users users
3454
                WHERE
3455
                    mailcue.c_id = $course_id AND
3456
                    posts.thread_id = $id AND
3457
                    posts.post_notification = '1' AND
3458
                    mailcue.thread_id = $id AND
3459
                    users.id = posts.poster_id AND
3460
                    users.active = 1
3461
                GROUP BY users.email";
3462
        $result = Database::query($sql);
3463
        while ($row = Database::fetch_array($result)) {
3464
            $forum = Container::getForumRepository()->find($row['forum_id']);
3465
            $thread = Container::getForumThreadRepository()->find($id);
3466
            send_mail($row, $forum, $thread);
3467
        }
3468
3469
        // Deleting the relevant entries from the mailcue.
3470
        $sql = "DELETE FROM $table_mailcue
3471
                WHERE c_id = $course_id AND thread_id = $id";
3472
        Database::query($sql);
3473
    } elseif ('forum' === $content) {
3474
        $sql = "SELECT iid FROM $table_threads
3475
                WHERE forum_id = $id";
3476
        $result = Database::query($sql);
3477
        while ($row = Database::fetch_array($result)) {
3478
            handle_mail_cue('thread', $row['iid']);
3479
        }
3480
    } elseif ('forum_category' === $content) {
3481
        $sql = "SELECT iid FROM $table_forums
3482
                WHERE forum_category = $id";
3483
        $result = Database::query($sql);
3484
        while ($row = Database::fetch_array($result)) {
3485
            handle_mail_cue('forum', $row['iid']);
3486
        }
3487
    } else {
3488
        return get_lang('Error');
3489
    }
3490
}
3491
3492
/**
3493
 * This function sends the mails for the mail notification.
3494
 */
3495
function send_mail($userInfo, CForum $forum, CForumThread $thread, CForumPost $postInfo = null)
3496
{
3497
    if (empty($userInfo) || empty($forum) || empty($thread)) {
3498
        return false;
3499
    }
3500
3501
    $_course = api_get_course_info();
3502
    $user_id = api_get_user_id();
3503
    $forumId = $forum->getIid();
3504
    $threadId = $thread->getIid();
3505
3506
    $thread_link = api_get_path(WEB_CODE_PATH).
3507
        'forum/viewthread.php?'.api_get_cidreq(true, true, false).'&forum='.$forumId.'&thread='.$threadId;
3508
3509
    $email_body = get_lang('Dear').' '.
3510
        api_get_person_name($userInfo['firstname'], $userInfo['lastname'], null, PERSON_NAME_EMAIL_ADDRESS).", <br />\n\r";
3511
    $email_body .= get_lang('New Post in the forum').
3512
        ': '.$forum->getTitle().' - '.$thread->getTitle()." <br />\n";
3513
3514
    $courseId = (int) api_get_setting('forum.global_forums_course_id');
3515
    $subject = get_lang('New Post in the forum').' - '.
3516
        $_course['official_code'].': '.$forum->getTitle().' - '.$thread->getTitle()." <br />\n";
3517
3518
    $courseInfoTitle = get_lang('Course').': '.$_course['name'].' - ['.$_course['official_code']."] - <br />\n";
3519
    if (!empty($courseId) && $_course['real_id'] == $courseId) {
3520
        $subject = get_lang('New Post in the forum').': '.
3521
            $forum->getTitle().' - '.$thread->getTitle()." <br />\n";
3522
        $courseInfoTitle = " <br />\n";
3523
    }
3524
    $email_body .= $courseInfoTitle;
3525
3526
    if (!empty($postInfo)) {
3527
        $text = cut(strip_tags($postInfo->getPostText()), 100);
3528
        if (!empty($text)) {
3529
            $email_body .= get_lang('Message').": <br />\n ";
3530
            $email_body .= $text;
3531
            $email_body .= "<br /><br />\n";
3532
        }
3533
    }
3534
3535
    $email_body .= get_lang('You stated that you wanted to be informed by e-mail whenever somebody replies on the thread')."<br />\n";
3536
3537
    if (!empty($thread_link)) {
3538
        $email_body .= get_lang('The thread can be found here').' : <br /><a href="'.$thread_link.'">'.$thread_link."</a>\n";
3539
    }
3540
3541
    if ($userInfo['user_id'] != $user_id) {
3542
        MessageManager::send_message(
3543
            $userInfo['user_id'],
3544
            $subject,
3545
            $email_body,
3546
            [],
3547
            [],
3548
            null,
3549
            null,
3550
            null,
3551
            null,
3552
            $user_id
3553
        );
3554
    }
3555
}
3556
3557
/**
3558
 * This function displays the form for moving a thread to a different (already existing) forum.
3559
 *
3560
 * @author Patrick Cool <[email protected]>, Ghent University
3561
 *
3562
 * @version february 2006, dokeos 1.8
3563
 */
3564
function move_thread_form()
3565
{
3566
    $form = new FormValidator(
3567
        'movepost',
3568
        'post',
3569
        api_get_self().'?forum='.(int) ($_GET['forum']).'&thread='.(int) ($_GET['thread']).'&action='.Security::remove_XSS($_GET['action']).'&'.api_get_cidreq()
3570
    );
3571
    $form->addHeader(get_lang('Move Thread'));
3572
    // Invisible form: the threadId
3573
    $form->addHidden('thread_id', (int) ($_GET['thread']));
3574
    $forum_categories = get_forum_categories();
3575
3576
    $htmlcontent = '<div class="row">
3577
        <div class="label">
3578
            <span class="form_required">*</span>'.get_lang('Move to').'
3579
        </div>
3580
        <div class="formw">';
3581
    $htmlcontent .= '<select name="forum">';
3582
    foreach ($forum_categories as $category) {
3583
        $htmlcontent .= '<optgroup label="'.$category->getTitle().'">';
3584
        $forums = $category->getForums();
3585
        foreach ($forums as $forum) {
3586
            $htmlcontent .= '<option value="'.$forum->getIid().'">'.$forum->getTitle().'</option>';
3587
        }
3588
        $htmlcontent .= '</optgroup>';
3589
    }
3590
    $htmlcontent .= '</select>';
3591
    $htmlcontent .= '   </div>
3592
                    </div>';
3593
3594
    $form->addElement('html', $htmlcontent);
3595
3596
    // The OK button
3597
    $form->addButtonSave(get_lang('Move Thread'), 'SubmitForum');
3598
3599
    // Validation or display
3600
    if ($form->validate()) {
3601
        $values = $form->exportValues();
3602
        if (isset($_POST['forum'])) {
3603
            store_move_thread($values);
3604
            Display::addFlash(Display::return_message(get_lang('Moved.')));
3605
        }
3606
    } else {
3607
        return $form->returnForm();
3608
    }
3609
}
3610
3611
/**
3612
 * This function displays the form for moving a post message to a different (already existing) or a new thread.
3613
 *
3614
 * @author Patrick Cool <[email protected]>, Ghent University
3615
 *
3616
 * @version february 2006, dokeos 1.8
3617
 */
3618
function move_post_form()
3619
{
3620
    $form = new FormValidator(
3621
        'movepost',
3622
        'post',
3623
        api_get_self().'?'.api_get_cidreq().'&forum='.(int) ($_GET['forum']).'&thread='.(int) ($_GET['thread']).'&post='.Security::remove_XSS($_GET['post']).'&action='.Security::remove_XSS($_GET['action']).'&post='.Security::remove_XSS($_GET['post'])
3624
    );
3625
    // The header for the form
3626
    $form->addElement('header', '', get_lang('Move post'));
3627
3628
    // Invisible form: the post_id
3629
    $form->addElement('hidden', 'post_id', (int) ($_GET['post']));
3630
3631
    // Dropdown list: Threads of this forum
3632
    $threads = get_threads($_GET['forum']);
3633
    $threads_list[0] = get_lang('A new thread');
3634
    foreach ($threads as $thread) {
3635
        $threads_list[$thread->getIid()] = $thread->getTitle();
3636
    }
3637
    $form->addSelect('thread', get_lang('Move to thread'), $threads_list);
3638
    $form->applyFilter('thread', 'html_filter');
3639
3640
    // The OK button
3641
    $form->addButtonSave(get_lang('Move post'), 'submit');
3642
3643
    // Setting the rules
3644
    $form->addRule('thread', get_lang('Required field'), 'required');
3645
3646
    return $form;
3647
}
3648
3649
/**
3650
 * @param array $values
3651
 *
3652
 * @return string HTML language variable
3653
 *
3654
 * @author Patrick Cool <[email protected]>, Ghent University
3655
 *
3656
 * @version february 2006, dokeos 1.8
3657
 */
3658
function store_move_post($values)
3659
{
3660
    $_course = api_get_course_info();
3661
    $course_id = api_get_course_int_id();
3662
3663
    $table_forums = Database::get_course_table(TABLE_FORUM);
3664
    $table_threads = Database::get_course_table(TABLE_FORUM_THREAD);
3665
    $table_posts = Database::get_course_table(TABLE_FORUM_POST);
3666
3667
    $user = api_get_user_entity(api_get_user_id());
3668
    $course = api_get_course_entity($course_id);
3669
    $session = api_get_session_entity();
3670
3671
    if ('0' == $values['thread']) {
3672
        $repoPost = Container::getForumPostRepository();
3673
3674
        /** @var CForumPost $post */
3675
        $post = $repoPost->find($values['post_id']);
3676
        $forumId = $post->getForum()->getIid();
3677
        $threadId = $post->getThread()->getIid();
3678
3679
        $thread = new CForumThread();
3680
        $thread
3681
            ->setTitle($post->getTitle())
3682
            ->setForum($post->getForum())
3683
            ->setUser($post->getUser())
3684
            ->setThreadLastPost($post)
3685
            ->setThreadDate($post->getPostDate())
3686
            ->setParent($post->getForum())
3687
            ->addCourseLink(
3688
                $user,
3689
                $course,
3690
                $session
3691
            );
3692
3693
        $repo = Container::getForumThreadRepository();
3694
        $repo->create($thread);
3695
        $new_thread_id = $thread->getIid();
3696
3697
        // Moving the post to the newly created thread.
3698
        $sql = "UPDATE $table_posts SET thread_id='".$new_thread_id."', post_parent_id = NULL
3699
                WHERE iid ='".(int) ($values['post_id'])."'";
3700
        Database::query($sql);
3701
3702
        // Resetting the parent_id of the thread to 0 for all those who had this moved post as parent.
3703
        $sql = "UPDATE $table_posts SET post_parent_id = NULL
3704
                WHERE post_parent_id='".(int) ($values['post_id'])."'";
3705
        Database::query($sql);
3706
3707
        // Updating updating the number of threads in the forum.
3708
        $sql = "UPDATE $table_forums SET forum_threads=forum_threads+1
3709
                WHERE iid ='".$forumId."'";
3710
        Database::query($sql);
3711
3712
        // Resetting the last post of the old thread and decreasing the number of replies and the thread.
3713
        $sql = "SELECT * FROM $table_posts
3714
                WHERE thread_id='".$threadId."'
3715
                ORDER BY iid DESC";
3716
        $result = Database::query($sql);
3717
        $row = Database::fetch_array($result);
3718
        $sql = "UPDATE $table_threads SET
3719
                    thread_last_post='".$row['iid']."',
3720
                    thread_replies=thread_replies-1
3721
                WHERE
3722
                    iid ='".$threadId."'";
3723
        Database::query($sql);
3724
    } else {
3725
        // Moving to the chosen thread.
3726
        $sql = 'SELECT thread_id FROM '.$table_posts."
3727
                WHERE iid = '".$values['post_id']."' ";
3728
        $result = Database::query($sql);
3729
        $row = Database::fetch_array($result);
3730
3731
        $original_thread_id = $row['thread_id'];
3732
        $sql = 'SELECT thread_last_post FROM '.$table_threads."
3733
                WHERE iid = '".$original_thread_id."' ";
3734
3735
        $result = Database::query($sql);
3736
        $row = Database::fetch_array($result);
3737
        $thread_is_last_post = $row['thread_last_post'];
3738
        // If is this thread, update the thread_last_post with the last one.
3739
3740
        if ($thread_is_last_post == $values['post_id']) {
3741
            $sql = 'SELECT iid as post_id FROM '.$table_posts."
3742
                    WHERE
3743
                        thread_id = '".$original_thread_id."' AND
3744
                        iid <> '".$values['post_id']."'
3745
                    ORDER BY post_date DESC LIMIT 1";
3746
            $result = Database::query($sql);
3747
3748
            $row = Database::fetch_array($result);
3749
            $thread_new_last_post = $row['post_id'];
3750
3751
            $sql = 'UPDATE '.$table_threads."
3752
                    SET thread_last_post = '".$thread_new_last_post."'
3753
                    WHERE iid = '".$original_thread_id."' ";
3754
            Database::query($sql);
3755
        }
3756
3757
        $sql = "UPDATE $table_threads SET thread_replies=thread_replies-1
3758
                WHERE iid ='".$original_thread_id."'";
3759
        Database::query($sql);
3760
3761
        // moving to the chosen thread
3762
        $sql = "UPDATE $table_posts SET thread_id='".(int) ($_POST['thread'])."', post_parent_id = NULL
3763
                WHERE iid ='".(int) ($values['post_id'])."'";
3764
        Database::query($sql);
3765
3766
        // resetting the parent_id of the thread to 0 for all those who had this moved post as parent
3767
        $sql = "UPDATE $table_posts SET post_parent_id = NULL
3768
                WHERE post_parent_id='".(int) ($values['post_id'])."'";
3769
        Database::query($sql);
3770
3771
        $sql = "UPDATE $table_threads SET thread_replies=thread_replies+1
3772
                WHERE iid ='".(int) ($_POST['thread'])."'";
3773
        Database::query($sql);
3774
    }
3775
3776
    return get_lang('Thread moved');
3777
}
3778
3779
/**
3780
 * @param array $values
3781
 *
3782
 * @return string HTML language variable
3783
 *
3784
 * @author Patrick Cool <[email protected]>, Ghent University
3785
 *
3786
 * @version february 2006, dokeos 1.8
3787
 */
3788
function store_move_thread($values)
3789
{
3790
    $table_threads = Database::get_course_table(TABLE_FORUM_THREAD);
3791
    $table_posts = Database::get_course_table(TABLE_FORUM_POST);
3792
3793
    $courseId = api_get_course_int_id();
3794
    $sessionId = api_get_session_id();
3795
3796
    $forumId = (int) ($_POST['forum']);
3797
    $threadId = (int) ($_POST['thread_id']);
3798
    //$forumInfo = get_forums($forumId);
3799
3800
    // Change the thread table: Setting the forum_id to the new forum.
3801
    $sql = "UPDATE $table_threads SET forum_id = $forumId
3802
            WHERE c_id = $courseId AND iid = $threadId";
3803
    Database::query($sql);
3804
3805
    // Changing all the posts of the thread: setting the forum_id to the new forum.
3806
    $sql = "UPDATE $table_posts SET forum_id = $forumId
3807
            WHERE c_id = $courseId AND thread_id= $threadId";
3808
    Database::query($sql);
3809
3810
    // Fix group id, if forum is moved to a different group
3811
    /*if (!empty($forumInfo['to_group_id'])) {
3812
        $groupId = $forumInfo['to_group_id'];
3813
        $item = api_get_item_property_info(
3814
            $courseId,
3815
            TABLE_FORUM_THREAD,
3816
            $threadId,
3817
            $sessionId,
3818
            $groupId
3819
        );
3820
        $table = Database::get_course_table(TABLE_ITEM_PROPERTY);
3821
        $sessionCondition = api_get_session_condition($sessionId);
3822
3823
        if (!empty($item)) {
3824
            if ($item['to_group_id'] != $groupId) {
3825
                $sql = "UPDATE $table
3826
                    SET to_group_id = $groupId
3827
                    WHERE
3828
                      tool = '".TABLE_FORUM_THREAD."' AND
3829
                      c_id = $courseId AND
3830
                      ref = ".$item['ref']."
3831
                      $sessionCondition
3832
                ";
3833
                Database::query($sql);
3834
            }
3835
        } else {
3836
            $sql = "UPDATE $table
3837
                    SET to_group_id = $groupId
3838
                    WHERE
3839
                      tool = '".TABLE_FORUM_THREAD."' AND
3840
                      c_id = $courseId AND
3841
                      ref = ".$threadId."
3842
                      $sessionCondition
3843
            ";
3844
            Database::query($sql);
3845
        }
3846
    }*/
3847
3848
    return get_lang('Thread moved');
3849
}
3850
3851
/**
3852
 * Prepares a string for displaying by highlighting the search results inside, if any.
3853
 *
3854
 * @param string $input the input string
3855
 *
3856
 * @return string the same string with highlighted hits inside
3857
 *
3858
 * @author Patrick Cool <[email protected]>, Ghent University, February 2006 - the initial version.
3859
 * @author Ivan Tcholakov, March 2011 - adaptation for Chamilo LMS.
3860
 */
3861
function prepare4display($input)
3862
{
3863
    static $highlightcolors = ['yellow', '#33CC33', '#3399CC', '#9999FF', '#33CC33'];
3864
    static $search;
3865
3866
    if (!isset($search)) {
3867
        if (isset($_POST['search_term'])) {
3868
            $search = $_POST['search_term']; // No html at all.
3869
        } elseif (isset($_GET['search'])) {
3870
            $search = $_GET['search'];
3871
        } else {
3872
            $search = '';
3873
        }
3874
    }
3875
3876
    if (!empty($search)) {
3877
        if (strstr($search, '+')) {
3878
            $search_terms = explode('+', $search);
3879
        } else {
3880
            $search_terms[] = trim($search);
3881
        }
3882
        $counter = 0;
3883
        foreach ($search_terms as $key => $search_term) {
3884
            $input = api_preg_replace(
3885
                '/'.preg_quote(trim($search_term), '/').'/i',
3886
                '<span style="background-color: '.$highlightcolors[$counter].'">$0</span>',
3887
                $input
3888
            );
3889
            $counter++;
3890
        }
3891
    }
3892
3893
    // TODO: Security should be implemented outside this function.
3894
    // Change this to COURSEMANAGERLOWSECURITY or COURSEMANAGER to lower filtering and allow more styles
3895
    // (see comments of Security::remove_XSS() method to learn about other levels).
3896
3897
    return Security::remove_XSS($input, STUDENT, true);
3898
}
3899
3900
/**
3901
 * Display the search form for the forum and display the search results.
3902
 *
3903
 * @author Patrick Cool <[email protected]>, Ghent University, Belgium
3904
 *
3905
 * @version march 2008, dokeos 1.8.5
3906
 */
3907
function forum_search()
3908
{
3909
    $form = new FormValidator(
3910
        'forumsearch',
3911
        'post',
3912
        'forumsearch.php?'.api_get_cidreq()
3913
    );
3914
3915
    // Setting the form elements.
3916
    $form->addElement('header', '', get_lang('Search in the Forum'));
3917
    $form->addElement('text', 'search_term', get_lang('Search term'), ['autofocus']);
3918
    $form->applyFilter('search_term', 'html_filter');
3919
    $form->addElement('static', 'search_information', '', get_lang('Search in the Forum'));
3920
    $form->addButtonSearch(get_lang('Search'));
3921
3922
    // Setting the rules.
3923
    $form->addRule('search_term', get_lang('Required field'), 'required');
3924
    $form->addRule('search_term', get_lang('Too short'), 'minlength', 3);
3925
3926
    // Validation or display.
3927
    if ($form->validate()) {
3928
        $values = $form->exportValues();
3929
        $form->setDefaults($values);
3930
        $form->display();
3931
        // Display the search results.
3932
        displayForumSearchResults($values['search_term']);
3933
    } else {
3934
        $form->display();
3935
    }
3936
}
3937
3938
/**
3939
 * Displays the search results for forums, threads, and posts within a course.
3940
 */
3941
function displayForumSearchResults(string $searchTerm): void
3942
{
3943
    $forumRepo = Container::getForumRepository();
3944
    $threadRepo = Container::getForumThreadRepository();
3945
    $postRepo = Container::getForumPostRepository();
3946
3947
    $courseId = api_get_course_int_id();
3948
    $sessionId = api_get_session_id();
3949
    $course = api_get_course_entity($courseId);
3950
    $session = api_get_session_entity($sessionId);
3951
3952
    $searchTerms = explode(' ', $searchTerm);
3953
    $searchTerms = array_filter($searchTerms);
3954
3955
    $forumQb = $forumRepo->getResourcesByCourse($course, $session);
3956
    foreach ($searchTerms as $term) {
3957
        $forumQb->andWhere('resource.title LIKE :term OR resource.forumComment LIKE :term')
3958
            ->setParameter('term', '%' . $term . '%');
3959
    }
3960
    $forums = $forumQb->getQuery()->getResult();
3961
3962
    $threadQb = $threadRepo->getResourcesByCourse($course, $session);
3963
    foreach ($searchTerms as $term) {
3964
        $threadQb->andWhere('resource.title LIKE :term')
3965
            ->setParameter('term', '%' . $term . '%');
3966
    }
3967
    $threads = $threadQb->getQuery()->getResult();
3968
3969
    $postQb = $postRepo->getResourcesByCourse($course, $session);
3970
    foreach ($searchTerms as $term) {
3971
        $postQb->andWhere('resource.title LIKE :term OR resource.postText LIKE :term')
3972
            ->setParameter('term', '%' . $term . '%');
3973
    }
3974
    $posts = $postQb->getQuery()->getResult();
3975
3976
    $search_results = [];
3977
    foreach ($forums as $forum) {
3978
        if ($forum->isVisible($course) && $forum->getForumCategory()->isVisible($course)) {
3979
            $search_results[] = '<li class="mb-2"><a href="viewforum.php?'.api_get_cidreq().'&forum='.$forum->getIid().'" class="text-blue-500 hover:text-blue-600">'.htmlspecialchars($forum->getTitle()).'</a></li>';
3980
        }
3981
    }
3982
    foreach ($threads as $thread) {
3983
        if ($thread->isVisible($course)) {
3984
            $search_results[] = '<li class="mb-2"><a href="viewthread.php?'.api_get_cidreq().'&thread='.$thread->getIid().'&forum='.$thread->getForum()->getIid().'" class="text-blue-500 hover:text-blue-600">'.htmlspecialchars($thread->getTitle()).'</a></li>';
3985
        }
3986
    }
3987
    foreach ($posts as $post) {
3988
        if ($post->isVisible($course)) {
3989
            $search_results[] = '<li class="mb-2"><a href="viewthread.php?'.api_get_cidreq().'&thread='.$post->getThread()->getIid().'&forum='.$post->getForum()->getIid().'" class="text-blue-500 hover:text-blue-600">'.htmlspecialchars($post->getTitle()).'</a></li>';
3990
        }
3991
    }
3992
3993
    echo '<div class="text-lg font-semibold mb-4">'.count($search_results).' '.get_lang('Search results').'</div>';
3994
    echo '<ol class="list-decimal pl-5">'.implode($search_results).'</ol>';
3995
}
3996
3997
/**
3998
 * Return the link to the forum search page.
3999
 *
4000
 * @author Patrick Cool <[email protected]>, Ghent University, Belgium
4001
 *
4002
 * @version April 2008, dokeos 1.8.5
4003
 */
4004
function search_link()
4005
{
4006
    $return = '';
4007
    $origin = api_get_origin();
4008
    if ('learnpath' != $origin) {
4009
        $return = '<a href="forumsearch.php?'.api_get_cidreq().'&action=search"> ';
4010
        $return .= Display::getMdiIcon('magnify-plus-outline	', 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('Search')).'</a>';
4011
4012
        if (!empty($_GET['search'])) {
4013
            $return .= ': '.Security::remove_XSS($_GET['search']).' ';
4014
            $url = api_get_self().'?';
4015
            $url_parameter = [];
4016
            foreach ($_GET as $key => $value) {
4017
                if ('search' != $key) {
4018
                    $url_parameter[] = Security::remove_XSS($key).'='.Security::remove_XSS($value);
4019
                }
4020
            }
4021
            $url .= implode('&', $url_parameter);
4022
            $return .= '<a href="'.$url.'">'.Display::getMdiIcon(ActionIcon::DELETE, 'ch-tool-icon', '', ICON_SIZE_MEDIUM,  get_lang('Clean search results')).'</a>';
4023
        }
4024
    }
4025
4026
    return $return;
4027
}
4028
4029
/**
4030
 * This function adds an attachment file into a forum.
4031
 *
4032
 * @param string     $file_comment a comment about file
4033
 * @param CForumPost $post         from forum_post table
4034
 *
4035
 * @return false|null
4036
 */
4037
function add_forum_attachment_file($file_comment, CForumPost $post)
4038
{
4039
    $request = Container::getRequest();
4040
    if (false === $request->files->has('user_upload')) {
4041
        return false;
4042
    }
4043
4044
    $file = $request->files->get('user_upload');
4045
    if (empty($file)) {
4046
        return false;
4047
    }
4048
4049
    $files = [];
4050
    if ($file instanceof UploadedFile) {
4051
        $files[] = $file;
4052
    } else {
4053
        $files = $file;
4054
    }
4055
4056
    /** @var UploadedFile $attachment */
4057
    foreach ($files as $file) {
4058
        $valid = process_uploaded_file($file);
4059
4060
        if (!$valid) {
4061
            continue;
4062
        }
4063
4064
        // Try to add an extension to the file if it hasn't one.
4065
        $new_file_name = add_ext_on_mime(
4066
            stripslashes($file->getPathname()),
4067
            $file->getType()
4068
        );
4069
4070
        // User's file name
4071
        $file_name = $file->getClientOriginalName();
4072
4073
        if (!filter_extension($new_file_name)) {
4074
            Display::addFlash(
4075
                Display::return_message(
4076
                    get_lang('File upload failed: this file extension or file type is prohibited'),
4077
                    'error'
4078
                )
4079
            );
4080
4081
            return;
4082
        }
4083
4084
        $safe_file_comment = Database::escape_string($file_comment);
4085
4086
        $user = api_get_user_entity(api_get_user_id());
4087
        $course = api_get_course_entity(api_get_course_int_id());
4088
        $session = api_get_session_entity(api_get_session_id());
4089
4090
        $attachment = (new CForumAttachment())
4091
            ->setCId(api_get_course_int_id())
4092
            ->setComment($safe_file_comment)
4093
            ->setFilename($file_name)
4094
            ->setPath($file_name)
4095
            ->setPost($post)
4096
            ->setSize($file->getSize())
4097
            ->setParent($post)
4098
            ->addCourseLink(
4099
                $course,
4100
                $session
4101
            );
4102
4103
        $repo = Container::getForumAttachmentRepository();
4104
        $repo->create($attachment);
4105
        $repo->addFile($attachment, $file);
4106
        $repo->update($attachment);
4107
    }
4108
}
4109
4110
/**
4111
 * This function edits an attachment file into a forum.
4112
 *
4113
 * @param string $file_comment a comment about file
4114
 * @param int    $post_id
4115
 * @param int    $id_attach    attachment file Id
4116
 */
4117
function edit_forum_attachment_file($file_comment, $post_id, $id_attach)
4118
{
4119
    throw new Exception('edit_forum_attachment_file');
4120
    /*
4121
    $_course = api_get_course_info();
4122
    $table_forum_attachment = Database::get_course_table(TABLE_FORUM_ATTACHMENT);
4123
    $course_id = api_get_course_int_id();
4124
4125
    $filesData = [];
4126
4127
    if (!is_array($_FILES['user_upload']['name'])) {
4128
        $filesData[] = $_FILES['user_upload'];
4129
    } else {
4130
        $fileCount = count($_FILES['user_upload']['name']);
4131
        $fileKeys = array_keys($_FILES['user_upload']);
4132
4133
        for ($i = 0; $i < $fileCount; $i++) {
4134
            foreach ($fileKeys as $key) {
4135
                $filesData[$i][$key] = $_FILES['user_upload'][$key][$i];
4136
            }
4137
        }
4138
    }
4139
4140
    foreach ($filesData as $attachment) {
4141
        if (empty($attachment['name'])) {
4142
            continue;
4143
        }
4144
4145
        $upload_ok = process_uploaded_file($attachment);
4146
4147
        if (!$upload_ok) {
4148
            continue;
4149
        }
4150
4151
        $course_dir = $_course['path'].'/upload/forum';
4152
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
4153
        $updir = $sys_course_path.$course_dir;
4154
4155
        // Try to add an extension to the file if it hasn't one.
4156
        $new_file_name = add_ext_on_mime(stripslashes($attachment['name']), $attachment['type']);
4157
        // User's file name
4158
        $file_name = $attachment['name'];
4159
4160
        if (!filter_extension($new_file_name)) {
4161
            Display::addFlash(
4162
                Display::return_message(
4163
                    get_lang('File upload failed: this file extension or file type is prohibited'),
4164
                    'error'
4165
                )
4166
            );
4167
        } else {
4168
            $new_file_name = uniqid('');
4169
            $new_path = $updir.'/'.$new_file_name;
4170
            $result = @move_uploaded_file($attachment['tmp_name'], $new_path);
4171
            $safe_file_comment = Database::escape_string($file_comment);
4172
            $safe_file_name = Database::escape_string($file_name);
4173
            $safe_new_file_name = Database::escape_string($new_file_name);
4174
            $safe_post_id = (int) $post_id;
4175
            $safe_id_attach = (int) $id_attach;
4176
            // Storing the attachments if any.
4177
            if ($result) {
4178
                $sql = "UPDATE $table_forum_attachment
4179
                        SET
4180
                            filename = '$safe_file_name',
4181
                            comment = '$safe_file_comment',
4182
                            path = '$safe_new_file_name',
4183
                            post_id = '$safe_post_id',
4184
                            size ='".$attachment['size']."'
4185
                        WHERE c_id = $course_id AND id = '$safe_id_attach'";
4186
                Database::query($sql);
4187
                api_item_property_update(
4188
                    $_course,
4189
                    TOOL_FORUM_ATTACH,
4190
                    $safe_id_attach,
4191
                    'ForumAttachmentUpdated',
4192
                    api_get_user_id()
4193
                );
4194
            }
4195
        }
4196
    }*/
4197
}
4198
4199
/**
4200
 * Show a list with all the attachments according to the post's id.
4201
 *
4202
 * @param int $postId
4203
 *
4204
 * @return array with the post info
4205
 *
4206
 * @author Julio Montoya
4207
 *
4208
 * @version avril 2008, dokeos 1.8.5
4209
 */
4210
function get_attachment($postId)
4211
{
4212
    $table = Database::get_course_table(TABLE_FORUM_ATTACHMENT);
4213
    $course_id = api_get_course_int_id();
4214
    $row = [];
4215
    $postId = (int) $postId;
4216
4217
    if (empty($postId)) {
4218
        return [];
4219
    }
4220
4221
    $sql = "SELECT iid, path, filename, comment
4222
            FROM $table
4223
            WHERE c_id = $course_id AND post_id = $postId";
4224
    $result = Database::query($sql);
4225
    if (0 != Database::num_rows($result)) {
4226
        $row = Database::fetch_array($result);
4227
    }
4228
4229
    return $row;
4230
}
4231
4232
/**
4233
 * Delete the all the attachments from the DB and the file according to the post's id or attach id(optional).
4234
 *
4235
 * @param int $postId
4236
 * @param int $attachmentId
4237
 *
4238
 * @return bool
4239
 */
4240
function delete_attachment($postId, $attachmentId)
4241
{
4242
    $repo = Container::getForumPostRepository();
4243
    $em = Database::getManager();
4244
    /** @var CForumPost $post */
4245
    $post = $repo->find($postId);
4246
    if ($post) {
4247
        $repoAttachment = Container::getForumAttachmentRepository();
4248
        $attachment = $repoAttachment->find($attachmentId);
4249
        if ($attachment) {
4250
            $post->removeAttachment($attachment);
4251
            $em->remove($attachment);
4252
        }
4253
        $repo->update($post);
4254
4255
        Display::addFlash(Display::return_message(get_lang('The attached file has been deleted'), 'confirmation'));
4256
    }
4257
4258
    return true;
4259
}
4260
4261
/**
4262
 * This function gets all the forum information of the all the forum of the group.
4263
 *
4264
 * @param array $groupInfo the id of the group we need the fora of (see forum.forum_of_group)
4265
 *
4266
 * @return CForum[]
4267
 *
4268
 * @todo this is basically the same code as the get_forums function. Consider merging the two.
4269
 */
4270
function get_forums_of_group(CGroup $group)
4271
{
4272
    $course = api_get_course_entity();
4273
    $session = api_get_session_entity();
4274
    $repo = Container::getForumRepository();
4275
    $qb = $repo->getResourcesByCourse($course, $session, $group);
4276
4277
    return $qb->getQuery()->getResult();
4278
}
4279
4280
/**
4281
 * This function stores which users have to be notified of which forums or threads.
4282
 *
4283
 * @param string $content    does the user want to be notified about a forum or about a thread
4284
 * @param int    $id         the id of the forum or thread
4285
 * @param bool   $addOnly
4286
 * @param array  $userInfo
4287
 * @param array  $courseInfo
4288
 *
4289
 * @return string language variable
4290
 *
4291
 * @author  Patrick Cool <[email protected]>, Ghent University, Belgium
4292
 * @author  Julio Montoya
4293
 *
4294
 * @since   May 2008 v1.8.5
4295
 */
4296
function set_notification($content, $id, $addOnly = false, $userInfo = [], $courseInfo = [])
4297
{
4298
    $userInfo = empty($userInfo) ? api_get_user_info() : $userInfo;
4299
    $courseInfo = empty($courseInfo) ? api_get_course_info() : $courseInfo;
4300
    $id = (int) $id;
4301
4302
    if (empty($userInfo) || empty($courseInfo) || empty($id) || empty($content)) {
4303
        return false;
4304
    }
4305
4306
    // Database table definition
4307
    $table_notification = Database::get_course_table(TABLE_FORUM_NOTIFICATION);
4308
    $course_id = $courseInfo['real_id'];
4309
4310
    // Which database field do we have to store the id in?
4311
    $field = 'thread_id';
4312
    if ('forum' === $content) {
4313
        $field = 'forum_id';
4314
    }
4315
4316
    $userId = $userInfo['user_id'];
4317
4318
    // First we check if the notification is already set for this.
4319
    $sql = "SELECT * FROM $table_notification
4320
            WHERE
4321
                c_id = $course_id AND
4322
                $field = $id AND
4323
                user_id = $userId ";
4324
    $result = Database::query($sql);
4325
    $total = Database::num_rows($result);
4326
4327
    // If the user did not indicate that (s)he wanted to be notified already
4328
    // then we store the notification request (to prevent double notification requests).
4329
    if ($total <= 0) {
4330
        $notification = new CForumNotification();
4331
        $notification
4332
            ->setCId($course_id)
4333
            ->setUserId($userId)
4334
        ;
4335
4336
        if ('forum' === $content) {
4337
            $notification->setForumId($id);
4338
        } else {
4339
            $notification->setThreadId($id);
4340
        }
4341
4342
        $em = Database::getManager();
4343
        $em->persist($notification);
4344
        $em->flush();
4345
4346
        Session::erase('forum_notification');
4347
        getNotificationsPerUser(0, true);
4348
4349
        return get_lang('You will be notified of new posts by e-mail.');
4350
    } else {
4351
        if (!$addOnly) {
4352
            $sql = "DELETE FROM $table_notification
4353
                    WHERE
4354
                        c_id = $course_id AND
4355
                        $field = $id AND
4356
                        user_id = $userId ";
4357
            Database::query($sql);
4358
            Session::erase('forum_notification');
4359
            getNotificationsPerUser(0, true);
4360
4361
            return get_lang('You will no longer be notified of new posts by email');
4362
        }
4363
    }
4364
}
4365
4366
/**
4367
 * This function retrieves all the email adresses of the users who wanted to be notified
4368
 * about a new post in a certain forum or thread.
4369
 *
4370
 * @param string $content does the user want to be notified about a forum or about a thread
4371
 * @param int $id      the id of the forum or thread
4372
 *
4373
 * @return array returns
4374
 *
4375
 * @author Patrick Cool <[email protected]>, Ghent University, Belgium
4376
 * @author Julio Montoya
4377
 *
4378
 * @version May 2008, dokeos 1.8.5
4379
 *
4380
 * @since May 2008, dokeos 1.8.5
4381
 */
4382
function get_notifications(string $content, int $id): array
4383
{
4384
    // Database table definition
4385
    $table_users = Database::get_main_table(TABLE_MAIN_USER);
4386
    $table_notification = Database::get_course_table(TABLE_FORUM_NOTIFICATION);
4387
    $course_id = api_get_course_int_id();
4388
4389
    // Which database field contains the notification?
4390
    $field = 'thread_id';
4391
    if ('forum' === $content) {
4392
        $field = 'forum_id';
4393
    }
4394
4395
    $sql = "SELECT user.id as user_id, user.firstname, user.lastname, user.email, user.id user
4396
            FROM $table_users user, $table_notification notification
4397
            WHERE
4398
                notification.c_id = $course_id AND user.active = 1 AND
4399
                user.id = notification.user_id AND
4400
                notification.$field = $id ";
4401
4402
    $result = Database::query($sql);
4403
    $return = [];
4404
4405
    while ($row = Database::fetch_array($result)) {
4406
        $return['user'.$row['user_id']] = ['email' => $row['email'], 'user_id' => $row['user_id']];
4407
    }
4408
4409
    return $return;
4410
}
4411
4412
/**
4413
 * Get all the users who need to receive a notification of a new post (those subscribed to
4414
 * the forum or the thread).
4415
 *
4416
 * @param int $post_id the id of the post
4417
 *
4418
 * @return false|null
4419
 *
4420
 * @author Patrick Cool <[email protected]>, Ghent University, Belgium
4421
 *
4422
 * @version May 2008, dokeos 1.8.5
4423
 *
4424
 * @since May 2008, dokeos 1.8.5
4425
 */
4426
function send_notifications(CForum $forum, CForumThread $thread, $post_id = 0)
4427
{
4428
    if (!$forum) {
4429
        return false;
4430
    }
4431
4432
    // Users who subscribed to the forum
4433
    $users_to_be_notified_by_forum = get_notifications('forum', $forum->getIid());
4434
4435
    // User who subscribed to the thread
4436
    $users_to_be_notified_by_thread = [];
4437
    if (!$thread) {
4438
        $users_to_be_notified_by_thread = get_notifications('thread', $thread->getIid());
4439
    }
4440
4441
    $postInfo = null;
4442
    if (!empty($post_id)) {
4443
        $postInfo = Container::getForumPostRepository()->find($post_id);
4444
    }
4445
4446
    // Merging the two
4447
    $users_to_be_notified = array_merge($users_to_be_notified_by_forum, $users_to_be_notified_by_thread);
4448
4449
    $subscribe = (int) api_get_course_setting('subscribe_users_to_forum_notifications');
4450
    if (1 === $subscribe) {
4451
        $sessionId = api_get_session_id();
4452
        if (!empty($sessionId)) {
4453
            $users_to_be_notified = SessionManager::getAllUserIdsInSession($sessionId);
4454
        } else {
4455
            $users_to_be_notified = CourseManager::get_user_list_from_course_code(api_get_course_id());
4456
        }
4457
    }
4458
4459
    if (is_array($users_to_be_notified)) {
4460
        foreach ($users_to_be_notified as $value) {
4461
            $userInfo = api_get_user_info($value['user_id']);
4462
            send_mail($userInfo, $forum, $thread, $postInfo);
4463
        }
4464
    }
4465
}
4466
4467
/**
4468
 * Get all the notification subscriptions of the user
4469
 * = which forums and which threads does the user wants to be informed of when a new
4470
 * post is added to this thread.
4471
 *
4472
 * @param int  $user_id the user_id of a user (default = 0 => the current user)
4473
 * @param bool $force   force get the notification subscriptions (even if the information is already in the session
4474
 *
4475
 * @return array
4476
 *
4477
 * @author Patrick Cool <[email protected]>, Ghent University, Belgium
4478
 *
4479
 * @version May 2008, dokeos 1.8.5
4480
 *
4481
 * @since May 2008, dokeos 1.8.5
4482
 */
4483
function getNotificationsPerUser($user_id = 0, $force = false, $course_id = 0)
4484
{
4485
    // Database table definition
4486
    $table_notification = Database::get_course_table(TABLE_FORUM_NOTIFICATION);
4487
    $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
4488
    if (empty($course_id) || -1 == $course_id) {
4489
        return null;
4490
    }
4491
4492
    $user_id = empty($user_id) ? api_get_user_id() : (int) $user_id;
4493
4494
    if (!isset($_SESSION['forum_notification']) ||
4495
        $_SESSION['forum_notification']['course'] != $course_id ||
4496
        true == $force
4497
    ) {
4498
        $_SESSION['forum_notification']['course'] = $course_id;
4499
        $sql = "SELECT * FROM $table_notification
4500
                WHERE c_id = $course_id AND user_id='".$user_id."'";
4501
4502
        $result = Database::query($sql);
4503
        while ($row = Database::fetch_array($result)) {
4504
            if (null !== $row['forum_id']) {
4505
                $_SESSION['forum_notification']['forum'][] = $row['forum_id'];
4506
            }
4507
            if (null !== $row['thread_id']) {
4508
                $_SESSION['forum_notification']['thread'][] = $row['thread_id'];
4509
            }
4510
        }
4511
    }
4512
}
4513
4514
/**
4515
 * This function counts the number of post inside a thread.
4516
 *
4517
 * @param int $thread_id
4518
 *
4519
 * @return int the number of post inside a thread
4520
 *
4521
 * @author Jhon Hinojosa <[email protected]>,
4522
 *
4523
 * @version octubre 2008, dokeos 1.8
4524
 */
4525
function count_number_of_post_in_thread($thread_id)
4526
{
4527
    $table_posts = Database::get_course_table(TABLE_FORUM_POST);
4528
    $course_id = api_get_course_int_id();
4529
    if (empty($course_id)) {
4530
        return 0;
4531
    }
4532
    $sql = "SELECT count(*) count FROM $table_posts
4533
            WHERE
4534
                c_id = $course_id AND
4535
                thread_id='".(int) $thread_id."' ";
4536
    $result = Database::query($sql);
4537
4538
    $count = 0;
4539
    if (Database::num_rows($result) > 0) {
4540
        $row = Database::fetch_array($result);
4541
        $count = $row['count'];
4542
    }
4543
4544
    return $count;
4545
}
4546
4547
/**
4548
 * This function counts the number of post inside a thread user.
4549
 *
4550
 * @param int $thread_id
4551
 * @param int $user_id
4552
 *
4553
 * @return int the number of post inside a thread user
4554
 */
4555
function count_number_of_post_for_user_thread($thread_id, $user_id)
4556
{
4557
    $table_posts = Database::get_course_table(TABLE_FORUM_POST);
4558
    $sql = "SELECT count(iid) as count
4559
            FROM $table_posts
4560
            WHERE
4561
                  thread_id=".(int) $thread_id.' AND
4562
                  poster_id = '.(int) $user_id.' AND
4563
                  visible = 1 ';
4564
    $result = Database::query($sql);
4565
    $count = 0;
4566
    if (Database::num_rows($result) > 0) {
4567
        $count = Database::fetch_array($result);
4568
        $count = $count['count'];
4569
    }
4570
4571
    return $count;
4572
}
4573
4574
/**
4575
 * This function retrieves information of statistical.
4576
 *
4577
 * @param int $thread_id
4578
 * @param int $user_id
4579
 * @param int $course_id
4580
 *
4581
 * @return array the information of statistical
4582
 *
4583
 * @author Jhon Hinojosa <[email protected]>,
4584
 *
4585
 * @version oct 2008, dokeos 1.8
4586
 */
4587
function get_statistical_information($thread_id, $user_id, $course_id)
4588
{
4589
    $result = [];
4590
    $courseInfo = api_get_course_info_by_id($course_id);
4591
    $result['user_course'] = CourseManager::get_users_count_in_course($courseInfo['code']);
4592
    $result['post'] = count_number_of_post_in_thread($thread_id);
4593
    $result['user_post'] = count_number_of_post_for_user_thread($thread_id, $user_id);
4594
4595
    return $result;
4596
}
4597
4598
/**
4599
 * This function return the posts inside a thread from a given user.
4600
 *
4601
 * @param int $thread_id
4602
 * @param int $user_id
4603
 *
4604
 * @return array posts inside a thread
4605
 *
4606
 * @author  Jhon Hinojosa <[email protected]>,
4607
 *
4608
 * @version oct 2008, dokeos 1.8
4609
 */
4610
function get_thread_user_post(Course $course, $thread_id, $user_id)
4611
{
4612
    $table_posts = Database::get_course_table(TABLE_FORUM_POST);
4613
    $table_users = Database::get_main_table(TABLE_MAIN_USER);
4614
    $thread_id = (int) $thread_id;
4615
    $user_id = (int) $user_id;
4616
    $course_id = $course->getId();
4617
4618
    if (empty($course_id)) {
4619
        $course_id = api_get_course_int_id();
4620
    }
4621
    $sql = "SELECT *, user.id as user_id FROM $table_posts posts
4622
            LEFT JOIN $table_users user
4623
            ON posts.poster_id = user.id
4624
            WHERE
4625
                posts.c_id = $course_id AND
4626
                posts.thread_id='$thread_id' AND
4627
                posts.poster_id='$user_id'
4628
            ORDER BY posts.iid ASC";
4629
4630
    $result = Database::query($sql);
4631
    $post_list = [];
4632
    while ($row = Database::fetch_array($result)) {
4633
        $row['status'] = '1';
4634
        $post_list[] = $row;
4635
        $sql = "SELECT * FROM $table_posts posts
4636
                LEFT JOIN $table_users users
4637
                ON (posts.poster_id=users.id)
4638
                WHERE
4639
                    posts.c_id = $course_id AND
4640
                    posts.thread_id='$thread_id'
4641
                    AND posts.post_parent_id='".$row['iid']."'
4642
                ORDER BY posts.iid ASC";
4643
        $result2 = Database::query($sql);
4644
        while ($row2 = Database::fetch_array($result2)) {
4645
            $row2['status'] = '0';
4646
            $post_list[] = $row2;
4647
        }
4648
    }
4649
4650
    return $post_list;
4651
}
4652
4653
/**
4654
 * This function get the name of an thread by id.
4655
 *
4656
 * @param int $threadId
4657
 *
4658
 * @return string
4659
 *
4660
 * @author Christian Fasanando
4661
 * @author Julio Montoya <[email protected]> Adding security
4662
 */
4663
function get_name_thread_by_id(int $threadId): string
4664
{
4665
    $tForumThread = Database::get_course_table(TABLE_FORUM_THREAD);
4666
    $course_id = api_get_course_int_id();
4667
    $sql = "SELECT title
4668
            FROM $tForumThread
4669
            WHERE iid = $threadId";
4670
    $result = Database::query($sql);
4671
    $row = Database::fetch_array($result);
4672
4673
    return $row[0];
4674
}
4675
4676
/**
4677
 * This function gets all the post written by an user.
4678
 *
4679
 * @param int $user_id
4680
 * @param int $courseId
4681
 *
4682
 * @return string
4683
 */
4684
function get_all_post_from_user(int $user_id, int $courseId): string
4685
{
4686
    $j = 0;
4687
    $forums = get_forums($courseId);
4688
    krsort($forums);
4689
    $forum_results = '';
4690
4691
    foreach ($forums as $forum) {
4692
        $forumId = $forum->getIid();
4693
4694
        /*if (0 == $forum['visibility']) {
4695
            continue;
4696
        }*/
4697
        if ($j <= 4) {
4698
            $threads = get_threads($forumId);
4699
4700
            if ($threads) {
4701
                $i = 0;
4702
                $hand_forums = '';
4703
                $post_counter = 0;
4704
                foreach ($threads as $thread) {
4705
                    /*if (0 == $thread['visibility']) {
4706
                        continue;
4707
                    }*/
4708
                    if ($i <= 4) {
4709
                        $post_list = get_thread_user_post_limit(
4710
                            $courseId,
4711
                            $thread->getIid(),
4712
                            $user_id,
4713
                            1
4714
                        );
4715
                        $post_counter = count($post_list);
4716
                        if (is_array($post_list) && count($post_list) > 0) {
4717
                            $hand_forums .= '<div id="social-thread">';
4718
                            $hand_forums .= Display::getMdiIcon('format-quote-open', 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Thread'));
4719
                            $hand_forums .= '&nbsp;'.Security::remove_XSS($thread->getTitle(), STUDENT);
4720
                            $hand_forums .= '</div>';
4721
4722
                            foreach ($post_list as $posts) {
4723
                                $hand_forums .= '<div id="social-post">';
4724
                                $hand_forums .= '<strong>'.Security::remove_XSS($posts['post_title'], STUDENT).'</strong>';
4725
                                $hand_forums .= '<br / >';
4726
                                $hand_forums .= Security::remove_XSS($posts['post_text'], STUDENT);
4727
                                $hand_forums .= '</div>';
4728
                                $hand_forums .= '<br / >';
4729
                            }
4730
                        }
4731
                    }
4732
                    $i++;
4733
                }
4734
                $forum_results .= '<div id="social-forum">';
4735
                $forum_results .= '<div class="clear"></div><br />';
4736
                $forum_results .= '<div id="social-forum-title">'.
4737
                    Display::getMdiIcon('comment-quote', 'ch-tool-icon', '', ICON_SIZE_SMALL, get_lang('Forum')).'&nbsp;'.Security::remove_XSS($forum->getTitle(), STUDENT).
4738
                    '<div style="float:right;margin-top:-35px">
4739
                        <a href="../forum/viewforum.php?'.api_get_cidreq_params($courseId).'&forum='.$forum->getIid().' " >'.
4740
                    get_lang('See forum').'
4741
                        </a>
4742
                     </div></div>';
4743
                $forum_results .= '<br / >';
4744
                if ($post_counter > 0) {
4745
                    $forum_results .= $hand_forums;
4746
                }
4747
                $forum_results .= '</div>';
4748
            }
4749
            $j++;
4750
        }
4751
    }
4752
4753
    return $forum_results;
4754
}
4755
4756
/**
4757
 * @param int $thread_id
4758
 * @param int $user_id
4759
 * @param int $limit
4760
 *
4761
 * @return array
4762
 */
4763
function get_thread_user_post_limit($courseId, $thread_id, $user_id, $limit = 10)
4764
{
4765
    $table_posts = Database::get_course_table(TABLE_FORUM_POST);
4766
    $table_users = Database::get_main_table(TABLE_MAIN_USER);
4767
4768
    $courseId = (int) $courseId;
4769
    $limit = (int) $limit;
4770
4771
    $sql = "SELECT * FROM $table_posts posts
4772
            LEFT JOIN $table_users users
4773
                ON posts.poster_id=users.id
4774
            WHERE
4775
                posts.c_id = $courseId AND
4776
                posts.thread_id='".Database::escape_string($thread_id)."' AND
4777
                posts.poster_id='".Database::escape_string($user_id)."'
4778
            ORDER BY posts.post_id DESC LIMIT $limit ";
4779
    $result = Database::query($sql);
4780
    $post_list = [];
4781
    while ($row = Database::fetch_array($result)) {
4782
        $row['status'] = '1';
4783
        $post_list[] = $row;
4784
    }
4785
4786
    return $post_list;
4787
}
4788
4789
/**
4790
 * @param string $userId
4791
 * @param array  $courseInfo
4792
 * @param int    $sessionId
4793
 *
4794
 * @return array
4795
 */
4796
function getForumCreatedByUser($userId, $courseInfo, $sessionId)
4797
{
4798
    if (empty($userId) || empty($courseInfo)) {
4799
        return [];
4800
    }
4801
4802
    $courseId = $courseInfo['real_id'];
4803
4804
    $repo = Container::getForumRepository();
4805
4806
    $courseEntity = api_get_course_entity($courseId);
4807
    $sessionEntity = api_get_session_entity($sessionId);
4808
4809
    $qb = $repo->getResourcesByCourse($courseEntity, $sessionEntity);
4810
4811
    $qb->andWhere('node.creator = :creator');
4812
    $qb->setParameter('creator', $userId);
4813
    $items = $qb->getQuery()->getResult();
4814
4815
    $forumList = [];
4816
    if (!empty($items)) {
4817
        /** @var CForum $forum */
4818
        foreach ($items as $forum) {
4819
            $forumList[] = [
4820
                $forum->getTitle(),
4821
                api_get_local_time($forum->getResourceNode()->getCreatedAt()),
4822
                api_get_local_time($forum->getResourceNode()->getUpdatedAt()),
4823
            ];
4824
        }
4825
    }
4826
4827
    return $forumList;
4828
}
4829
4830
/**
4831
 * This function builds an array of all the posts in a given thread
4832
 * where the key of the array is the post_id
4833
 * It also adds an element children to the array which itself is an array
4834
 * that contains all the id's of the first-level children.
4835
 *
4836
 * @return array containing all the information on the posts of a thread
4837
 *
4838
 * @author Patrick Cool <[email protected]>, Ghent University
4839
 */
4840
function calculate_children($rows)
4841
{
4842
    $sorted_rows = [0 => []];
4843
    if (!empty($rows)) {
4844
        foreach ($rows as $row) {
4845
            $rows_with_children[$row['post_id']] = $row;
4846
            $rows_with_children[$row['post_parent_id']]['children'][] = $row['post_id'];
4847
        }
4848
4849
        $rows = $rows_with_children;
4850
        forumRecursiveSort($rows, $sorted_rows);
4851
        unset($sorted_rows[0]);
4852
    }
4853
4854
    return $sorted_rows;
4855
}
4856
4857
/**
4858
 * @param $rows
4859
 * @param $threads
4860
 * @param int $seed
4861
 * @param int $indent
4862
 */
4863
function forumRecursiveSort($rows, &$threads, $seed = 0, $indent = 0)
4864
{
4865
    if ($seed > 0) {
4866
        $threads[$rows[$seed]['post_id']] = $rows[$seed];
4867
        $threads[$rows[$seed]['post_id']]['indent_cnt'] = $indent;
4868
        $indent++;
4869
    }
4870
4871
    if (isset($rows[$seed]['children'])) {
4872
        foreach ($rows[$seed]['children'] as $child) {
4873
            forumRecursiveSort($rows, $threads, $child, $indent);
4874
        }
4875
    }
4876
}
4877
4878
/**
4879
 * Update forum attachment data, used to update comment and post ID.
4880
 *
4881
 * @param array $array    (field => value) to update forum attachment row
4882
 * @param int   $id       ID to find row to update
4883
 * @param int   $courseId course ID to find row to update
4884
 *
4885
 * @return int number of affected rows
4886
 */
4887
function editAttachedFile($array, $id, $courseId = null)
4888
{
4889
    // Init variables
4890
    $setString = '';
4891
    $id = (int) $id;
4892
    $courseId = (int) $courseId;
4893
    if (empty($courseId)) {
4894
        // $courseId can be null, use api method
4895
        $courseId = api_get_course_int_id();
4896
    }
4897
    /*
4898
     * Check if Attachment ID and Course ID are greater than zero
4899
     * and array of field values is not empty
4900
     */
4901
    if ($id > 0 && $courseId > 0 && !empty($array) && is_array($array)) {
4902
        foreach ($array as $key => &$item) {
4903
            $item = Database::escape_string($item);
4904
            $setString .= $key.' = "'.$item.'", ';
4905
        }
4906
        // Delete last comma
4907
        $setString = substr($setString, 0, strlen($setString) - 2);
4908
        $forumAttachmentTable = Database::get_course_table(TABLE_FORUM_ATTACHMENT);
4909
        $sql = "UPDATE $forumAttachmentTable
4910
                SET $setString WHERE c_id = $courseId AND iid = $id";
4911
        $result = Database::query($sql);
4912
        if (false !== $result) {
4913
            $affectedRows = Database::affected_rows($result);
4914
            if ($affectedRows > 0) {
4915
                /*
4916
                 * If exist in $_SESSION variable, then delete them from it
4917
                 * because they would be deprecated
4918
                 */
4919
                if (!empty($_SESSION['forum']['upload_file'][$courseId][$id])) {
4920
                    unset($_SESSION['forum']['upload_file'][$courseId][$id]);
4921
                }
4922
            }
4923
4924
            return $affectedRows;
4925
        }
4926
    }
4927
4928
    return 0;
4929
}
4930
4931
/**
4932
 * Return a table where the attachments will be set.
4933
 *
4934
 * @param int $postId Forum Post ID
4935
 *
4936
 * @return string The Forum Attachments Ajax Table
4937
 */
4938
function getAttachmentsAjaxTable($postId = 0)
4939
{
4940
    $postId = (int) $postId;
4941
    $courseId = api_get_course_int_id();
4942
    $attachIds = getAttachmentIdsByPostId($postId, $courseId);
4943
    $fileDataContent = '';
4944
    // Update comment to show if form did not pass validation
4945
    if (!empty($_REQUEST['file_ids']) && is_array($_REQUEST['file_ids'])) {
4946
        // 'file_ids is the name from forum attachment ajax form
4947
        foreach ($_REQUEST['file_ids'] as $key => $attachId) {
4948
            if (!empty($_SESSION['forum']['upload_file'][$courseId][$attachId]) &&
4949
                is_array($_SESSION['forum']['upload_file'][$courseId][$attachId])
4950
            ) {
4951
                // If exist forum attachment then update into $_SESSION data
4952
                $_SESSION['forum']['upload_file'][$courseId][$attachId]['comment'] = $_POST['file_comments'][$key];
4953
            }
4954
        }
4955
    }
4956
4957
    // Get data to fill into attachment files table
4958
    if (!empty($_SESSION['forum']['upload_file'][$courseId]) &&
4959
        is_array($_SESSION['forum']['upload_file'][$courseId])
4960
    ) {
4961
        $uploadedFiles = $_SESSION['forum']['upload_file'][$courseId];
4962
        foreach ($uploadedFiles as $k => $uploadedFile) {
4963
            if (!empty($uploadedFile) && in_array($uploadedFile['id'], $attachIds)) {
4964
                // Buil html table including an input with attachmentID
4965
                $fileDataContent .= '<tr id="'.$uploadedFile['id'].'" ><td>'.$uploadedFile['name'].'</td><td>'.$uploadedFile['size'].'</td><td>&nbsp;'.$uploadedFile['result'].
4966
                    ' </td><td> <input style="width:90%;" type="text" value="'.$uploadedFile['comment'].'" name="file_comments[]"> </td><td>'.
4967
                    $uploadedFile['delete'].'</td>'.
4968
                    '<input type="hidden" value="'.$uploadedFile['id'].'" name="file_ids[]">'.'</tr>';
4969
            } else {
4970
                /*
4971
                 * If attachment data is empty, then delete it from $_SESSION
4972
                 * because could generate and empty row into html table
4973
                 */
4974
                unset($_SESSION['forum']['upload_file'][$courseId][$k]);
4975
            }
4976
        }
4977
    }
4978
    $style = empty($fileDataContent) ? 'display: none;' : '';
4979
    // Forum attachment Ajax table
4980
    return '
4981
    <div class="control-group " style="'.$style.'">
4982
        <label class="control-label">'.get_lang('Attachments list').'</label>
4983
        <div class="controls">
4984
            <table id="attachmentFileList" class="files data_table span10">
4985
                <tr>
4986
                    <th>'.get_lang('Filename').'</th>
4987
                    <th>'.get_lang('Size').'</th>
4988
                    <th>'.get_lang('Status').'</th>
4989
                    <th>'.get_lang('Comment').'</th>
4990
                    <th>'.get_lang('Delete').'</th>
4991
                </tr>
4992
                '.$fileDataContent.'
4993
            </table>
4994
        </div>
4995
    </div>';
4996
}
4997
4998
/**
4999
 * Return an array of prepared attachment data to build forum attachment table
5000
 * Also, save this array into $_SESSION to do available the attachment data.
5001
 *
5002
 * @param int $forumId
5003
 * @param int $threadId
5004
 * @param int $postId
5005
 * @param int $attachId
5006
 * @param int $courseId
5007
 *
5008
 * @return array
5009
 */
5010
function getAttachedFiles(
5011
    $forumId,
5012
    $threadId,
5013
    $postId = 0,
5014
    $attachId = 0,
5015
    $courseId = 0
5016
) {
5017
    $forumId = (int) $forumId;
5018
    $courseId = (int) $courseId;
5019
    $attachId = (int) $attachId;
5020
    $postId = (int) $postId;
5021
    $threadId = (int) $threadId;
5022
5023
    if (empty($courseId)) {
5024
        // $courseId can be null, use api method
5025
        $courseId = api_get_course_int_id();
5026
    }
5027
    if (empty($forumId)) {
5028
        if (!empty($_REQUEST['forum'])) {
5029
            $forumId = (int) $_REQUEST['forum'];
5030
        } else {
5031
            // if forum ID is empty, cannot generate delete url
5032
5033
            return [];
5034
        }
5035
    }
5036
    // Check if exist at least one of them to filter forum attachment select query
5037
    if (empty($postId) && empty($attachId)) {
5038
        return [];
5039
    }
5040
5041
    if (empty($postId)) {
5042
        $filter = "AND iid = $attachId";
5043
    } elseif (empty($attachId)) {
5044
        $filter = "AND post_id = $postId";
5045
    } else {
5046
        $filter = "AND post_id = $postId AND iid = $attachId";
5047
    }
5048
    $forumAttachmentTable = Database::get_course_table(TABLE_FORUM_ATTACHMENT);
5049
    $sql = "SELECT iid
5050
            FROM $forumAttachmentTable
5051
            WHERE c_id = $courseId $filter";
5052
    $result = Database::query($sql);
5053
    $json = [];
5054
    if (Database::num_rows($result) > 0) {
5055
        $repo = Container::getForumAttachmentRepository();
5056
        while ($row = Database::fetch_assoc($result)) {
5057
            /** @var CForumAttachment $attachment */
5058
            $attachment = $repo->find($row['iid']);
5059
            $downloadUrl = $repo->getResourceFileDownloadUrl($attachment);
5060
5061
            // name contains an URL to download attachment file and its filename
5062
            $json['name'] = Display::url(
5063
                api_htmlentities($attachment->getFilename()),
5064
                $downloadUrl,
5065
                ['target' => '_blank', 'class' => 'attachFilename']
5066
            );
5067
            $json['id'] = $row['iid'];
5068
            $json['comment'] = $attachment->getComment();
5069
            // Format file size
5070
            $json['size'] = format_file_size($attachment->getSize());
5071
            // Check if $row is consistent
5072
            if ($attachment) {
5073
                // Set result as success and bring delete URL
5074
                $json['result'] = Display::getMdiIcon('check-circle', 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Uploaded.'));
5075
                $url = api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&action=delete_attach&forum='.$forumId.'&thread='.$threadId.'&id_attach='.$row['iid'];
5076
                $json['delete'] = Display::url(
5077
                    Display::getMdiIcon(ActionIcon::DELETE, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Delete')),
5078
                    $url,
5079
                    ['class' => 'deleteLink']
5080
                );
5081
            } else {
5082
                // If not, set an exclamation result
5083
                $json['result'] = Display::getMdiIcon('close-circle', 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Error'));
5084
            }
5085
            // Store array data into $_SESSION
5086
            $_SESSION['forum']['upload_file'][$courseId][$json['id']] = $json;
5087
        }
5088
    }
5089
5090
    return $json;
5091
}
5092
5093
/**
5094
 * Clear forum attachment data stored in $_SESSION,
5095
 * If is not defined post, it will clear all forum attachment data from course.
5096
 *
5097
 * @param int $postId   -1 : Clear all attachments from course stored in $_SESSION
5098
 *                      0 : Clear attachments from course, except from temporal post "0"
5099
 *                      but without delete them from file system and database
5100
 *                      Other values : Clear attachments from course except specified post
5101
 *                      and delete them from file system and database
5102
 * @param int $courseId : Course ID, if it is null, will use api_get_course_int_id()
5103
 *
5104
 * @return array
5105
 */
5106
function clearAttachedFiles($postId = 0, $courseId = 0)
5107
{
5108
    // Init variables
5109
    $courseId = (int) $courseId;
5110
    $postId = (int) $postId;
5111
    $array = [];
5112
    if (empty($courseId)) {
5113
        // $courseId can be null, use api method
5114
        $courseId = api_get_course_int_id();
5115
    }
5116
    if (-1 === $postId) {
5117
        // If post ID is -1 then delete course's attachment data from $_SESSION
5118
        if (!empty($_SESSION['forum']['upload_file'][$courseId])) {
5119
            $array = array_keys($_SESSION['forum']['upload_file'][$courseId]);
5120
            unset($_SESSION['forum']['upload_file'][$courseId]);
5121
        }
5122
    } else {
5123
        $attachIds = getAttachmentIdsByPostId($postId, $courseId);
5124
        if (!empty($_SESSION['forum']['upload_file'][$courseId]) &&
5125
            is_array($_SESSION['forum']['upload_file'][$courseId])) {
5126
            foreach ($_SESSION['forum']['upload_file'][$courseId] as $attachId => $attach) {
5127
                if (!in_array($attachId, $attachIds)) {
5128
                    // If attach ID is not into specified post, delete attachment
5129
                    // Save deleted attachment ID
5130
                    $array[] = $attachId;
5131
                    if (0 !== $postId) {
5132
                        // Post 0 is temporal, delete them from file system and DB
5133
                        delete_attachment(0, $attachId);
5134
                    }
5135
                    // Delete attachment data from $_SESSION
5136
                    unset($_SESSION['forum']['upload_file'][$courseId][$attachId]);
5137
                }
5138
            }
5139
        }
5140
    }
5141
5142
    return $array;
5143
}
5144
5145
/**
5146
 * Returns an array of forum attachment ids into a course and forum post.
5147
 *
5148
 * @param int $postId
5149
 * @param int $courseId
5150
 *
5151
 * @return array
5152
 */
5153
function getAttachmentIdsByPostId($postId, $courseId = 0)
5154
{
5155
    $array = [];
5156
    $courseId = (int) $courseId;
5157
    $postId = (int) $postId;
5158
    if (empty($courseId)) {
5159
        // $courseId can be null, use api method
5160
        $courseId = api_get_course_int_id();
5161
    }
5162
    if ($courseId > 0) {
5163
        $forumAttachmentTable = Database::get_course_table(TABLE_FORUM_ATTACHMENT);
5164
        $sql = "SELECT iid FROM $forumAttachmentTable
5165
                WHERE c_id = $courseId AND post_id = $postId";
5166
        $result = Database::query($sql);
5167
        if (false !== $result && Database::num_rows($result) > 0) {
5168
            while ($row = Database::fetch_assoc($result)) {
5169
                $array[] = $row['iid'];
5170
            }
5171
        }
5172
    }
5173
5174
    return $array;
5175
}
5176
5177
function getPostStatus(CForum $forum, array $row, bool $addWrapper = true): string
5178
{
5179
    $statusIcon = '';
5180
    if ($forum->isModerated()) {
5181
        if ($addWrapper) {
5182
            $statusIcon = '<br /><br /><span id="status_post_'.$row['iid'].'">';
5183
        }
5184
        $row['status'] = empty($row['status']) ? 2 : $row['status'];
5185
5186
        $addUrl = false;
5187
        $showStatus = false;
5188
        if (api_is_allowed_to_edit(false, true)) {
5189
            $addUrl = true;
5190
        } else {
5191
            if ($row['user_id'] == api_get_user_id()) {
5192
                $showStatus = true;
5193
            }
5194
        }
5195
5196
        $label = '';
5197
        $icon = '';
5198
        $buttonType = '';
5199
        switch ($row['status']) {
5200
            case CForumPost::STATUS_VALIDATED:
5201
                $label = get_lang('Validated');
5202
                $icon = 'check-circle';
5203
                $buttonType = 'success';
5204
5205
                break;
5206
            case CForumPost::STATUS_WAITING_MODERATION:
5207
                $label = get_lang('Waiting for moderation');
5208
                $icon = 'alert';
5209
                $buttonType = 'warning';
5210
5211
                break;
5212
            case CForumPost::STATUS_REJECTED:
5213
                $label = get_lang('Rejected');
5214
                $icon = 'minus-circle';
5215
                $buttonType = 'danger';
5216
5217
                break;
5218
        }
5219
5220
        if ($addUrl) {
5221
            $statusIcon .= Display::toolbarButton(
5222
                $label.'&nbsp;',
5223
                'javascript:void(0)',
5224
                $icon,
5225
                $buttonType,
5226
                ['class' => 'change_post_status']
5227
            );
5228
        } else {
5229
            if ($showStatus) {
5230
                $statusIcon .= Display::label(
5231
                    Display::getMdiIcon($icon).$label,
5232
                    $buttonType
5233
                );
5234
            }
5235
        }
5236
5237
        if ($addWrapper) {
5238
            $statusIcon .= '</span>';
5239
        }
5240
    }
5241
5242
    return $statusIcon;
5243
}
5244
5245
/**
5246
 * @param CForum $forum
5247
 * @param int    $threadId
5248
 * @param int    $status
5249
 */
5250
function getCountPostsWithStatus($status, $forum, $threadId = null)
5251
{
5252
    $em = Database::getManager();
5253
    $criteria = Criteria::create();
5254
    $criteria
5255
        ->where(Criteria::expr()->eq('status', $status))
5256
        //->andWhere(Criteria::expr()->eq('cId', $forum->getCId()))
5257
        ->andWhere(Criteria::expr()->eq('visible', 1))
5258
    ;
5259
5260
    if (!empty($threadId)) {
5261
        $criteria->andWhere(Criteria::expr()->eq('thread', $threadId));
5262
    }
5263
5264
    $qb = $em->getRepository(CForumPost::class)->createQueryBuilder('p');
5265
    $qb->select('count(p.iid)')
5266
        ->addCriteria($criteria);
5267
5268
    return $qb->getQuery()->getSingleScalarResult();
5269
}
5270
5271
/**
5272
 * @param CForum     $forum
5273
 * @param CForumPost $post
5274
 *
5275
 * @return bool
5276
 */
5277
/**
5278
 * Student editability guard that accepts either an entity or the array shape used in views.
5279
 *
5280
 * @param CForum              $forum
5281
 * @param CForumPost|array    $post
5282
 */
5283
function postIsEditableByStudent(CForum $forum, $post): bool
5284
{
5285
    // Normalize $post to an entity
5286
    /** @var CForumPost|null $entity */
5287
    $entity = null;
5288
5289
    if ($post instanceof CForumPost) {
5290
        $entity = $post;
5291
    } elseif (is_array($post)) {
5292
        // Prefer the embedded entity from getPosts()
5293
        if (isset($post['entity']) && $post['entity'] instanceof CForumPost) {
5294
            $entity = $post['entity'];
5295
        } else {
5296
            // Fallback: fetch by id if present in the array
5297
            $postId = $post['post_id'] ?? $post['iid'] ?? null;
5298
            if ($postId) {
5299
                $em = Database::getManager();
5300
                $entity = $em->getRepository(CForumPost::class)->find($postId);
5301
            }
5302
        }
5303
    }
5304
5305
    // If we still don't have a valid entity, be conservative: deny student edit
5306
    if (!$entity instanceof CForumPost) {
5307
        return false;
5308
    }
5309
5310
    // Admins/teachers can always edit
5311
    if (api_is_platform_admin() || api_is_allowed_to_edit()) {
5312
        return true;
5313
    }
5314
5315
    $isModerated = (bool) $forum->isModerated();
5316
    $userId      = (int) api_get_user_id();
5317
    $ownerId     = $entity->getUser() ? (int) $entity->getUser()->getId() : 0;
5318
    $isOwner     = ($ownerId === $userId);
5319
    $status      = $entity->getStatus();
5320
5321
    // Unmoderated forum: student can edit only own posts
5322
    if (!$isModerated) {
5323
        return $isOwner;
5324
    }
5325
5326
    // editable by owner when status is NULL or WAITING or REJECTED.
5327
    if ($isOwner) {
5328
        if ($status === null) {
5329
            return true;
5330
        }
5331
5332
        return in_array(
5333
            $status,
5334
            [
5335
                CForumPost::STATUS_WAITING_MODERATION,
5336
                CForumPost::STATUS_REJECTED,
5337
            ],
5338
            true
5339
        );
5340
    }
5341
5342
    // Not owner and not a teacher/admin
5343
    return false;
5344
}
5345
5346
5347
/**
5348
 * @return bool
5349
 */
5350
function savePostRevision(CForumPost $post)
5351
{
5352
    $userId = api_get_user_id();
5353
5354
    if ($post->getUser()->getId() != $userId) {
5355
        return false;
5356
    }
5357
5358
    $status = (int) !postNeedsRevision($post);
5359
    $extraFieldValue = new ExtraFieldValue('forum_post');
5360
    $params = [
5361
        'item_id' => $post->getIid(),
5362
        'extra_ask_for_revision' => ['extra_ask_for_revision' => $status],
5363
    ];
5364
    if (empty($status)) {
5365
        unset($params['extra_ask_for_revision']);
5366
    }
5367
    $extraFieldValue->saveFieldValues(
5368
        $params,
5369
        true,
5370
        false,
5371
        ['ask_for_revision']
5372
    );
5373
}
5374
5375
/**
5376
 * @param int $postId
5377
 *
5378
 * @return string
5379
 */
5380
function getPostRevision($postId)
5381
{
5382
    $extraFieldValue = new ExtraFieldValue('forum_post');
5383
    $value = $extraFieldValue->get_values_by_handler_and_field_variable(
5384
        $postId,
5385
        'revision_language'
5386
    );
5387
    $revision = '';
5388
    if ($value && isset($value['value'])) {
5389
        $revision = $value['value'];
5390
    }
5391
5392
    return $revision;
5393
}
5394
5395
function postNeedsRevision(CForumPost $post): bool
5396
{
5397
    $postId = $post->getIid();
5398
    $extraFieldValue = new ExtraFieldValue('forum_post');
5399
    $value = $extraFieldValue->get_values_by_handler_and_field_variable(
5400
        $postId,
5401
        'ask_for_revision'
5402
    );
5403
    $hasRevision = false;
5404
    if ($value && isset($value['value'])) {
5405
        return 1 == $value['value'];
5406
    }
5407
5408
    return $hasRevision;
5409
}
5410
5411
/**
5412
 * Generates an HTML button to ask for a review
5413
 */
5414
function getAskRevisionButton(CForumPost $post, CForumThread $threadInfo): string
5415
{
5416
    if ('false' === api_get_setting('forum.allow_forum_post_revisions')) {
5417
        return '';
5418
    }
5419
5420
    $postId = $post->getIid();
5421
5422
    $status = 'btn--plain';
5423
    if (postNeedsRevision($post)) {
5424
        $status = 'btn--success';
5425
    }
5426
5427
    return Display::url(
5428
        get_lang('Ask for a revision'),
5429
        api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.
5430
        api_get_cidreq().'&action=ask_revision&post_id='.$postId.'&forum='.$threadInfo->getForum()->getIid().'&thread='.$threadInfo->getIid(),
5431
        ['class' => "btn $status", 'title' => get_lang('Ask for a revision')]
5432
    );
5433
}
5434
5435
/**
5436
 * Generates an HTML button to give a review
5437
 */
5438
function getGiveRevisionButton(int $postId, CForumThread $threadInfo): string
5439
{
5440
    return Display::toolbarButton(
5441
        get_lang('Give revision'),
5442
        api_get_path(WEB_CODE_PATH).'forum/reply.php?'.api_get_cidreq().'&'.http_build_query(
5443
            [
5444
                'forum' => $threadInfo->getForum()->getIid(),
5445
                'thread' => $threadInfo->getIid(),
5446
                'post' => $postId,
5447
                'action' => 'replymessage',
5448
                'give_revision' => 1,
5449
            ]
5450
        ),
5451
        'reply',
5452
        'primary',
5453
        ['id' => "reply-to-post-{$postId}"]
5454
    );
5455
}
5456
5457
/**
5458
 * Generates an HTML button to report a post as offensive
5459
 */
5460
function getReportButton(int $postId, CForumThread $threadInfo): string
5461
{
5462
    return Display::url(
5463
        Display::getMdiIcon('flag'),
5464
        api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.
5465
        api_get_cidreq().'&action=report&post_id='.$postId.
5466
        '&forum='.$threadInfo->getForum()->getIid().'&thread='.$threadInfo->getIid(),
5467
        ['class' => 'btn btn--danger', 'title' => get_lang('Report')]
5468
    );
5469
}
5470
5471
/**
5472
 * @return bool
5473
 */
5474
function reportAvailable()
5475
{
5476
    $extraFieldValue = new ExtraFieldValue('course');
5477
    $value = $extraFieldValue->get_values_by_handler_and_field_variable(
5478
        api_get_course_int_id(),
5479
        'allow_forum_report_button'
5480
    );
5481
    $allowReport = false;
5482
    if ($value && isset($value['value']) && 1 == $value['value']) {
5483
        $allowReport = true;
5484
    }
5485
5486
    return $allowReport;
5487
}
5488
5489
/**
5490
 * @return array
5491
 */
5492
function getReportRecipients()
5493
{
5494
    $extraFieldValue = new ExtraFieldValue('course');
5495
    $value = $extraFieldValue->get_values_by_handler_and_field_variable(
5496
        api_get_course_int_id(),
5497
        'forum_report_recipients'
5498
    );
5499
    $users = [];
5500
    if ($value && isset($value['value'])) {
5501
        $usersType = explode(';', $value['value']);
5502
5503
        foreach ($usersType as $type) {
5504
            switch ($type) {
5505
                case 'teachers':
5506
                    $teachers = CourseManager::get_teacher_list_from_course_code(api_get_course_id());
5507
                    if (!empty($teachers)) {
5508
                        $users = array_merge($users, array_column($teachers, 'user_id'));
5509
                    }
5510
5511
                break;
5512
                case 'admins':
5513
                    $admins = UserManager::get_all_administrators();
5514
                    if (!empty($admins)) {
5515
                        $users = array_merge($users, array_column($admins, 'user_id'));
5516
                    }
5517
5518
                    break;
5519
                case 'community_managers':
5520
                    $managers = api_get_setting('forum.community_managers_user_list', true);
5521
                    if (!empty($managers) && isset($managers['users'])) {
5522
                        $users = array_merge($users, $managers['users']);
5523
                    }
5524
5525
                    break;
5526
            }
5527
        }
5528
5529
        $users = array_unique(array_filter($users));
5530
    }
5531
5532
    return $users;
5533
}
5534
5535
/**
5536
 * Sends an e-mail to all users from getReportRecipients() (users with extra field 'forum_report_recipients')
5537
 */
5538
function reportPost(CForumPost $post, CForum $forumInfo, CForumThread $threadInfo): bool
5539
{
5540
    if (!reportAvailable()) {
5541
        return false;
5542
    }
5543
5544
    if (empty($forumInfo) || empty($threadInfo)) {
5545
        return false;
5546
    }
5547
5548
    $postId = $post->getIid();
5549
5550
    $currentUser = api_get_user_info();
5551
    $users = getReportRecipients();
5552
    if (!empty($users)) {
5553
        $url = api_get_path(WEB_CODE_PATH).
5554
            'forum/viewthread.php?forum='.$forumInfo->getIid().'&thread='.$threadInfo->getIid().'&'.api_get_cidreq(true, true, false).'&post_id='.$postId.'#post_id_'.$postId;
5555
        $postLink = Display::url(
5556
            $post->getTitle(),
5557
            $url
5558
        );
5559
        $subject = get_lang('Post reported');
5560
        $content = sprintf(
5561
            get_lang('User %s has reported the message %s in the forum %s'),
5562
            $currentUser['complete_name'],
5563
            $postLink,
5564
            $forumInfo->getTitle()
5565
        );
5566
        foreach ($users as $userId) {
5567
            MessageManager::send_message_simple($userId, $subject, $content);
5568
        }
5569
    }
5570
5571
    return true;
5572
}
5573
5574
function getVisibleForums($courseId, $sessionId): array
5575
{
5576
    $forums = get_forums($courseId, $sessionId);
5577
    $visibleForums = [];
5578
5579
    foreach ($forums as $forum) {
5580
        $forumSession = $forum->getFirstResourceLink()->getSession();
5581
        if ($sessionId > 0) {
5582
            if (null === $forumSession) {
5583
                $threads = get_threads($forum->getIid(), $courseId, $sessionId, true);
5584
                if (!empty($threads)) {
5585
                    $visibleForums[] = $forum;
5586
                }
5587
            } else {
5588
                $visibleForums[] = $forum;
5589
            }
5590
        } else {
5591
            $visibleForums[] = $forum;
5592
        }
5593
    }
5594
5595
    return $visibleForums;
5596
}
5597
5598
function getVisibleForumsInCategory($categoryId, $courseId, $sessionId): array
5599
{
5600
    $forumsInCategory = get_forums_in_category($categoryId, $courseId, $sessionId);
5601
    $visibleForums = [];
5602
5603
    foreach ($forumsInCategory as $forum) {
5604
        $forumSession = $forum->getFirstResourceLink()->getSession();
5605
        if ($sessionId > 0) {
5606
            if (null === $forumSession) {
5607
                $threads = get_threads($forum->getIid(), $courseId, $sessionId, true);
5608
                if (!empty($threads)) {
5609
                    $visibleForums[] = $forum;
5610
                }
5611
            } else {
5612
                $visibleForums[] = $forum;
5613
            }
5614
        } else {
5615
            $visibleForums[] = $forum;
5616
        }
5617
    }
5618
5619
    return $visibleForums;
5620
}
5621