Passed
Push — master ( baa39b...8f650b )
by
unknown
17:03 queued 07:19
created

get_thread_users_details()   B

Complexity

Conditions 5
Paths 10

Size

Total Lines 79
Code Lines 51

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 51
nc 10
nop 1
dl 0
loc 79
rs 8.7579
c 0
b 0
f 0

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

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