store_forum()   F
last analyzed

Complexity

Conditions 20
Paths 3600

Size

Total Lines 172
Code Lines 94

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 20
eloc 94
c 0
b 0
f 0
nc 3600
nop 3
dl 0
loc 172
rs 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\AbstractResource;
6
use Chamilo\CoreBundle\Entity\Course;
7
use Chamilo\CoreBundle\Entity\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 post 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 ((api_is_course_admin() || api_is_session_general_coach() || api_is_course_tutor())) {
2409
        $form->addElement('advanced_settings', 'advanced_params', get_lang('Advanced settings'));
2410
        $form->addElement('html', '<div id="advanced_params_options" style="display:none">');
2411
2412
        // Thread qualify
2413
        GradebookUtils::load_gradebook_select_in_tool($form);
2414
        $form->addCheckBox(
2415
            'thread_qualify_gradebook',
2416
            '',
2417
            get_lang('Grade this thread'),
2418
            ['onclick' => 'javascript:if(this.checked==true){document.getElementById(\'options_field\').style.display = \'block\';}else{document.getElementById(\'options_field\').style.display = \'none\';}']
2419
        );
2420
2421
        $form->addElement('html', '<div id="options_field" style="display:none">');
2422
        $form->addElement('text', 'numeric_calification', get_lang('Maximum score'));
2423
        $form->applyFilter('numeric_calification', 'html_filter');
2424
        $form->addElement('text', 'calification_notebook_title', get_lang('Column header in Competences Report'));
2425
        $form->applyFilter('calification_notebook_title', 'html_filter');
2426
2427
        $form->addElement(
2428
            'text',
2429
            'weight_calification',
2430
            get_lang('Weight in Report'),
2431
            ['value' => '0.00', 'onfocus' => 'javascript: this.select();']
2432
        );
2433
        $form->applyFilter('weight_calification', 'html_filter');
2434
2435
        $group = [];
2436
        $group[] = $form->createElement('radio', 'thread_peer_qualify', null, get_lang('Yes'), 1);
2437
        $group[] = $form->createElement('radio', 'thread_peer_qualify', null, get_lang('No'), 0);
2438
        $form->addGroup(
2439
            $group,
2440
            '',
2441
            [
2442
                get_lang('Thread scored by peers'),
2443
                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.'),
2444
            ]
2445
        );
2446
        $form->addElement('html', '</div>');
2447
        $form->addElement('html', '</div>');
2448
    }
2449
2450
    SkillModel::addSkillsToForm($form, ITEM_TYPE_FORUM_THREAD, 0);
2451
    $form->addElement('checkbox', 'thread_sticky', '', get_lang('This is a sticky message (appears always on top and has a special sticky icon)'));
2452
2453
    $form->addFile('user_upload', get_lang('Attachment'));
2454
2455
    if ($giveRevision) {
2456
        $hide = ('true' === api_get_setting('forum.hide_forum_post_revision_language'));
2457
        $form->addHidden('give_revision', 1);
2458
        if (false === $hide) {
2459
            $extraField = new ExtraField('forum_post');
2460
            $extraField->addElements(
2461
                $form,
2462
                null,
2463
                [], //exclude
2464
                false, // filter
2465
                false, // tag as select
2466
                ['revision_language'], //show only fields
2467
                [], // order fields
2468
                [] // extra data
2469
            );
2470
        } else {
2471
            $form->addHidden('extra_revision_language', 1);
2472
        }
2473
    }
2474
    $form->addButtonCreate(get_lang('Create thread'), 'SubmitPost');
2475
2476
    $defaults['thread_peer_qualify'] = 0;
2477
    if (!empty($form_values)) {
2478
        $defaults['post_title'] = prepare4display($form_values['post_title']);
2479
        $defaults['post_text'] = prepare4display($form_values['post_text']);
2480
        $defaults['post_notification'] = (int) $form_values['post_notification'];
2481
        $defaults['thread_sticky'] = (int) $form_values['thread_sticky'];
2482
        $defaults['thread_peer_qualify'] = (int) $form_values['thread_peer_qualify'];
2483
    }
2484
2485
    $form->setDefaults(isset($defaults) ? $defaults : []);
2486
2487
    // The course admin can make a thread sticky (=appears with special icon and always on top).
2488
    $form->addRule('post_title', get_lang('Required field'), 'required');
2489
    if (1 == $forum->getAllowAnonymous() && !isset($_user['user_id'])) {
2490
        $form->addRule(
2491
            'poster_name',
2492
            get_lang('Required field'),
2493
            'required'
2494
        );
2495
    }
2496
2497
    // Validation or display
2498
    if ($form->validate()) {
2499
        $check = Security::check_token('post');
2500
        if ($check) {
2501
            $values = $form->getSubmitValues();
2502
            if (isset($values['thread_qualify_gradebook']) &&
2503
                '1' == $values['thread_qualify_gradebook'] &&
2504
                empty($values['weight_calification'])
2505
            ) {
2506
                Display::addFlash(
2507
                    Display::return_message(
2508
                        get_lang('You must assign a score to this activity').'&nbsp;<a href="javascript:window.history.go(-1);">'.get_lang('Back').'</a>',
2509
                        'error',
2510
                        false
2511
                    )
2512
                );
2513
2514
                return false;
2515
            }
2516
2517
            $newThread = saveThread($forum, $values);
2518
            if ($newThread) {
2519
                SkillModel::saveSkills($form, ITEM_TYPE_FORUM_THREAD, $newThread->getIid());
2520
                $post = $newThread->getThreadLastPost();
2521
2522
                if ($post) {
2523
                    $postId = $post->getIid();
2524
2525
                    if (isset($values['give_revision']) && 1 == $values['give_revision']) {
2526
                        $extraFieldValues = new ExtraFieldValue('forum_post');
2527
                        $revisionLanguage = isset($values['extra_revision_language']) ? $values['extra_revision_language'] : '';
2528
2529
                        $params = [
2530
                            'item_id' => $postId,
2531
                            'extra_revision_language' => $revisionLanguage,
2532
                        ];
2533
2534
                        $extraFieldValues->saveFieldValues(
2535
                            $params,
2536
                            false,
2537
                            false,
2538
                            ['revision_language']
2539
                        );
2540
                    }
2541
                    $extraFieldValues = new ExtraFieldValue('forum_post');
2542
                    $params = [
2543
                        'item_id' => $postId,
2544
                        'extra_ask_for_revision' => $values['extra_ask_for_revision'] ?? '',
2545
                    ];
2546
                    $extraFieldValues->saveFieldValues(
2547
                        $params,
2548
                        false,
2549
                        false,
2550
                        ['ask_for_revision']
2551
                    );
2552
                }
2553
            }
2554
2555
            $url = api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&'.http_build_query(
2556
                [
2557
                    'forum' => $forumId,
2558
                    'thread' => $newThread->getIid(),
2559
                ]
2560
            );
2561
2562
            Security::clear_token();
2563
            header('Location: '.$url);
2564
            exit;
2565
        }
2566
    } else {
2567
        $token = Security::get_token();
2568
        $form->addElement('hidden', 'sec_token');
2569
        $form->setConstants(['sec_token' => $token]);
2570
2571
        // Delete from $_SESSION forum attachment from other posts
2572
        // and keep only attachments for new post
2573
        //clearAttachedFiles(FORUM_NEW_POST);
2574
        // Get forum attachment ajax table to add it to form
2575
        $attachmentAjaxTable = getAttachmentsAjaxTable(0, $forum->getIid());
2576
        $ajaxHtml = $attachmentAjaxTable;
2577
        $form->addElement('html', $ajaxHtml);
2578
2579
        return $form;
2580
    }
2581
}
2582
2583
/**
2584
 * @param CForumThread $threadInfo
2585
 * @param int          $user_id
2586
 * @param int          $thread_id
2587
 * @param int          $thread_qualify
2588
 * @param int          $qualify_time
2589
 * @param int          $session_id
2590
 *
2591
 * @return string
2592
 *
2593
 * @author Isaac Flores <[email protected]>, U.N.A.S University
2594
 *
2595
 * @version October 2008, dokeos  1.8.6
2596
 */
2597
function saveThreadScore(
2598
    CForumThread $threadEntity,
2599
    $user_id,
2600
    $thread_id,
2601
    $thread_qualify,
2602
    $qualify_time,
2603
    $session_id
2604
) {
2605
    $table_threads_qualify = Database::get_course_table(TABLE_FORUM_THREAD_QUALIFY);
2606
2607
    $course_id = api_get_course_int_id();
2608
    $session_id = (int) $session_id;
2609
    $thread_id = (int) $thread_id;
2610
    $user_id = (int) $user_id;
2611
    $thread_qualify = (float) $thread_qualify;
2612
    $currentUserId = api_get_user_id();
2613
    $qualify_time = Database::escape_string($qualify_time);
2614
2615
    $max = $threadEntity->getThreadQualifyMax();
2616
    if ($thread_qualify <= $max) {
2617
        if ($threadEntity->isThreadPeerQualify()) {
2618
            $sql = "SELECT COUNT(*) FROM $table_threads_qualify
2619
                    WHERE
2620
                        user_id = $user_id AND
2621
                        qualify_user_id = $currentUserId AND
2622
                        thread_id = ".$thread_id;
2623
        } else {
2624
            $sql = "SELECT COUNT(*) FROM $table_threads_qualify
2625
                    WHERE
2626
                        user_id = $user_id AND
2627
                        thread_id = ".$thread_id;
2628
        }
2629
2630
        $result = Database::query($sql);
2631
        $row = Database::fetch_array($result);
2632
2633
        if (0 == $row[0]) {
2634
            $sql = "INSERT INTO $table_threads_qualify (c_id, user_id, thread_id,qualify,qualify_user_id,qualify_time)
2635
                    VALUES (".$course_id.", '".$user_id."','".$thread_id."',".$thread_qualify.", '".$currentUserId."','".$qualify_time."')";
2636
            Database::query($sql);
2637
2638
            return 'insert';
2639
        } else {
2640
            saveThreadScoreHistory(
2641
                '1',
2642
                $course_id,
2643
                $user_id,
2644
                $thread_id
2645
            );
2646
2647
            // Update
2648
            $sql = "UPDATE $table_threads_qualify
2649
                    SET
2650
                        qualify = '".$thread_qualify."',
2651
                        qualify_time = '".$qualify_time."'
2652
                    WHERE
2653
                        user_id=".$user_id.' AND
2654
                        thread_id='.$thread_id." AND
2655
                        qualify_user_id = $currentUserId
2656
                    ";
2657
            Database::query($sql);
2658
2659
            return 'update';
2660
        }
2661
    }
2662
2663
    return '';
2664
}
2665
2666
/**
2667
 * This function shows qualify.
2668
 *
2669
 * @param string $option    contains the information of option to run
2670
 * @param int    $user_id   contains the information the current user id
2671
 * @param int    $thread_id contains the information the current thread id
2672
 *
2673
 * @return int qualify
2674
 *             <code> $option=1 obtained the qualification of the current thread</code>
2675
 *
2676
 * @author Isaac Flores <[email protected]>, U.N.A.S University
2677
 *
2678
 * @version October 2008, dokeos  1.8.6
2679
 */
2680
function showQualify($option, $user_id, $thread_id)
2681
{
2682
    $table_threads_qualify = Database::get_course_table(TABLE_FORUM_THREAD_QUALIFY);
2683
    $table_threads = Database::get_course_table(TABLE_FORUM_THREAD);
2684
2685
    $course_id = api_get_course_int_id();
2686
    $user_id = (int) $user_id;
2687
    $thread_id = (int) $thread_id;
2688
2689
    if (empty($user_id) || empty($thread_id)) {
2690
        return 0;
2691
    }
2692
2693
    $sql = '';
2694
    switch ($option) {
2695
        case 1:
2696
            $sql = "SELECT qualify FROM $table_threads_qualify
2697
                    WHERE
2698
                        c_id = $course_id AND
2699
                        user_id=".$user_id.' AND
2700
                        thread_id='.$thread_id;
2701
2702
            break;
2703
        case 2:
2704
            $sql = "SELECT thread_qualify_max FROM $table_threads
2705
                    WHERE iid=".$thread_id;
2706
2707
            break;
2708
    }
2709
2710
    if (!empty($sql)) {
2711
        $rs = Database::query($sql);
2712
        $row = Database::fetch_array($rs);
2713
        if ($row) {
2714
            return $row[0];
2715
        }
2716
    }
2717
2718
    return 0;
2719
}
2720
2721
/**
2722
 * This function gets qualify historical.
2723
 *
2724
 * @param int  $user_id   contains the information the current user id
2725
 * @param int  $thread_id contains the information the current thread id
2726
 * @param bool $opt       contains the information of option to run
2727
 *
2728
 * @return array
2729
 *
2730
 * @author Christian Fasanando <[email protected]>,
2731
 * @author Isaac Flores <[email protected]>,
2732
 *
2733
 * @version October 2008, dokeos  1.8.6
2734
 */
2735
function getThreadScoreHistory($user_id, $thread_id, $opt)
2736
{
2737
    $user_id = (int) $user_id;
2738
    $thread_id = (int) $thread_id;
2739
2740
    $table_threads_qualify_log = Database::get_course_table(TABLE_FORUM_THREAD_QUALIFY_LOG);
2741
    $course_id = api_get_course_int_id();
2742
2743
    if ('false' == $opt) {
2744
        $sql = "SELECT * FROM $table_threads_qualify_log
2745
                WHERE
2746
                    c_id = $course_id AND
2747
                    thread_id='".$thread_id."' AND
2748
                    user_id='".$user_id."'
2749
                ORDER BY qualify_time";
2750
    } else {
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 DESC";
2757
    }
2758
    $rs = Database::query($sql);
2759
    $log = [];
2760
    while ($row = Database::fetch_assoc($rs)) {
2761
        $log[] = $row;
2762
    }
2763
2764
    return $log;
2765
}
2766
2767
/**
2768
 * This function stores qualify historical.
2769
 *
2770
 * @param bool contains the information of option to run
2771
 * @param string contains the information the current course id
2772
 * @param int contains the information the current forum id
2773
 * @param int contains the information the current user id
2774
 * @param int contains the information the current thread id
2775
 * @param int contains the information the current qualify
2776
 * @param string $option
2777
 * @param int    $course_id
2778
 * @param int    $user_id
2779
 * @param int    $thread_id
2780
 *
2781
 * @author Isaac Flores <[email protected]>, U.N.A.S University
2782
 *
2783
 * @version October 2008, dokeos  1.8.6
2784
 */
2785
function saveThreadScoreHistory(
2786
    $option,
2787
    $course_id,
2788
    $user_id,
2789
    $thread_id
2790
) {
2791
    $table_threads_qualify = Database::get_course_table(TABLE_FORUM_THREAD_QUALIFY);
2792
    $table_threads_qualify_log = Database::get_course_table(TABLE_FORUM_THREAD_QUALIFY_LOG);
2793
2794
    $thread_id = (int) $thread_id;
2795
    $course_id = (int) $course_id;
2796
    $user_id = (int) $user_id;
2797
    $qualify_user_id = api_get_user_id();
2798
2799
    if (1 == $option) {
2800
        // Extract information of thread_qualify.
2801
        $sql = "SELECT qualify, qualify_time
2802
                FROM $table_threads_qualify
2803
                WHERE
2804
                    c_id = $course_id AND
2805
                    user_id = ".$user_id.' AND
2806
                    thread_id = '.$thread_id." AND
2807
                    qualify_user_id = $qualify_user_id
2808
                ";
2809
        $rs = Database::query($sql);
2810
        $row = Database::fetch_array($rs);
2811
2812
        // Insert thread_historical.
2813
        $sql = "INSERT INTO $table_threads_qualify_log (c_id, user_id, thread_id, qualify, qualify_user_id,qualify_time)
2814
                VALUES(".$course_id.", '".$user_id."','".$thread_id."',".(float) $row[0].", '".$qualify_user_id."')";
2815
        Database::query($sql);
2816
    }
2817
}
2818
2819
/**
2820
 * This function shows current thread qualify .
2821
 *
2822
 * @param int $threadId
2823
 * @param int $sessionId
2824
 * @param int $userId
2825
 *
2826
 * @return array or null if is empty
2827
 *
2828
 * @author Isaac Flores <[email protected]>, U.N.A.S University
2829
 *
2830
 * @version December 2008, dokeos  1.8.6
2831
 */
2832
function current_qualify_of_thread($threadId, $sessionId, $userId)
2833
{
2834
    $table_threads_qualify = Database::get_course_table(TABLE_FORUM_THREAD_QUALIFY);
2835
2836
    $course_id = api_get_course_int_id();
2837
    $currentUserId = api_get_user_id();
2838
    $sessionId = (int) $sessionId;
2839
    $threadId = (int) $threadId;
2840
2841
    $sql = "SELECT qualify FROM $table_threads_qualify
2842
            WHERE
2843
                c_id = $course_id AND
2844
                thread_id = $threadId AND
2845
                qualify_user_id = $currentUserId AND
2846
                user_id = $userId
2847
            ";
2848
    $res = Database::query($sql);
2849
    $row = Database::fetch_assoc($res);
2850
2851
    if ($row) {
2852
        return $row['qualify'];
2853
    }
2854
2855
    return 0;
2856
}
2857
2858
/**
2859
 * This function stores a reply in the forum_post table.
2860
 * It also updates the forum_threads table (thread_replies +1 , thread_last_post, thread_date).
2861
 *
2862
 * @param array $values
2863
 * @param int   $courseId Optional
2864
 * @param int   $userId   Optional
2865
 *
2866
 * @return int post id
2867
 */
2868
function store_reply(CForum $forum, CForumThread $thread, $values, $courseId = 0, $userId = 0)
2869
{
2870
    $courseId = !empty($courseId) ? $courseId : api_get_course_int_id();
2871
    $post_date = api_get_utc_datetime();
2872
    $userId = $userId ?: api_get_user_id();
2873
2874
    if (1 == $forum->getAllowAnonymous()) {
2875
        if (api_is_anonymous() && empty($userId)) {
2876
            $userId = api_get_anonymous_id();
2877
        }
2878
    }
2879
2880
    if (empty($userId)) {
2881
        return false;
2882
    }
2883
2884
    $visible = 1;
2885
    if ('1' == $forum->getApprovalDirectPost() &&
2886
        !api_is_allowed_to_edit(null, true)
2887
    ) {
2888
        $visible = 0;
2889
    }
2890
2891
    $upload_ok = 1;
2892
    $new_post_id = 0;
2893
2894
    if ($upload_ok) {
2895
        $course = api_get_course_entity($courseId);
2896
        $session = api_get_session_entity();
2897
2898
        $repo = Container::getForumPostRepository();
2899
        $post = new CForumPost();
2900
        $text = empty($values['post_text']) ? '' : $values['post_text'];
2901
        $post
2902
            ->setTitle($values['post_title'])
2903
            ->setPostText($text)
2904
            ->setThread($thread)
2905
            ->setForum($forum)
2906
            ->setUser(api_get_user_entity($userId))
2907
            ->setPostNotification(isset($values['post_notification']) ? (bool) $values['post_notification'] : false)
2908
            ->setVisible($visible)
2909
            ->setPostDate(api_get_utc_datetime(null, false, true))
2910
            ->setParent($thread)
2911
            ->addCourseLink($course, $session)
2912
        ;
2913
2914
        if (isset($values['post_parent_id']) && !empty($values['post_parent_id'])) {
2915
            $parent = $repo->find($values['post_parent_id']);
2916
            $post->setPostParent($parent);
2917
        }
2918
        $repo->create($post);
2919
2920
        $new_post_id = $post->getIid();
2921
        if ($new_post_id) {
2922
            $values['new_post_id'] = $new_post_id;
2923
            $message = get_lang('The reply has been added');
2924
2925
            if (!empty($_POST['file_ids']) && is_array($_POST['file_ids'])) {
2926
                foreach ($_POST['file_ids'] as $key => $id) {
2927
                    editAttachedFile(
2928
                        [
2929
                            'comment' => $_POST['file_comments'][$key],
2930
                            'post_id' => $new_post_id,
2931
                        ],
2932
                        $id
2933
                    );
2934
                }
2935
            }
2936
2937
            // Update the thread.
2938
            updateThreadInfo($values['thread_id'], $new_post_id, $post_date);
2939
2940
            if ('1' == $forum->getApprovalDirectPost() &&
2941
                !api_is_allowed_to_edit(null, true)
2942
            ) {
2943
                $message .= '<br />'.get_lang('Your message has to be approved before people can view it.').'<br />';
2944
            }
2945
2946
            if ($forum->isModerated() &&
2947
                !api_is_allowed_to_edit(null, true)
2948
            ) {
2949
                $message .= '<br />'.get_lang('Your message has to be approved before people can view it.').'<br />';
2950
            }
2951
2952
            // Setting the notification correctly.
2953
            $my_post_notification = isset($values['post_notification']) ? $values['post_notification'] : null;
2954
            if (1 == $my_post_notification) {
2955
                set_notification('thread', $values['thread_id'], true);
2956
            }
2957
2958
            send_notification_mails(
2959
                $forum,
2960
                $thread,
2961
                $values,
2962
                $course
2963
            );
2964
            add_forum_attachment_file('', $post);
2965
2966
            $logInfo = [
2967
                'tool' => TOOL_FORUM,
2968
                'tool_id' => $values['forum_id'],
2969
                'tool_id_detail' => $values['thread_id'],
2970
                'action' => 'new-post',
2971
                'action_details' => $values['action'],
2972
                'info' => $values['post_title'],
2973
            ];
2974
            Event::registerLog($logInfo);
2975
        }
2976
2977
        Session::erase('formelements');
2978
        Session::erase('origin');
2979
        Session::erase('breadcrumbs');
2980
        Session::erase('addedresource');
2981
        Session::erase('addedresourceid');
2982
2983
        Display::addFlash(Display::return_message($message, 'confirmation', false));
2984
    } else {
2985
        Display::addFlash(
2986
            Display::return_message(
2987
                get_lang('No file was uploaded.').' '.get_lang('Please select a file before pressing the upload button.'),
2988
                'error'
2989
            )
2990
        );
2991
    }
2992
2993
    return $new_post_id;
2994
}
2995
2996
/**
2997
 * This function displays the form that is used to edit a post. This can be a new thread or a reply.
2998
 *
2999
 * @param CForumPost   $post        contains all the information about the current post
3000
 * @param CForumThread $thread      contains all the information about the current thread
3001
 * @param CForum       $forum       contains all info about the current forum (to check if attachments are allowed)
3002
 * @param array        $form_values contains the default values to fill the form
3003
 *
3004
 * @author Patrick Cool <[email protected]>, Ghent University
3005
 *
3006
 * @version february 2006, dokeos 1.8
3007
 */
3008
function show_edit_post_form(
3009
    $post,
3010
    $thread,
3011
    $forum,
3012
    $form_values,
3013
    $id_attach = 0
3014
) {
3015
    // Initialize the object.
3016
    $form = new FormValidator(
3017
        'edit_post',
3018
        'post',
3019
        api_get_self().'?'.api_get_cidreq().'&forum='.(int) ($_GET['forum']).'&thread='.(int) ($_GET['thread']).'&post='.(int) ($_GET['post'])
3020
    );
3021
    $form->addElement('header', get_lang('Edit a post'));
3022
    // Setting the form elements.
3023
    $form->addElement('hidden', 'post_id', $post->getIid());
3024
    $form->addElement('hidden', 'thread_id', $thread->getIid());
3025
    $form->addElement('hidden', 'id_attach', $id_attach);
3026
3027
    if (null === $post->getPostParent()) {
3028
        $form->addElement('hidden', 'is_first_post_of_thread', '1');
3029
    }
3030
3031
    $form->addElement('text', 'post_title', get_lang('Title'));
3032
    $form->applyFilter('post_title', 'html_filter');
3033
    $form->addElement(
3034
        'html_editor',
3035
        'post_text',
3036
        get_lang('Text'),
3037
        null,
3038
        api_is_allowed_to_edit(null, true) ? [
3039
            'ToolbarSet' => 'Forum',
3040
            'Width' => '100%',
3041
            'Height' => '400',
3042
        ] : [
3043
            'ToolbarSet' => 'ForumStudent',
3044
            'Width' => '100%',
3045
            'Height' => '400',
3046
            'UserStatus' => 'student',
3047
        ]
3048
    );
3049
    $form->addRule('post_text', get_lang('Required field'), 'required');
3050
3051
    $extraFields = new ExtraField('forum_post');
3052
    $extraFields->addElements($form, $post->getIid());
3053
3054
    $form->addButtonAdvancedSettings('advanced_params');
3055
    $form->addElement('html', '<div id="advanced_params_options" style="display:none">');
3056
3057
    if ($forum->isModerated() && api_is_allowed_to_edit(null, true)) {
3058
        $group = [];
3059
        $group[] = $form->createElement(
3060
            'radio',
3061
            'status',
3062
            null,
3063
            get_lang('Validated'),
3064
            1
3065
        );
3066
        $group[] = $form->createElement(
3067
            'radio',
3068
            'status',
3069
            null,
3070
            get_lang('Waiting for moderation'),
3071
            2
3072
        );
3073
        $group[] = $form->createElement(
3074
            'radio',
3075
            'status',
3076
            null,
3077
            get_lang('Rejected'),
3078
            3
3079
        );
3080
        $form->addGroup($group, 'status', get_lang('Status'));
3081
    }
3082
3083
    $defaults['status']['status'] = $post->getStatus();
3084
3085
    $form->addElement(
3086
        'checkbox',
3087
        'post_notification',
3088
        '',
3089
        get_lang('Notify me by e-mail when somebody replies')
3090
    );
3091
3092
    if (api_is_allowed_to_edit(null, true) && null === $post->getPostParent()) {
3093
        // The sticky checkbox only appears when it is the first post of a thread.
3094
        $form->addElement(
3095
            'checkbox',
3096
            'thread_sticky',
3097
            '',
3098
            get_lang('This is a sticky message (appears always on top and has a special sticky icon)')
3099
        );
3100
        if (1 == $thread->getThreadSticky()) {
3101
            $defaults['thread_sticky'] = true;
3102
        }
3103
    }
3104
3105
    $form->addElement('html', '</div>');
3106
3107
    $form->addFile('user_upload[]', get_lang('Attachment'));
3108
    $form->addButton(
3109
        'add_attachment',
3110
        get_lang('Add attachment'),
3111
        'paperclip',
3112
        'default',
3113
        'default',
3114
        null,
3115
        ['id' => 'reply-add-attachment']
3116
    );
3117
3118
    $form->addButtonUpdate(get_lang('Edit'), 'SubmitPost');
3119
3120
    // Setting the default values for the form elements.
3121
    $defaults['post_title'] = $post->getTitle();
3122
    $defaults['post_text'] = $post->getPostText();
3123
3124
    if (1 == $post->getPostNotification()) {
3125
        $defaults['post_notification'] = true;
3126
    }
3127
3128
    if (!empty($form_values)) {
3129
        $defaults['post_notification'] = Security::remove_XSS($form_values['post_notification']);
3130
        $defaults['thread_sticky'] = Security::remove_XSS($form_values['thread_sticky']);
3131
    }
3132
3133
    $form->setDefaults($defaults);
3134
3135
    // The course admin can make a thread sticky (=appears with special icon and always on top).
3136
    $form->addRule('post_title', get_lang('Required field'), 'required');
3137
3138
    // Validation or display
3139
    if ($form->validate()) {
3140
        $values = $form->exportValues();
3141
        $values['item_id'] = $post->getIid();
3142
        $extraFieldValues = new ExtraFieldValue('forum_post');
3143
        $extraFieldValues->saveFieldValues($values);
3144
3145
        store_edit_post($forum, $values);
3146
    } else {
3147
        // Delete from $_SESSION forum attachment from other posts
3148
        clearAttachedFiles($post->getIid());
3149
        // Get forum attachment ajax table to add it to form
3150
        $fileData = getAttachmentsAjaxTable($post->getIid(), $forum->getIid());
3151
        $form->addElement('html', $fileData);
3152
        $form->display();
3153
    }
3154
}
3155
3156
/**
3157
 * This function stores the edit of a post in the forum_post table.
3158
 *
3159
 * @author Patrick Cool <[email protected]>, Ghent University
3160
 *
3161
 * @version february 2006, dokeos 1.8
3162
 */
3163
function store_edit_post(CForum $forum, $values)
3164
{
3165
    $logInfo = [
3166
        'tool' => TOOL_FORUM,
3167
        'tool_id' => $_GET['forum'],
3168
        'tool_id_detail' => $values['thread_id'],
3169
        'action' => 'edit-post',
3170
        'action_details' => 'post',
3171
        'info' => $values['post_title'],
3172
    ];
3173
    Event::registerLog($logInfo);
3174
3175
    $threadTable = Database::get_course_table(TABLE_FORUM_THREAD);
3176
3177
    //check if this post is the first of the thread
3178
    // First we check if the change affects the thread and if so we commit
3179
    // the changes (sticky and post_title=thread_title are relevant).
3180
    $posts = getPosts($forum, $values['thread_id']);
3181
    $first_post = null;
3182
    if (!empty($posts) && count($posts) > 0 && isset($posts[0])) {
3183
        $first_post = $posts[0];
3184
    }
3185
3186
    if (!empty($first_post) && $first_post['post_id'] == $values['post_id']) {
3187
        // Simple edit
3188
        $params = [
3189
            'title' => $values['post_title'],
3190
            'thread_sticky' => isset($values['thread_sticky']) ? $values['thread_sticky'] : 0,
3191
        ];
3192
        $where = ['iid = ?' => [$values['thread_id']]];
3193
        Database::update($threadTable, $params, $where);
3194
    }
3195
3196
    $status = '';
3197
    $updateStatus = false;
3198
    if ($forum->isModerated()) {
3199
        if (api_is_allowed_to_edit(null, true)) {
3200
            $status = $values['status']['status'];
3201
            $updateStatus = true;
3202
        } else {
3203
            $status = CForumPost::STATUS_WAITING_MODERATION;
3204
            $updateStatus = true;
3205
        }
3206
    }
3207
3208
    $postId = $values['post_id'];
3209
    $repo = Container::getForumPostRepository();
3210
    /** @var CForumPost $post */
3211
    $post = $repo->find($postId);
3212
    if ($post) {
3213
        $post
3214
            ->setTitle($values['post_title'])
3215
            ->setPostText($values['post_text'])
3216
            ->setPostNotification(isset($values['post_notification']))
3217
        ;
3218
3219
        if ($updateStatus) {
3220
            $post->setStatus($status);
3221
        }
3222
        $repo->update($post);
3223
    }
3224
3225
    // Update attached files
3226
    if (!empty($_POST['file_ids']) && is_array($_POST['file_ids'])) {
3227
        foreach ($_POST['file_ids'] as $key => $id) {
3228
            editAttachedFile(
3229
                [
3230
                    'comment' => $_POST['file_comments'][$key],
3231
                    'post_id' => $values['post_id'],
3232
                ],
3233
                $id
3234
            );
3235
        }
3236
    }
3237
3238
    if (!empty($values['remove_attach'])) {
3239
        throw new Exception('remove_attach');
3240
    }
3241
3242
    if (empty($values['id_attach'])) {
3243
        add_forum_attachment_file(
3244
            isset($values['file_comment']) ? $values['file_comment'] : null,
3245
            $post
3246
        );
3247
    }
3248
3249
    $message = get_lang('The post has been modified').'<br />';
3250
    $message .= get_lang('You can now return to the').
3251
        ' <a href="viewforum.php?'.api_get_cidreq().'&forum='.(int) ($_GET['forum']).'&">'.
3252
        get_lang('Forum').'</a><br />';
3253
    $message .= get_lang('You can now return to the').
3254
        ' <a
3255
            href="viewthread.php?'.api_get_cidreq().'&forum='.(int) ($_GET['forum']).'&thread='.$values['thread_id'].'&post='.Security::remove_XSS($_GET['post']).'">'.
3256
        get_lang('Message').'</a>';
3257
3258
    Session::erase('formelements');
3259
    Session::erase('origin');
3260
    Session::erase('breadcrumbs');
3261
    Session::erase('addedresource');
3262
    Session::erase('addedresourceid');
3263
3264
    echo Display::return_message($message, 'confirmation', false);
3265
}
3266
3267
function displayUserLink(User $user)
3268
{
3269
    return '<a href="'.$user->getProfileUrl().'">'.
3270
        Security::remove_XSS(UserManager::formatUserFullName($user)).'</a>';
3271
}
3272
3273
function displayUserImage(User $user)
3274
{
3275
    $url = Container::getIllustrationRepository()->getIllustrationUrl($user, '', ICON_SIZE_BIG);
3276
3277
    return '<div class="thumbnail"><img src="'.$url.'?w=100"/></div>';
3278
}
3279
3280
/**
3281
 * The relies counter gets increased every time somebody replies to the thread.
3282
 *
3283
 * @author Patrick Cool <[email protected]>, Ghent University
3284
 *
3285
 * @version february 2006, dokeos 1.8
3286
 *
3287
 * @param int    $threadId
3288
 * @param string $lastPostId
3289
 * @param string $post_date
3290
 */
3291
function updateThreadInfo($threadId, $lastPostId, $post_date)
3292
{
3293
    $table_threads = Database::get_course_table(TABLE_FORUM_THREAD);
3294
    $threadId = (int) $threadId;
3295
    $lastPostId = (int) $lastPostId;
3296
3297
    $sql = "UPDATE $table_threads SET
3298
            thread_replies = thread_replies+1,
3299
            thread_last_post = '".$lastPostId."',
3300
            thread_date = '".Database::escape_string($post_date)."'
3301
            WHERE
3302
                iid ='".$threadId."'"; // this needs to be cleaned first
3303
    Database::query($sql);
3304
}
3305
3306
function approvePost(CForumPost $post, $action): string
3307
{
3308
    if ('invisible' === $action) {
3309
        $visibility = 0;
3310
        $message = 'PostMadeInvisible';
3311
    }
3312
3313
    if ('visible' === $action) {
3314
        $visibility = 1;
3315
        handle_mail_cue('post', $post->getIid());
3316
        $message = 'PostMadeVisible';
3317
    }
3318
3319
    $post->setVisible($visibility);
3320
    Database::getManager()->persist($post);
3321
    Database::getManager()->flush();
3322
3323
    return $message;
3324
}
3325
3326
/**
3327
 * This function retrieves all the unapproved messages for a given forum
3328
 * This is needed to display the icon that there are unapproved messages in that thread (only the courseadmin can see
3329
 * this).
3330
 *
3331
 * @param int $forum_id forum where we want to know the unapproved messages of
3332
 *
3333
 * @return array returns
3334
 *
3335
 * @author Patrick Cool <[email protected]>, Ghent University
3336
 *
3337
 * @version february 2006, dokeos 1.8
3338
 */
3339
function get_unaproved_messages($forum_id)
3340
{
3341
    $table_posts = Database::get_course_table(TABLE_FORUM_POST);
3342
    $course_id = api_get_course_int_id();
3343
3344
    $return_array = [];
3345
    $sql = "SELECT DISTINCT thread_id FROM $table_posts
3346
            WHERE
3347
                c_id = $course_id AND
3348
                forum_id='".Database::escape_string($forum_id)."' AND
3349
                visible='0' ";
3350
    $result = Database::query($sql);
3351
    while ($row = Database::fetch_array($result)) {
3352
        $return_array[] = $row['thread_id'];
3353
    }
3354
3355
    return $return_array;
3356
}
3357
3358
/**
3359
 * This function sends the notification mails to everybody who stated that they wanted to be informed when a new post
3360
 * was added to a given thread.
3361
 */
3362
function send_notification_mails(CForum $forum, CForumThread $thread, $reply_info, ?Course $course = null)
3363
{
3364
    $courseEntity = api_get_course_entity();
3365
3366
    if (null !== $course) {
3367
        $courseEntity = $course;
3368
    }
3369
3370
    $courseId = $courseEntity->getId();
3371
3372
    $sessionId = api_get_session_id();
3373
    $sessionEntity = api_get_session_entity($sessionId);
3374
3375
    $table = Database::get_course_table(TABLE_FORUM_MAIL_QUEUE);
3376
3377
    // First we need to check if
3378
    // 1. the forum category is visible
3379
    // 2. the forum is visible
3380
    // 3. the thread is visible
3381
    // 4. the reply is visible (=when there is)
3382
3383
    $current_forum_category = null;
3384
    if ($forum->getForumCategory()) {
3385
        $current_forum_category = $forum->getForumCategory();
3386
    }
3387
3388
    $send_mails = false;
3389
    if ($thread->isVisible($courseEntity) &&
3390
        $forum->isVisible($courseEntity) &&
3391
        ($current_forum_category && $forum->getForumCategory()->isVisible($courseEntity)) &&
3392
        '1' != $forum->getApprovalDirectPost()
3393
    ) {
3394
        $send_mails = true;
3395
    }
3396
3397
    // The forum category, the forum, the thread and the reply are visible to the user
3398
    if ($send_mails && !empty($forum)) {
3399
        $postId = isset($reply_info['new_post_id']) ? $reply_info['new_post_id'] : 0;
3400
        send_notifications($forum, $thread, $postId);
3401
    } else {
3402
        $table_notification = Database::get_course_table(TABLE_FORUM_NOTIFICATION);
3403
        if ($forum) {
3404
            $sql = "SELECT * FROM $table_notification
3405
                    WHERE
3406
                        c_id = ".$courseId." AND
3407
                        (
3408
                            forum_id = '".$forum->getIid()."' OR
3409
                            thread_id = '".$thread->getIid()."'
3410
                        ) ";
3411
            $result = Database::query($sql);
3412
            $user_id = api_get_user_id();
3413
            while ($row = Database::fetch_array($result)) {
3414
                $sql = "INSERT INTO $table (c_id, thread_id, post_id, user_id)
3415
                        VALUES (".$courseId.", '".$thread->getIid()."', '".(int) ($reply_info['new_post_id'])."', '$user_id' )";
3416
                Database::query($sql);
3417
            }
3418
        }
3419
    }
3420
}
3421
3422
/**
3423
 * This function is called whenever something is made visible because there might
3424
 * be new posts and the user might have indicated that (s)he wanted to be
3425
 * informed about the new posts by mail.
3426
 *
3427
 * @param string $content Content type (post, thread, forum, forum_category)
3428
 * @param int    $id      Item DB ID of the corresponding content type
3429
 *
3430
 * @return string language variable
3431
 *
3432
 * @author Patrick Cool <[email protected]>, Ghent University
3433
 *
3434
 * @version february 2006, dokeos 1.8
3435
 */
3436
function handle_mail_cue($content, $id)
3437
{
3438
    $table_mailcue = Database::get_course_table(TABLE_FORUM_MAIL_QUEUE);
3439
    $table_forums = Database::get_course_table(TABLE_FORUM);
3440
    $table_threads = Database::get_course_table(TABLE_FORUM_THREAD);
3441
    $table_posts = Database::get_course_table(TABLE_FORUM_POST);
3442
    $table_users = Database::get_main_table(TABLE_MAIN_USER);
3443
3444
    $course_id = api_get_course_int_id();
3445
    $id = (int) $id;
3446
3447
    /* If the post is made visible we only have to send mails to the people
3448
     who indicated that they wanted to be informed for that thread.*/
3449
    if ('post' === $content) {
3450
        // Getting the information about the post (need the threadId).
3451
        /** @var CForumPost $post */
3452
        $post = Container::getForumPostRepository()->find($id);
3453
        $thread_id = $post->getThread()->getIid();
3454
3455
        // Sending the mail to all the users that wanted to be informed for replies on this thread.
3456
        $sql = "SELECT users.firstname, users.lastname, users.id as user_id, users.email
3457
                FROM $table_mailcue mailcue, $table_posts posts, $table_users users
3458
                WHERE
3459
                    mailcue.c_id = $course_id AND
3460
                    posts.thread_id = $thread_id AND
3461
                    posts.post_notification = '1' AND
3462
                    mailcue.thread_id = $thread_id AND
3463
                    users.id = posts.poster_id AND
3464
                    users.active = 1
3465
                GROUP BY users.email";
3466
3467
        $result = Database::query($sql);
3468
        $forum = Container::getForumRepository()->find($post->getForum()->getIid());
3469
3470
        while ($row = Database::fetch_array($result)) {
3471
            send_mail($row, $forum, $post->getThread(), $post);
3472
        }
3473
    } elseif ('thread' === $content) {
3474
        // Sending the mail to all the users that wanted to be informed for replies on this thread.
3475
        $sql = "SELECT users.firstname, users.lastname, users.id as user_id, users.email, posts.forum_id
3476
                FROM $table_mailcue mailcue, $table_posts posts, $table_users users
3477
                WHERE
3478
                    mailcue.c_id = $course_id AND
3479
                    posts.thread_id = $id AND
3480
                    posts.post_notification = '1' AND
3481
                    mailcue.thread_id = $id AND
3482
                    users.id = posts.poster_id AND
3483
                    users.active = 1
3484
                GROUP BY users.email";
3485
        $result = Database::query($sql);
3486
        while ($row = Database::fetch_array($result)) {
3487
            $forum = Container::getForumRepository()->find($row['forum_id']);
3488
            $thread = Container::getForumThreadRepository()->find($id);
3489
            send_mail($row, $forum, $thread);
3490
        }
3491
3492
        // Deleting the relevant entries from the mailcue.
3493
        $sql = "DELETE FROM $table_mailcue
3494
                WHERE c_id = $course_id AND thread_id = $id";
3495
        Database::query($sql);
3496
    } elseif ('forum' === $content) {
3497
        $sql = "SELECT iid FROM $table_threads
3498
                WHERE forum_id = $id";
3499
        $result = Database::query($sql);
3500
        while ($row = Database::fetch_array($result)) {
3501
            handle_mail_cue('thread', $row['iid']);
3502
        }
3503
    } elseif ('forum_category' === $content) {
3504
        $sql = "SELECT iid FROM $table_forums
3505
                WHERE forum_category = $id";
3506
        $result = Database::query($sql);
3507
        while ($row = Database::fetch_array($result)) {
3508
            handle_mail_cue('forum', $row['iid']);
3509
        }
3510
    } else {
3511
        return get_lang('Error');
3512
    }
3513
}
3514
3515
/**
3516
 * This function sends the mails for the mail notification.
3517
 */
3518
function send_mail($userInfo, CForum $forum, CForumThread $thread, CForumPost $postInfo = null)
3519
{
3520
    if (empty($userInfo) || empty($forum) || empty($thread)) {
3521
        return false;
3522
    }
3523
3524
    $_course = api_get_course_info();
3525
    $user_id = api_get_user_id();
3526
    $forumId = $forum->getIid();
3527
    $threadId = $thread->getIid();
3528
3529
    $thread_link = api_get_path(WEB_CODE_PATH).
3530
        'forum/viewthread.php?'.api_get_cidreq(true, true, false).'&forum='.$forumId.'&thread='.$threadId;
3531
3532
    $email_body = get_lang('Dear').' '.
3533
        api_get_person_name($userInfo['firstname'], $userInfo['lastname'], null, PERSON_NAME_EMAIL_ADDRESS).", <br />\n\r";
3534
    $email_body .= get_lang('New Post in the forum').
3535
        ': '.$forum->getTitle().' - '.$thread->getTitle()." <br />\n";
3536
3537
    $courseId = (int) api_get_setting('forum.global_forums_course_id');
3538
    $subject = get_lang('New Post in the forum').' - '.
3539
        $_course['official_code'].': '.$forum->getTitle().' - '.$thread->getTitle()." <br />\n";
3540
3541
    $courseInfoTitle = get_lang('Course').': '.$_course['name'].' - ['.$_course['official_code']."] - <br />\n";
3542
    if (!empty($courseId) && $_course['real_id'] == $courseId) {
3543
        $subject = get_lang('New Post in the forum').': '.
3544
            $forum->getTitle().' - '.$thread->getTitle()." <br />\n";
3545
        $courseInfoTitle = " <br />\n";
3546
    }
3547
    $email_body .= $courseInfoTitle;
3548
3549
    if (!empty($postInfo)) {
3550
        $text = cut(strip_tags($postInfo->getPostText()), 100);
3551
        if (!empty($text)) {
3552
            $email_body .= get_lang('Message').": <br />\n ";
3553
            $email_body .= $text;
3554
            $email_body .= "<br /><br />\n";
3555
        }
3556
    }
3557
3558
    $email_body .= get_lang('You stated that you wanted to be informed by e-mail whenever somebody replies on the thread')."<br />\n";
3559
3560
    if (!empty($thread_link)) {
3561
        $email_body .= get_lang('The thread can be found here').' : <br /><a href="'.$thread_link.'">'.$thread_link."</a>\n";
3562
    }
3563
3564
    if ($userInfo['user_id'] != $user_id) {
3565
        MessageManager::send_message(
3566
            $userInfo['user_id'],
3567
            $subject,
3568
            $email_body,
3569
            [],
3570
            [],
3571
            null,
3572
            null,
3573
            null,
3574
            null,
3575
            $user_id
3576
        );
3577
    }
3578
}
3579
3580
/**
3581
 * This function displays the form for moving a thread to a different (already existing) forum.
3582
 *
3583
 * @author Patrick Cool <[email protected]>, Ghent University
3584
 *
3585
 * @version february 2006, dokeos 1.8
3586
 */
3587
function move_thread_form()
3588
{
3589
    $form = new FormValidator(
3590
        'movepost',
3591
        'post',
3592
        api_get_self().'?forum='.(int) ($_GET['forum']).'&thread='.(int) ($_GET['thread']).'&action='.Security::remove_XSS($_GET['action']).'&'.api_get_cidreq()
3593
    );
3594
    $form->addHeader(get_lang('Move Thread'));
3595
    // Invisible form: the threadId
3596
    $form->addHidden('thread_id', (int) ($_GET['thread']));
3597
    $forum_categories = get_forum_categories();
3598
3599
    $htmlcontent = '<div class="row">
3600
        <div class="label">
3601
            <span class="form_required">*</span>'.get_lang('Move to').'
3602
        </div>
3603
        <div class="formw">';
3604
    $htmlcontent .= '<select name="forum">';
3605
    foreach ($forum_categories as $category) {
3606
        $htmlcontent .= '<optgroup label="'.$category->getTitle().'">';
3607
        $forums = $category->getForums();
3608
        foreach ($forums as $forum) {
3609
            $htmlcontent .= '<option value="'.$forum->getIid().'">'.$forum->getTitle().'</option>';
3610
        }
3611
        $htmlcontent .= '</optgroup>';
3612
    }
3613
    $htmlcontent .= '</select>';
3614
    $htmlcontent .= '   </div>
3615
                    </div>';
3616
3617
    $form->addElement('html', $htmlcontent);
3618
3619
    // The OK button
3620
    $form->addButtonSave(get_lang('Move Thread'), 'SubmitForum');
3621
3622
    // Validation or display
3623
    if ($form->validate()) {
3624
        $values = $form->exportValues();
3625
        if (isset($_POST['forum'])) {
3626
            store_move_thread($values);
3627
            Display::addFlash(Display::return_message(get_lang('Moved.')));
3628
        }
3629
    } else {
3630
        return $form->returnForm();
3631
    }
3632
}
3633
3634
/**
3635
 * This function displays the form for moving a post message to a different (already existing) or a new thread.
3636
 *
3637
 * @author Patrick Cool <[email protected]>, Ghent University
3638
 *
3639
 * @version february 2006, dokeos 1.8
3640
 */
3641
function move_post_form()
3642
{
3643
    $form = new FormValidator(
3644
        'movepost',
3645
        'post',
3646
        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'])
3647
    );
3648
    // The header for the form
3649
    $form->addElement('header', '', get_lang('Move post'));
3650
3651
    // Invisible form: the post_id
3652
    $form->addElement('hidden', 'post_id', (int) ($_GET['post']));
3653
3654
    // Dropdown list: Threads of this forum
3655
    $threads = get_threads($_GET['forum']);
3656
    $threads_list[0] = get_lang('A new thread');
3657
    foreach ($threads as $thread) {
3658
        $threads_list[$thread->getIid()] = $thread->getTitle();
3659
    }
3660
    $form->addSelect('thread', get_lang('Move to thread'), $threads_list);
3661
    $form->applyFilter('thread', 'html_filter');
3662
3663
    // The OK button
3664
    $form->addButtonSave(get_lang('Move post'), 'submit');
3665
3666
    // Setting the rules
3667
    $form->addRule('thread', get_lang('Required field'), 'required');
3668
3669
    return $form;
3670
}
3671
3672
/**
3673
 * @param array $values
3674
 *
3675
 * @return string HTML language variable
3676
 *
3677
 * @author Patrick Cool <[email protected]>, Ghent University
3678
 *
3679
 * @version february 2006, dokeos 1.8
3680
 */
3681
function store_move_post($values)
3682
{
3683
    $_course = api_get_course_info();
3684
    $course_id = api_get_course_int_id();
3685
3686
    $table_forums = Database::get_course_table(TABLE_FORUM);
3687
    $table_threads = Database::get_course_table(TABLE_FORUM_THREAD);
3688
    $table_posts = Database::get_course_table(TABLE_FORUM_POST);
3689
3690
    $user = api_get_user_entity(api_get_user_id());
3691
    $course = api_get_course_entity($course_id);
3692
    $session = api_get_session_entity();
3693
3694
    if ('0' == $values['thread']) {
3695
        $repoPost = Container::getForumPostRepository();
3696
3697
        /** @var CForumPost $post */
3698
        $post = $repoPost->find($values['post_id']);
3699
        $forumId = $post->getForum()->getIid();
3700
        $threadId = $post->getThread()->getIid();
3701
3702
        $thread = new CForumThread();
3703
        $thread
3704
            ->setTitle($post->getTitle())
3705
            ->setForum($post->getForum())
3706
            ->setUser($post->getUser())
3707
            ->setThreadLastPost($post)
3708
            ->setThreadDate($post->getPostDate())
3709
            ->setParent($post->getForum())
3710
            ->addCourseLink(
3711
                $user,
3712
                $course,
3713
                $session
3714
            );
3715
3716
        $repo = Container::getForumThreadRepository();
3717
        $repo->create($thread);
3718
        $new_thread_id = $thread->getIid();
3719
3720
        // Moving the post to the newly created thread.
3721
        $sql = "UPDATE $table_posts SET thread_id='".$new_thread_id."', post_parent_id = NULL
3722
                WHERE iid ='".(int) ($values['post_id'])."'";
3723
        Database::query($sql);
3724
3725
        // Resetting the parent_id of the thread to 0 for all those who had this moved post as parent.
3726
        $sql = "UPDATE $table_posts SET post_parent_id = NULL
3727
                WHERE post_parent_id='".(int) ($values['post_id'])."'";
3728
        Database::query($sql);
3729
3730
        // Updating updating the number of threads in the forum.
3731
        $sql = "UPDATE $table_forums SET forum_threads=forum_threads+1
3732
                WHERE iid ='".$forumId."'";
3733
        Database::query($sql);
3734
3735
        // Resetting the last post of the old thread and decreasing the number of replies and the thread.
3736
        $sql = "SELECT * FROM $table_posts
3737
                WHERE thread_id='".$threadId."'
3738
                ORDER BY iid DESC";
3739
        $result = Database::query($sql);
3740
        $row = Database::fetch_array($result);
3741
        $sql = "UPDATE $table_threads SET
3742
                    thread_last_post='".$row['iid']."',
3743
                    thread_replies=thread_replies-1
3744
                WHERE
3745
                    iid ='".$threadId."'";
3746
        Database::query($sql);
3747
    } else {
3748
        // Moving to the chosen thread.
3749
        $sql = 'SELECT thread_id FROM '.$table_posts."
3750
                WHERE iid = '".$values['post_id']."' ";
3751
        $result = Database::query($sql);
3752
        $row = Database::fetch_array($result);
3753
3754
        $original_thread_id = $row['thread_id'];
3755
        $sql = 'SELECT thread_last_post FROM '.$table_threads."
3756
                WHERE iid = '".$original_thread_id."' ";
3757
3758
        $result = Database::query($sql);
3759
        $row = Database::fetch_array($result);
3760
        $thread_is_last_post = $row['thread_last_post'];
3761
        // If is this thread, update the thread_last_post with the last one.
3762
3763
        if ($thread_is_last_post == $values['post_id']) {
3764
            $sql = 'SELECT iid as post_id FROM '.$table_posts."
3765
                    WHERE
3766
                        thread_id = '".$original_thread_id."' AND
3767
                        iid <> '".$values['post_id']."'
3768
                    ORDER BY post_date DESC LIMIT 1";
3769
            $result = Database::query($sql);
3770
3771
            $row = Database::fetch_array($result);
3772
            $thread_new_last_post = $row['post_id'];
3773
3774
            $sql = 'UPDATE '.$table_threads."
3775
                    SET thread_last_post = '".$thread_new_last_post."'
3776
                    WHERE iid = '".$original_thread_id."' ";
3777
            Database::query($sql);
3778
        }
3779
3780
        $sql = "UPDATE $table_threads SET thread_replies=thread_replies-1
3781
                WHERE iid ='".$original_thread_id."'";
3782
        Database::query($sql);
3783
3784
        // moving to the chosen thread
3785
        $sql = "UPDATE $table_posts SET thread_id='".(int) ($_POST['thread'])."', post_parent_id = NULL
3786
                WHERE iid ='".(int) ($values['post_id'])."'";
3787
        Database::query($sql);
3788
3789
        // resetting the parent_id of the thread to 0 for all those who had this moved post as parent
3790
        $sql = "UPDATE $table_posts SET post_parent_id = NULL
3791
                WHERE post_parent_id='".(int) ($values['post_id'])."'";
3792
        Database::query($sql);
3793
3794
        $sql = "UPDATE $table_threads SET thread_replies=thread_replies+1
3795
                WHERE iid ='".(int) ($_POST['thread'])."'";
3796
        Database::query($sql);
3797
    }
3798
3799
    return get_lang('Thread moved');
3800
}
3801
3802
/**
3803
 * @param array $values
3804
 *
3805
 * @return string HTML language variable
3806
 *
3807
 * @author Patrick Cool <[email protected]>, Ghent University
3808
 *
3809
 * @version february 2006, dokeos 1.8
3810
 */
3811
function store_move_thread($values)
3812
{
3813
    $table_threads = Database::get_course_table(TABLE_FORUM_THREAD);
3814
    $table_posts = Database::get_course_table(TABLE_FORUM_POST);
3815
3816
    $courseId = api_get_course_int_id();
3817
    $sessionId = api_get_session_id();
3818
3819
    $forumId = (int) ($_POST['forum']);
3820
    $threadId = (int) ($_POST['thread_id']);
3821
    //$forumInfo = get_forums($forumId);
3822
3823
    // Change the thread table: Setting the forum_id to the new forum.
3824
    $sql = "UPDATE $table_threads SET forum_id = $forumId
3825
            WHERE iid = $threadId";
3826
    Database::query($sql);
3827
3828
    // Changing all the posts of the thread: setting the forum_id to the new forum.
3829
    $sql = "UPDATE $table_posts SET forum_id = $forumId
3830
            WHERE thread_id= $threadId";
3831
    Database::query($sql);
3832
3833
    // Fix group id, if forum is moved to a different group
3834
    /*if (!empty($forumInfo['to_group_id'])) {
3835
        $groupId = $forumInfo['to_group_id'];
3836
        $item = api_get_item_property_info(
3837
            $courseId,
3838
            TABLE_FORUM_THREAD,
3839
            $threadId,
3840
            $sessionId,
3841
            $groupId
3842
        );
3843
        $table = Database::get_course_table(TABLE_ITEM_PROPERTY);
3844
        $sessionCondition = api_get_session_condition($sessionId);
3845
3846
        if (!empty($item)) {
3847
            if ($item['to_group_id'] != $groupId) {
3848
                $sql = "UPDATE $table
3849
                    SET to_group_id = $groupId
3850
                    WHERE
3851
                      tool = '".TABLE_FORUM_THREAD."' AND
3852
                      c_id = $courseId AND
3853
                      ref = ".$item['ref']."
3854
                      $sessionCondition
3855
                ";
3856
                Database::query($sql);
3857
            }
3858
        } else {
3859
            $sql = "UPDATE $table
3860
                    SET to_group_id = $groupId
3861
                    WHERE
3862
                      tool = '".TABLE_FORUM_THREAD."' AND
3863
                      c_id = $courseId AND
3864
                      ref = ".$threadId."
3865
                      $sessionCondition
3866
            ";
3867
            Database::query($sql);
3868
        }
3869
    }*/
3870
3871
    return get_lang('Thread moved');
3872
}
3873
3874
/**
3875
 * Prepares a string for displaying by highlighting the search results inside, if any.
3876
 *
3877
 * @param string $input the input string
3878
 *
3879
 * @return string the same string with highlighted hits inside
3880
 *
3881
 * @author Patrick Cool <[email protected]>, Ghent University, February 2006 - the initial version.
3882
 * @author Ivan Tcholakov, March 2011 - adaptation for Chamilo LMS.
3883
 */
3884
function prepare4display($input)
3885
{
3886
    static $highlightcolors = ['yellow', '#33CC33', '#3399CC', '#9999FF', '#33CC33'];
3887
    static $search;
3888
3889
    if (!isset($search)) {
3890
        if (isset($_POST['search_term'])) {
3891
            $search = $_POST['search_term']; // No html at all.
3892
        } elseif (isset($_GET['search'])) {
3893
            $search = $_GET['search'];
3894
        } else {
3895
            $search = '';
3896
        }
3897
    }
3898
3899
    if (!empty($search)) {
3900
        if (strstr($search, '+')) {
3901
            $search_terms = explode('+', $search);
3902
        } else {
3903
            $search_terms[] = trim($search);
3904
        }
3905
        $counter = 0;
3906
        foreach ($search_terms as $key => $search_term) {
3907
            $input = api_preg_replace(
3908
                '/'.preg_quote(trim($search_term), '/').'/i',
3909
                '<span style="background-color: '.$highlightcolors[$counter].'">$0</span>',
3910
                $input
3911
            );
3912
            $counter++;
3913
        }
3914
    }
3915
3916
    // TODO: Security should be implemented outside this function.
3917
    // Change this to COURSEMANAGERLOWSECURITY or COURSEMANAGER to lower filtering and allow more styles
3918
    // (see comments of Security::remove_XSS() method to learn about other levels).
3919
3920
    return Security::remove_XSS($input, STUDENT, true);
3921
}
3922
3923
/**
3924
 * Display the search form for the forum and display the search results.
3925
 *
3926
 * @author Patrick Cool <[email protected]>, Ghent University, Belgium
3927
 *
3928
 * @version march 2008, dokeos 1.8.5
3929
 */
3930
function forum_search()
3931
{
3932
    $form = new FormValidator(
3933
        'forumsearch',
3934
        'post',
3935
        'forumsearch.php?'.api_get_cidreq()
3936
    );
3937
3938
    // Setting the form elements.
3939
    $form->addElement('header', '', get_lang('Search in the Forum'));
3940
    $form->addElement('text', 'search_term', get_lang('Search term'), ['autofocus']);
3941
    $form->applyFilter('search_term', 'html_filter');
3942
    $form->addElement('static', 'search_information', '', get_lang('Search in the Forum'));
3943
    $form->addButtonSearch(get_lang('Search'));
3944
3945
    // Setting the rules.
3946
    $form->addRule('search_term', get_lang('Required field'), 'required');
3947
    $form->addRule('search_term', get_lang('Too short'), 'minlength', 3);
3948
3949
    // Validation or display.
3950
    if ($form->validate()) {
3951
        $values = $form->exportValues();
3952
        $form->setDefaults($values);
3953
        $form->display();
3954
        // Display the search results.
3955
        displayForumSearchResults($values['search_term']);
3956
    } else {
3957
        $form->display();
3958
    }
3959
}
3960
3961
/**
3962
 * Displays the search results for forums, threads, and posts within a course.
3963
 */
3964
function displayForumSearchResults(string $searchTerm): void
3965
{
3966
    $forumRepo = Container::getForumRepository();
3967
    $threadRepo = Container::getForumThreadRepository();
3968
    $postRepo = Container::getForumPostRepository();
3969
3970
    $courseId = api_get_course_int_id();
3971
    $sessionId = api_get_session_id();
3972
    $course = api_get_course_entity($courseId);
3973
    $session = api_get_session_entity($sessionId);
3974
3975
    $searchTerms = explode(' ', $searchTerm);
3976
    $searchTerms = array_filter($searchTerms);
3977
3978
    $forumQb = $forumRepo->getResourcesByCourse($course, $session);
3979
    foreach ($searchTerms as $term) {
3980
        $forumQb->andWhere('resource.title LIKE :term OR resource.forumComment LIKE :term')
3981
            ->setParameter('term', '%' . $term . '%');
3982
    }
3983
    $forums = $forumQb->getQuery()->getResult();
3984
3985
    $threadQb = $threadRepo->getResourcesByCourse($course, $session);
3986
    foreach ($searchTerms as $term) {
3987
        $threadQb->andWhere('resource.title LIKE :term')
3988
            ->setParameter('term', '%' . $term . '%');
3989
    }
3990
    $threads = $threadQb->getQuery()->getResult();
3991
3992
    $postQb = $postRepo->getResourcesByCourse($course, $session);
3993
    foreach ($searchTerms as $term) {
3994
        $postQb->andWhere('resource.title LIKE :term OR resource.postText LIKE :term')
3995
            ->setParameter('term', '%' . $term . '%');
3996
    }
3997
    $posts = $postQb->getQuery()->getResult();
3998
3999
    $search_results = [];
4000
    foreach ($forums as $forum) {
4001
        if ($forum->isVisible($course) && $forum->getForumCategory()->isVisible($course)) {
4002
            $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>';
4003
        }
4004
    }
4005
    foreach ($threads as $thread) {
4006
        if ($thread->isVisible($course)) {
4007
            $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>';
4008
        }
4009
    }
4010
    foreach ($posts as $post) {
4011
        if ($post->isVisible($course)) {
4012
            $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>';
4013
        }
4014
    }
4015
4016
    echo '<div class="text-lg font-semibold mb-4">'.count($search_results).' '.get_lang('Search results').'</div>';
4017
    echo '<ol class="list-decimal pl-5">'.implode($search_results).'</ol>';
4018
}
4019
4020
/**
4021
 * Return the link to the forum search page.
4022
 *
4023
 * @author Patrick Cool <[email protected]>, Ghent University, Belgium
4024
 *
4025
 * @version April 2008, dokeos 1.8.5
4026
 */
4027
function search_link()
4028
{
4029
    $return = '';
4030
    $origin = api_get_origin();
4031
    if ('learnpath' != $origin) {
4032
        $return = '<a href="forumsearch.php?'.api_get_cidreq().'&action=search"> ';
4033
        $return .= Display::getMdiIcon('magnify-plus-outline	', 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('Search')).'</a>';
4034
4035
        if (!empty($_GET['search'])) {
4036
            $return .= ': '.Security::remove_XSS($_GET['search']).' ';
4037
            $url = api_get_self().'?';
4038
            $url_parameter = [];
4039
            foreach ($_GET as $key => $value) {
4040
                if ('search' != $key) {
4041
                    $url_parameter[] = Security::remove_XSS($key).'='.Security::remove_XSS($value);
4042
                }
4043
            }
4044
            $url .= implode('&', $url_parameter);
4045
            $return .= '<a href="'.$url.'">'.Display::getMdiIcon(ActionIcon::DELETE, 'ch-tool-icon', '', ICON_SIZE_MEDIUM,  get_lang('Clean search results')).'</a>';
4046
        }
4047
    }
4048
4049
    return $return;
4050
}
4051
4052
/**
4053
 * This function adds an attachment file into a forum.
4054
 *
4055
 * @param string     $file_comment a comment about file
4056
 * @param CForumPost $post         from forum_post table
4057
 *
4058
 * @return false|null
4059
 */
4060
function add_forum_attachment_file($file_comment, CForumPost $post)
4061
{
4062
    $request = Container::getRequest();
4063
    if (false === $request->files->has('user_upload')) {
4064
        return false;
4065
    }
4066
4067
    $file = $request->files->get('user_upload');
4068
    if (empty($file)) {
4069
        return false;
4070
    }
4071
4072
    $files = [];
4073
    if ($file instanceof UploadedFile) {
4074
        $files[] = $file;
4075
    } else {
4076
        $files = $file;
4077
    }
4078
4079
    /** @var UploadedFile $attachment */
4080
    foreach ($files as $file) {
4081
        $valid = process_uploaded_file($file);
4082
4083
        if (!$valid) {
4084
            continue;
4085
        }
4086
4087
        // Try to add an extension to the file if it hasn't one.
4088
        $new_file_name = add_ext_on_mime(
4089
            stripslashes($file->getPathname()),
4090
            $file->getType()
4091
        );
4092
4093
        // User's file name
4094
        $file_name = $file->getClientOriginalName();
4095
4096
        if (!filter_extension($new_file_name)) {
4097
            Display::addFlash(
4098
                Display::return_message(
4099
                    get_lang('File upload failed: this file extension or file type is prohibited'),
4100
                    'error'
4101
                )
4102
            );
4103
4104
            return;
4105
        }
4106
4107
        $safe_file_comment = Database::escape_string($file_comment);
4108
4109
        $user = api_get_user_entity(api_get_user_id());
4110
        $course = api_get_course_entity(api_get_course_int_id());
4111
        $session = api_get_session_entity(api_get_session_id());
4112
4113
        $attachment = (new CForumAttachment())
4114
            ->setCId(api_get_course_int_id())
4115
            ->setComment($safe_file_comment)
4116
            ->setFilename($file_name)
4117
            ->setPath($file_name)
4118
            ->setPost($post)
4119
            ->setSize($file->getSize())
4120
            ->setParent($post)
4121
            ->addCourseLink(
4122
                $course,
4123
                $session
4124
            );
4125
4126
        $repo = Container::getForumAttachmentRepository();
4127
        $repo->create($attachment);
4128
        $repo->addFile($attachment, $file);
4129
        $repo->update($attachment);
4130
    }
4131
}
4132
4133
/**
4134
 * This function edits an attachment file into a forum.
4135
 *
4136
 * @param string $file_comment a comment about file
4137
 * @param int    $post_id
4138
 * @param int    $id_attach    attachment file Id
4139
 */
4140
function edit_forum_attachment_file($file_comment, $post_id, $id_attach)
4141
{
4142
    throw new Exception('edit_forum_attachment_file');
4143
    /*
4144
    $_course = api_get_course_info();
4145
    $table_forum_attachment = Database::get_course_table(TABLE_FORUM_ATTACHMENT);
4146
    $course_id = api_get_course_int_id();
4147
4148
    $filesData = [];
4149
4150
    if (!is_array($_FILES['user_upload']['name'])) {
4151
        $filesData[] = $_FILES['user_upload'];
4152
    } else {
4153
        $fileCount = count($_FILES['user_upload']['name']);
4154
        $fileKeys = array_keys($_FILES['user_upload']);
4155
4156
        for ($i = 0; $i < $fileCount; $i++) {
4157
            foreach ($fileKeys as $key) {
4158
                $filesData[$i][$key] = $_FILES['user_upload'][$key][$i];
4159
            }
4160
        }
4161
    }
4162
4163
    foreach ($filesData as $attachment) {
4164
        if (empty($attachment['name'])) {
4165
            continue;
4166
        }
4167
4168
        $upload_ok = process_uploaded_file($attachment);
4169
4170
        if (!$upload_ok) {
4171
            continue;
4172
        }
4173
4174
        $course_dir = $_course['path'].'/upload/forum';
4175
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
4176
        $updir = $sys_course_path.$course_dir;
4177
4178
        // Try to add an extension to the file if it hasn't one.
4179
        $new_file_name = add_ext_on_mime(stripslashes($attachment['name']), $attachment['type']);
4180
        // User's file name
4181
        $file_name = $attachment['name'];
4182
4183
        if (!filter_extension($new_file_name)) {
4184
            Display::addFlash(
4185
                Display::return_message(
4186
                    get_lang('File upload failed: this file extension or file type is prohibited'),
4187
                    'error'
4188
                )
4189
            );
4190
        } else {
4191
            $new_file_name = uniqid('');
4192
            $new_path = $updir.'/'.$new_file_name;
4193
            $result = @move_uploaded_file($attachment['tmp_name'], $new_path);
4194
            $safe_file_comment = Database::escape_string($file_comment);
4195
            $safe_file_name = Database::escape_string($file_name);
4196
            $safe_new_file_name = Database::escape_string($new_file_name);
4197
            $safe_post_id = (int) $post_id;
4198
            $safe_id_attach = (int) $id_attach;
4199
            // Storing the attachments if any.
4200
            if ($result) {
4201
                $sql = "UPDATE $table_forum_attachment
4202
                        SET
4203
                            filename = '$safe_file_name',
4204
                            comment = '$safe_file_comment',
4205
                            path = '$safe_new_file_name',
4206
                            post_id = '$safe_post_id',
4207
                            size ='".$attachment['size']."'
4208
                        WHERE c_id = $course_id AND id = '$safe_id_attach'";
4209
                Database::query($sql);
4210
                api_item_property_update(
4211
                    $_course,
4212
                    TOOL_FORUM_ATTACH,
4213
                    $safe_id_attach,
4214
                    'ForumAttachmentUpdated',
4215
                    api_get_user_id()
4216
                );
4217
            }
4218
        }
4219
    }*/
4220
}
4221
4222
/**
4223
 * Show a list with all the attachments according to the post's id.
4224
 *
4225
 * @param int $postId
4226
 *
4227
 * @return array with the post info
4228
 *
4229
 * @author Julio Montoya
4230
 *
4231
 * @version avril 2008, dokeos 1.8.5
4232
 */
4233
function get_attachment($postId)
4234
{
4235
    $table = Database::get_course_table(TABLE_FORUM_ATTACHMENT);
4236
    $course_id = api_get_course_int_id();
4237
    $row = [];
4238
    $postId = (int) $postId;
4239
4240
    if (empty($postId)) {
4241
        return [];
4242
    }
4243
4244
    $sql = "SELECT iid, path, filename, comment
4245
            FROM $table
4246
            WHERE c_id = $course_id AND post_id = $postId";
4247
    $result = Database::query($sql);
4248
    if (0 != Database::num_rows($result)) {
4249
        $row = Database::fetch_array($result);
4250
    }
4251
4252
    return $row;
4253
}
4254
4255
/**
4256
 * Delete the all the attachments from the DB and the file according to the post's id or attach id(optional).
4257
 *
4258
 * @param int $postId
4259
 * @param int $attachmentId
4260
 *
4261
 * @return bool
4262
 */
4263
function delete_attachment($postId, $attachmentId)
4264
{
4265
    $repo = Container::getForumPostRepository();
4266
    $em = Database::getManager();
4267
    /** @var CForumPost $post */
4268
    $post = $repo->find($postId);
4269
    if ($post) {
4270
        $repoAttachment = Container::getForumAttachmentRepository();
4271
        $attachment = $repoAttachment->find($attachmentId);
4272
        if ($attachment) {
4273
            $post->removeAttachment($attachment);
4274
            $em->remove($attachment);
4275
        }
4276
        $repo->update($post);
4277
4278
        Display::addFlash(Display::return_message(get_lang('The attached file has been deleted'), 'confirmation'));
4279
    }
4280
4281
    return true;
4282
}
4283
4284
/**
4285
 * This function gets all the forum information of the all the forum of the group.
4286
 *
4287
 * @param array $groupInfo the id of the group we need the fora of (see forum.forum_of_group)
4288
 *
4289
 * @return CForum[]
4290
 *
4291
 * @todo this is basically the same code as the get_forums function. Consider merging the two.
4292
 */
4293
function get_forums_of_group(CGroup $group)
4294
{
4295
    $course = api_get_course_entity();
4296
    $session = api_get_session_entity();
4297
    $repo = Container::getForumRepository();
4298
    $qb = $repo->getResourcesByCourse($course, $session, $group);
4299
4300
    return $qb->getQuery()->getResult();
4301
}
4302
4303
/**
4304
 * This function stores which users have to be notified of which forums or threads.
4305
 *
4306
 * @param string $content    does the user want to be notified about a forum or about a thread
4307
 * @param int    $id         the id of the forum or thread
4308
 * @param bool   $addOnly
4309
 * @param array  $userInfo
4310
 * @param array  $courseInfo
4311
 *
4312
 * @return string language variable
4313
 *
4314
 * @author  Patrick Cool <[email protected]>, Ghent University, Belgium
4315
 * @author  Julio Montoya
4316
 *
4317
 * @since   May 2008 v1.8.5
4318
 */
4319
function set_notification($content, $id, $addOnly = false, $userInfo = [], $courseInfo = [])
4320
{
4321
    $userInfo = empty($userInfo) ? api_get_user_info() : $userInfo;
4322
    $courseInfo = empty($courseInfo) ? api_get_course_info() : $courseInfo;
4323
    $id = (int) $id;
4324
4325
    if (empty($userInfo) || empty($courseInfo) || empty($id) || empty($content)) {
4326
        return false;
4327
    }
4328
4329
    // Database table definition
4330
    $table_notification = Database::get_course_table(TABLE_FORUM_NOTIFICATION);
4331
    $course_id = $courseInfo['real_id'];
4332
4333
    // Which database field do we have to store the id in?
4334
    $field = 'thread_id';
4335
    if ('forum' === $content) {
4336
        $field = 'forum_id';
4337
    }
4338
4339
    $userId = $userInfo['user_id'];
4340
4341
    // First we check if the notification is already set for this.
4342
    $sql = "SELECT * FROM $table_notification
4343
            WHERE
4344
                c_id = $course_id AND
4345
                $field = $id AND
4346
                user_id = $userId ";
4347
    $result = Database::query($sql);
4348
    $total = Database::num_rows($result);
4349
4350
    // If the user did not indicate that (s)he wanted to be notified already
4351
    // then we store the notification request (to prevent double notification requests).
4352
    if ($total <= 0) {
4353
        $notification = new CForumNotification();
4354
        $notification
4355
            ->setCId($course_id)
4356
            ->setUserId($userId)
4357
        ;
4358
4359
        if ('forum' === $content) {
4360
            $notification->setForumId($id);
4361
        } else {
4362
            $notification->setThreadId($id);
4363
        }
4364
4365
        $em = Database::getManager();
4366
        $em->persist($notification);
4367
        $em->flush();
4368
4369
        Session::erase('forum_notification');
4370
        getNotificationsPerUser(0, true);
4371
4372
        return get_lang('You will be notified of new posts by e-mail.');
4373
    } else {
4374
        if (!$addOnly) {
4375
            $sql = "DELETE FROM $table_notification
4376
                    WHERE
4377
                        c_id = $course_id AND
4378
                        $field = $id AND
4379
                        user_id = $userId ";
4380
            Database::query($sql);
4381
            Session::erase('forum_notification');
4382
            getNotificationsPerUser(0, true);
4383
4384
            return get_lang('You will no longer be notified of new posts by email');
4385
        }
4386
    }
4387
}
4388
4389
/**
4390
 * This function retrieves all the email adresses of the users who wanted to be notified
4391
 * about a new post in a certain forum or thread.
4392
 *
4393
 * @param string $content does the user want to be notified about a forum or about a thread
4394
 * @param int $id      the id of the forum or thread
4395
 *
4396
 * @return array returns
4397
 *
4398
 * @author Patrick Cool <[email protected]>, Ghent University, Belgium
4399
 * @author Julio Montoya
4400
 *
4401
 * @version May 2008, dokeos 1.8.5
4402
 *
4403
 * @since May 2008, dokeos 1.8.5
4404
 */
4405
function get_notifications(string $content, int $id): array
4406
{
4407
    // Database table definition
4408
    $table_users = Database::get_main_table(TABLE_MAIN_USER);
4409
    $table_notification = Database::get_course_table(TABLE_FORUM_NOTIFICATION);
4410
    $course_id = api_get_course_int_id();
4411
4412
    // Which database field contains the notification?
4413
    $field = 'thread_id';
4414
    if ('forum' === $content) {
4415
        $field = 'forum_id';
4416
    }
4417
4418
    $sql = "SELECT user.id as user_id, user.firstname, user.lastname, user.email, user.id user
4419
            FROM $table_users user, $table_notification notification
4420
            WHERE
4421
                notification.c_id = $course_id AND user.active = 1 AND
4422
                user.id = notification.user_id AND
4423
                notification.$field = $id ";
4424
4425
    $result = Database::query($sql);
4426
    $return = [];
4427
4428
    while ($row = Database::fetch_array($result)) {
4429
        $return['user'.$row['user_id']] = ['email' => $row['email'], 'user_id' => $row['user_id']];
4430
    }
4431
4432
    return $return;
4433
}
4434
4435
/**
4436
 * Get all the users who need to receive a notification of a new post (those subscribed to
4437
 * the forum or the thread).
4438
 *
4439
 * @param int $post_id the id of the post
4440
 *
4441
 * @return false|null
4442
 *
4443
 * @author Patrick Cool <[email protected]>, Ghent University, Belgium
4444
 *
4445
 * @version May 2008, dokeos 1.8.5
4446
 *
4447
 * @since May 2008, dokeos 1.8.5
4448
 */
4449
function send_notifications(CForum $forum, CForumThread $thread, $post_id = 0)
4450
{
4451
    if (!$forum) {
4452
        return false;
4453
    }
4454
4455
    // Users who subscribed to the forum
4456
    $users_to_be_notified_by_forum = get_notifications('forum', $forum->getIid());
4457
4458
    // User who subscribed to the thread
4459
    $users_to_be_notified_by_thread = [];
4460
    if (!$thread) {
4461
        $users_to_be_notified_by_thread = get_notifications('thread', $thread->getIid());
4462
    }
4463
4464
    $postInfo = null;
4465
    if (!empty($post_id)) {
4466
        $postInfo = Container::getForumPostRepository()->find($post_id);
4467
    }
4468
4469
    // Merging the two
4470
    $users_to_be_notified = array_merge($users_to_be_notified_by_forum, $users_to_be_notified_by_thread);
4471
4472
    $subscribe = (int) api_get_course_setting('subscribe_users_to_forum_notifications');
4473
    if (1 === $subscribe) {
4474
        $sessionId = api_get_session_id();
4475
        if (!empty($sessionId)) {
4476
            $users_to_be_notified = SessionManager::getAllUserIdsInSession($sessionId);
4477
        } else {
4478
            $users_to_be_notified = CourseManager::get_user_list_from_course_code(api_get_course_id());
4479
        }
4480
    }
4481
4482
    if (is_array($users_to_be_notified)) {
4483
        foreach ($users_to_be_notified as $value) {
4484
            $userInfo = api_get_user_info($value['user_id']);
4485
            send_mail($userInfo, $forum, $thread, $postInfo);
4486
        }
4487
    }
4488
}
4489
4490
/**
4491
 * Get all the notification subscriptions of the user
4492
 * = which forums and which threads does the user wants to be informed of when a new
4493
 * post is added to this thread.
4494
 *
4495
 * @param int  $user_id the user_id of a user (default = 0 => the current user)
4496
 * @param bool $force   force get the notification subscriptions (even if the information is already in the session
4497
 *
4498
 * @return array
4499
 *
4500
 * @author Patrick Cool <[email protected]>, Ghent University, Belgium
4501
 *
4502
 * @version May 2008, dokeos 1.8.5
4503
 *
4504
 * @since May 2008, dokeos 1.8.5
4505
 */
4506
function getNotificationsPerUser($user_id = 0, $force = false, $course_id = 0)
4507
{
4508
    // Database table definition
4509
    $table_notification = Database::get_course_table(TABLE_FORUM_NOTIFICATION);
4510
    $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
4511
    if (empty($course_id) || -1 == $course_id) {
4512
        return null;
4513
    }
4514
4515
    $user_id = empty($user_id) ? api_get_user_id() : (int) $user_id;
4516
4517
    if (!isset($_SESSION['forum_notification']) ||
4518
        $_SESSION['forum_notification']['course'] != $course_id ||
4519
        true == $force
4520
    ) {
4521
        $_SESSION['forum_notification']['course'] = $course_id;
4522
        $sql = "SELECT * FROM $table_notification
4523
                WHERE c_id = $course_id AND user_id='".$user_id."'";
4524
4525
        $result = Database::query($sql);
4526
        while ($row = Database::fetch_array($result)) {
4527
            if (null !== $row['forum_id']) {
4528
                $_SESSION['forum_notification']['forum'][] = $row['forum_id'];
4529
            }
4530
            if (null !== $row['thread_id']) {
4531
                $_SESSION['forum_notification']['thread'][] = $row['thread_id'];
4532
            }
4533
        }
4534
    }
4535
}
4536
4537
/**
4538
 * This function counts the number of post inside a thread.
4539
 *
4540
 * @param int $thread_id
4541
 *
4542
 * @return int the number of post inside a thread
4543
 *
4544
 * @author Jhon Hinojosa <[email protected]>,
4545
 *
4546
 * @version octubre 2008, dokeos 1.8
4547
 */
4548
function count_number_of_post_in_thread($thread_id)
4549
{
4550
    $table_posts = Database::get_course_table(TABLE_FORUM_POST);
4551
    $course_id = api_get_course_int_id();
4552
    if (empty($course_id)) {
4553
        return 0;
4554
    }
4555
    $sql = "SELECT count(*) count FROM $table_posts
4556
            WHERE thread_id='".(int) $thread_id."' ";
4557
    $result = Database::query($sql);
4558
4559
    $count = 0;
4560
    if (Database::num_rows($result) > 0) {
4561
        $row = Database::fetch_array($result);
4562
        $count = $row['count'];
4563
    }
4564
4565
    return $count;
4566
}
4567
4568
/**
4569
 * This function counts the number of post inside a thread user.
4570
 *
4571
 * @param int $thread_id
4572
 * @param int $user_id
4573
 *
4574
 * @return int the number of post inside a thread user
4575
 */
4576
function count_number_of_post_for_user_thread($thread_id, $user_id)
4577
{
4578
    $table_posts = Database::get_course_table(TABLE_FORUM_POST);
4579
    $sql = "SELECT count(iid) as count
4580
            FROM $table_posts
4581
            WHERE
4582
                  thread_id=".(int) $thread_id.' AND
4583
                  poster_id = '.(int) $user_id.' AND
4584
                  visible = 1 ';
4585
    $result = Database::query($sql);
4586
    $count = 0;
4587
    if (Database::num_rows($result) > 0) {
4588
        $count = Database::fetch_array($result);
4589
        $count = $count['count'];
4590
    }
4591
4592
    return $count;
4593
}
4594
4595
/**
4596
 * This function retrieves information of statistical.
4597
 *
4598
 * @param int $thread_id
4599
 * @param int $user_id
4600
 * @param int $course_id
4601
 *
4602
 * @return array the information of statistical
4603
 *
4604
 * @author Jhon Hinojosa <[email protected]>,
4605
 *
4606
 * @version oct 2008, dokeos 1.8
4607
 */
4608
function get_statistical_information($thread_id, $user_id, $course_id)
4609
{
4610
    $result = [];
4611
    $courseInfo = api_get_course_info_by_id($course_id);
4612
    $result['user_course'] = CourseManager::get_users_count_in_course($courseInfo['code']);
4613
    $result['post'] = count_number_of_post_in_thread($thread_id);
4614
    $result['user_post'] = count_number_of_post_for_user_thread($thread_id, $user_id);
4615
4616
    return $result;
4617
}
4618
4619
/**
4620
 * This function return the posts inside a thread from a given user.
4621
 *
4622
 * @param int $thread_id
4623
 * @param int $user_id
4624
 *
4625
 * @return array posts inside a thread
4626
 *
4627
 * @author  Jhon Hinojosa <[email protected]>,
4628
 *
4629
 * @version oct 2008, dokeos 1.8
4630
 */
4631
function get_thread_user_post(Course $course, $thread_id, $user_id)
4632
{
4633
    $table_posts = Database::get_course_table(TABLE_FORUM_POST);
4634
    $table_users = Database::get_main_table(TABLE_MAIN_USER);
4635
    $thread_id = (int) $thread_id;
4636
    $user_id = (int) $user_id;
4637
    $course_id = $course->getId();
4638
4639
    if (empty($course_id)) {
4640
        $course_id = api_get_course_int_id();
4641
    }
4642
    $sql = "SELECT *, user.id as user_id FROM $table_posts posts
4643
            LEFT JOIN $table_users user
4644
            ON posts.poster_id = user.id
4645
            WHERE posts.thread_id='$thread_id' AND
4646
                posts.poster_id='$user_id'
4647
            ORDER BY posts.iid ASC";
4648
4649
    $result = Database::query($sql);
4650
    $post_list = [];
4651
    while ($row = Database::fetch_array($result)) {
4652
        $row['status'] = '1';
4653
        $post_list[] = $row;
4654
        $sql = "SELECT *, users.id as user_id FROM $table_posts posts
4655
                LEFT JOIN $table_users users
4656
                ON (posts.poster_id=users.id)
4657
                WHERE posts.thread_id='$thread_id'
4658
                    AND posts.post_parent_id='".$row['iid']."'
4659
                ORDER BY posts.iid ASC";
4660
        $result2 = Database::query($sql);
4661
        while ($row2 = Database::fetch_array($result2)) {
4662
            $row2['status'] = '0';
4663
            $post_list[] = $row2;
4664
        }
4665
    }
4666
4667
    return $post_list;
4668
}
4669
4670
/**
4671
 * This function get the name of an thread by id.
4672
 *
4673
 * @param int $threadId
4674
 *
4675
 * @return string
4676
 *
4677
 * @author Christian Fasanando
4678
 * @author Julio Montoya <[email protected]> Adding security
4679
 */
4680
function get_name_thread_by_id(int $threadId): string
4681
{
4682
    $tForumThread = Database::get_course_table(TABLE_FORUM_THREAD);
4683
    $course_id = api_get_course_int_id();
4684
    $sql = "SELECT title
4685
            FROM $tForumThread
4686
            WHERE iid = $threadId";
4687
    $result = Database::query($sql);
4688
    $row = Database::fetch_array($result);
4689
4690
    return $row[0];
4691
}
4692
4693
/**
4694
 * This function gets all the post written by an user.
4695
 *
4696
 * @param int $user_id
4697
 * @param int $courseId
4698
 *
4699
 * @return string
4700
 */
4701
function get_all_post_from_user(int $user_id, int $courseId): string
4702
{
4703
    $j = 0;
4704
    $forums = get_forums($courseId);
4705
    krsort($forums);
4706
    $forum_results = '';
4707
4708
    foreach ($forums as $forum) {
4709
        $forumId = $forum->getIid();
4710
4711
        /*if (0 == $forum['visibility']) {
4712
            continue;
4713
        }*/
4714
        if ($j <= 4) {
4715
            $threads = get_threads($forumId);
4716
4717
            if ($threads) {
4718
                $i = 0;
4719
                $hand_forums = '';
4720
                $post_counter = 0;
4721
                foreach ($threads as $thread) {
4722
                    /*if (0 == $thread['visibility']) {
4723
                        continue;
4724
                    }*/
4725
                    if ($i <= 4) {
4726
                        $post_list = get_thread_user_post_limit(
4727
                            $courseId,
4728
                            $thread->getIid(),
4729
                            $user_id,
4730
                            1
4731
                        );
4732
                        $post_counter = count($post_list);
4733
                        if (is_array($post_list) && count($post_list) > 0) {
4734
                            $hand_forums .= '<div id="social-thread">';
4735
                            $hand_forums .= Display::getMdiIcon('format-quote-open', 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Thread'));
4736
                            $hand_forums .= '&nbsp;'.Security::remove_XSS($thread->getTitle(), STUDENT);
4737
                            $hand_forums .= '</div>';
4738
4739
                            foreach ($post_list as $posts) {
4740
                                $hand_forums .= '<div id="social-post">';
4741
                                $hand_forums .= '<strong>'.Security::remove_XSS($posts['post_title'], STUDENT).'</strong>';
4742
                                $hand_forums .= '<br / >';
4743
                                $hand_forums .= Security::remove_XSS($posts['post_text'], STUDENT);
4744
                                $hand_forums .= '</div>';
4745
                                $hand_forums .= '<br / >';
4746
                            }
4747
                        }
4748
                    }
4749
                    $i++;
4750
                }
4751
                $forum_results .= '<div id="social-forum">';
4752
                $forum_results .= '<div class="clear"></div><br />';
4753
                $forum_results .= '<div id="social-forum-title">'.
4754
                    Display::getMdiIcon('comment-quote', 'ch-tool-icon', '', ICON_SIZE_SMALL, get_lang('Forum')).'&nbsp;'.Security::remove_XSS($forum->getTitle(), STUDENT).
4755
                    '<div style="float:right;margin-top:-35px">
4756
                        <a href="../forum/viewforum.php?'.api_get_cidreq_params($courseId).'&forum='.$forum->getIid().' " >'.
4757
                    get_lang('See forum').'
4758
                        </a>
4759
                     </div></div>';
4760
                $forum_results .= '<br / >';
4761
                if ($post_counter > 0) {
4762
                    $forum_results .= $hand_forums;
4763
                }
4764
                $forum_results .= '</div>';
4765
            }
4766
            $j++;
4767
        }
4768
    }
4769
4770
    return $forum_results;
4771
}
4772
4773
/**
4774
 * @param int $thread_id
4775
 * @param int $user_id
4776
 * @param int $limit
4777
 *
4778
 * @return array
4779
 */
4780
function get_thread_user_post_limit($courseId, $thread_id, $user_id, $limit = 10)
4781
{
4782
    $table_posts = Database::get_course_table(TABLE_FORUM_POST);
4783
    $table_users = Database::get_main_table(TABLE_MAIN_USER);
4784
4785
    $courseId = (int) $courseId;
4786
    $limit = (int) $limit;
4787
4788
    $sql = "SELECT * FROM $table_posts posts
4789
            LEFT JOIN $table_users users
4790
                ON posts.poster_id=users.id
4791
            WHERE posts.thread_id='".Database::escape_string($thread_id)."' AND
4792
                posts.poster_id='".Database::escape_string($user_id)."'
4793
            ORDER BY posts.post_id DESC LIMIT $limit ";
4794
    $result = Database::query($sql);
4795
    $post_list = [];
4796
    while ($row = Database::fetch_array($result)) {
4797
        $row['status'] = '1';
4798
        $post_list[] = $row;
4799
    }
4800
4801
    return $post_list;
4802
}
4803
4804
/**
4805
 * @param string $userId
4806
 * @param array  $courseInfo
4807
 * @param int    $sessionId
4808
 *
4809
 * @return array
4810
 */
4811
function getForumCreatedByUser($userId, $courseInfo, $sessionId)
4812
{
4813
    if (empty($userId) || empty($courseInfo)) {
4814
        return [];
4815
    }
4816
4817
    $courseId = $courseInfo['real_id'];
4818
4819
    $repo = Container::getForumRepository();
4820
4821
    $courseEntity = api_get_course_entity($courseId);
4822
    $sessionEntity = api_get_session_entity($sessionId);
4823
4824
    $qb = $repo->getResourcesByCourse($courseEntity, $sessionEntity);
4825
4826
    $qb->andWhere('node.creator = :creator');
4827
    $qb->setParameter('creator', $userId);
4828
    $items = $qb->getQuery()->getResult();
4829
4830
    $forumList = [];
4831
    if (!empty($items)) {
4832
        /** @var CForum $forum */
4833
        foreach ($items as $forum) {
4834
            $forumList[] = [
4835
                $forum->getTitle(),
4836
                api_get_local_time($forum->getResourceNode()->getCreatedAt()),
4837
                api_get_local_time($forum->getResourceNode()->getUpdatedAt()),
4838
            ];
4839
        }
4840
    }
4841
4842
    return $forumList;
4843
}
4844
4845
/**
4846
 * This function builds an array of all the posts in a given thread
4847
 * where the key of the array is the post_id
4848
 * It also adds an element children to the array which itself is an array
4849
 * that contains all the id's of the first-level children.
4850
 *
4851
 * @return array containing all the information on the posts of a thread
4852
 *
4853
 * @author Patrick Cool <[email protected]>, Ghent University
4854
 */
4855
function calculate_children($rows)
4856
{
4857
    $sorted_rows = [0 => []];
4858
    if (!empty($rows)) {
4859
        foreach ($rows as $row) {
4860
            $rows_with_children[$row['post_id']] = $row;
4861
            $rows_with_children[$row['post_parent_id']]['children'][] = $row['post_id'];
4862
        }
4863
4864
        $rows = $rows_with_children;
4865
        forumRecursiveSort($rows, $sorted_rows);
4866
        unset($sorted_rows[0]);
4867
    }
4868
4869
    return $sorted_rows;
4870
}
4871
4872
/**
4873
 * @param $rows
4874
 * @param $threads
4875
 * @param int $seed
4876
 * @param int $indent
4877
 */
4878
function forumRecursiveSort($rows, &$threads, $seed = 0, $indent = 0)
4879
{
4880
    if ($seed > 0) {
4881
        $threads[$rows[$seed]['post_id']] = $rows[$seed];
4882
        $threads[$rows[$seed]['post_id']]['indent_cnt'] = $indent;
4883
        $indent++;
4884
    }
4885
4886
    if (isset($rows[$seed]['children'])) {
4887
        foreach ($rows[$seed]['children'] as $child) {
4888
            forumRecursiveSort($rows, $threads, $child, $indent);
4889
        }
4890
    }
4891
}
4892
4893
/**
4894
 * Update forum attachment data, used to update comment and post ID.
4895
 *
4896
 * @param array $array    (field => value) to update forum attachment row
4897
 * @param int   $id       ID to find row to update
4898
 * @param int   $courseId course ID to find row to update
4899
 *
4900
 * @return int number of affected rows
4901
 */
4902
function editAttachedFile($array, $id, $courseId = null)
4903
{
4904
    // Init variables
4905
    $setString = '';
4906
    $id = (int) $id;
4907
    $courseId = (int) $courseId;
4908
    if (empty($courseId)) {
4909
        // $courseId can be null, use api method
4910
        $courseId = api_get_course_int_id();
4911
    }
4912
    /*
4913
     * Check if Attachment ID and Course ID are greater than zero
4914
     * and array of field values is not empty
4915
     */
4916
    if ($id > 0 && $courseId > 0 && !empty($array) && is_array($array)) {
4917
        foreach ($array as $key => &$item) {
4918
            $item = Database::escape_string($item);
4919
            $setString .= $key.' = "'.$item.'", ';
4920
        }
4921
        // Delete last comma
4922
        $setString = substr($setString, 0, strlen($setString) - 2);
4923
        $forumAttachmentTable = Database::get_course_table(TABLE_FORUM_ATTACHMENT);
4924
        $sql = "UPDATE $forumAttachmentTable
4925
                SET $setString WHERE c_id = $courseId AND iid = $id";
4926
        $result = Database::query($sql);
4927
        if (false !== $result) {
4928
            $affectedRows = Database::affected_rows($result);
4929
            if ($affectedRows > 0) {
4930
                /*
4931
                 * If exist in $_SESSION variable, then delete them from it
4932
                 * because they would be deprecated
4933
                 */
4934
                if (!empty($_SESSION['forum']['upload_file'][$courseId][$id])) {
4935
                    unset($_SESSION['forum']['upload_file'][$courseId][$id]);
4936
                }
4937
            }
4938
4939
            return $affectedRows;
4940
        }
4941
    }
4942
4943
    return 0;
4944
}
4945
4946
/**
4947
 * Return a table where the attachments will be set.
4948
 *
4949
 * @param int $postId Forum Post ID
4950
 *
4951
 * @return string The Forum Attachments Ajax Table
4952
 */
4953
function getAttachmentsAjaxTable($postId = 0)
4954
{
4955
    $postId = (int) $postId;
4956
    $courseId = api_get_course_int_id();
4957
    $attachIds = getAttachmentIdsByPostId($postId, $courseId);
4958
    $fileDataContent = '';
4959
    // Update comment to show if form did not pass validation
4960
    if (!empty($_REQUEST['file_ids']) && is_array($_REQUEST['file_ids'])) {
4961
        // 'file_ids is the name from forum attachment ajax form
4962
        foreach ($_REQUEST['file_ids'] as $key => $attachId) {
4963
            if (!empty($_SESSION['forum']['upload_file'][$courseId][$attachId]) &&
4964
                is_array($_SESSION['forum']['upload_file'][$courseId][$attachId])
4965
            ) {
4966
                // If exist forum attachment then update into $_SESSION data
4967
                $_SESSION['forum']['upload_file'][$courseId][$attachId]['comment'] = $_POST['file_comments'][$key];
4968
            }
4969
        }
4970
    }
4971
4972
    // Get data to fill into attachment files table
4973
    if (!empty($_SESSION['forum']['upload_file'][$courseId]) &&
4974
        is_array($_SESSION['forum']['upload_file'][$courseId])
4975
    ) {
4976
        $uploadedFiles = $_SESSION['forum']['upload_file'][$courseId];
4977
        foreach ($uploadedFiles as $k => $uploadedFile) {
4978
            if (!empty($uploadedFile) && in_array($uploadedFile['id'], $attachIds)) {
4979
                // Buil html table including an input with attachmentID
4980
                $fileDataContent .= '<tr id="'.$uploadedFile['id'].'" ><td>'.$uploadedFile['name'].'</td><td>'.$uploadedFile['size'].'</td><td>&nbsp;'.$uploadedFile['result'].
4981
                    ' </td><td> <input style="width:90%;" type="text" value="'.$uploadedFile['comment'].'" name="file_comments[]"> </td><td>'.
4982
                    $uploadedFile['delete'].'</td>'.
4983
                    '<input type="hidden" value="'.$uploadedFile['id'].'" name="file_ids[]">'.'</tr>';
4984
            } else {
4985
                /*
4986
                 * If attachment data is empty, then delete it from $_SESSION
4987
                 * because could generate and empty row into html table
4988
                 */
4989
                unset($_SESSION['forum']['upload_file'][$courseId][$k]);
4990
            }
4991
        }
4992
    }
4993
    $style = empty($fileDataContent) ? 'display: none;' : '';
4994
    // Forum attachment Ajax table
4995
    return '
4996
    <div class="control-group " style="'.$style.'">
4997
        <label class="control-label">'.get_lang('Attachments list').'</label>
4998
        <div class="controls">
4999
            <table id="attachmentFileList" class="files data_table span10">
5000
                <tr>
5001
                    <th>'.get_lang('Filename').'</th>
5002
                    <th>'.get_lang('Size').'</th>
5003
                    <th>'.get_lang('Status').'</th>
5004
                    <th>'.get_lang('Comment').'</th>
5005
                    <th>'.get_lang('Delete').'</th>
5006
                </tr>
5007
                '.$fileDataContent.'
5008
            </table>
5009
        </div>
5010
    </div>';
5011
}
5012
5013
/**
5014
 * Return an array of prepared attachment data to build forum attachment table
5015
 * Also, save this array into $_SESSION to do available the attachment data.
5016
 *
5017
 * @param int $forumId
5018
 * @param int $threadId
5019
 * @param int $postId
5020
 * @param int $attachId
5021
 * @param int $courseId
5022
 *
5023
 * @return array
5024
 */
5025
function getAttachedFiles(
5026
    $forumId,
5027
    $threadId,
5028
    $postId = 0,
5029
    $attachId = 0,
5030
    $courseId = 0
5031
) {
5032
    $forumId = (int) $forumId;
5033
    $courseId = (int) $courseId;
5034
    $attachId = (int) $attachId;
5035
    $postId = (int) $postId;
5036
    $threadId = (int) $threadId;
5037
5038
    if (empty($courseId)) {
5039
        // $courseId can be null, use api method
5040
        $courseId = api_get_course_int_id();
5041
    }
5042
    if (empty($forumId)) {
5043
        if (!empty($_REQUEST['forum'])) {
5044
            $forumId = (int) $_REQUEST['forum'];
5045
        } else {
5046
            // if forum ID is empty, cannot generate delete url
5047
5048
            return [];
5049
        }
5050
    }
5051
    // Check if exist at least one of them to filter forum attachment select query
5052
    if (empty($postId) && empty($attachId)) {
5053
        return [];
5054
    }
5055
5056
    if (empty($postId)) {
5057
        $filter = "AND iid = $attachId";
5058
    } elseif (empty($attachId)) {
5059
        $filter = "AND post_id = $postId";
5060
    } else {
5061
        $filter = "AND post_id = $postId AND iid = $attachId";
5062
    }
5063
    $forumAttachmentTable = Database::get_course_table(TABLE_FORUM_ATTACHMENT);
5064
    $sql = "SELECT iid
5065
            FROM $forumAttachmentTable
5066
            WHERE c_id = $courseId $filter";
5067
    $result = Database::query($sql);
5068
    $json = [];
5069
    if (Database::num_rows($result) > 0) {
5070
        $repo = Container::getForumAttachmentRepository();
5071
        while ($row = Database::fetch_assoc($result)) {
5072
            /** @var CForumAttachment $attachment */
5073
            $attachment = $repo->find($row['iid']);
5074
            $downloadUrl = $repo->getResourceFileDownloadUrl($attachment);
5075
5076
            // name contains an URL to download attachment file and its filename
5077
            $json['name'] = Display::url(
5078
                api_htmlentities($attachment->getFilename()),
5079
                $downloadUrl,
5080
                ['target' => '_blank', 'class' => 'attachFilename']
5081
            );
5082
            $json['id'] = $row['iid'];
5083
            $json['comment'] = $attachment->getComment();
5084
            // Format file size
5085
            $json['size'] = format_file_size($attachment->getSize());
5086
            // Check if $row is consistent
5087
            if ($attachment) {
5088
                // Set result as success and bring delete URL
5089
                $json['result'] = Display::getMdiIcon('check-circle', 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Uploaded.'));
5090
                $url = api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&action=delete_attach&forum='.$forumId.'&thread='.$threadId.'&id_attach='.$row['iid'];
5091
                $json['delete'] = Display::url(
5092
                    Display::getMdiIcon(ActionIcon::DELETE, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Delete')),
5093
                    $url,
5094
                    ['class' => 'deleteLink']
5095
                );
5096
            } else {
5097
                // If not, set an exclamation result
5098
                $json['result'] = Display::getMdiIcon('close-circle', 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Error'));
5099
            }
5100
            // Store array data into $_SESSION
5101
            $_SESSION['forum']['upload_file'][$courseId][$json['id']] = $json;
5102
        }
5103
    }
5104
5105
    return $json;
5106
}
5107
5108
/**
5109
 * Clear forum attachment data stored in $_SESSION,
5110
 * If is not defined post, it will clear all forum attachment data from course.
5111
 *
5112
 * @param int $postId   -1 : Clear all attachments from course stored in $_SESSION
5113
 *                      0 : Clear attachments from course, except from temporal post "0"
5114
 *                      but without delete them from file system and database
5115
 *                      Other values : Clear attachments from course except specified post
5116
 *                      and delete them from file system and database
5117
 * @param int $courseId : Course ID, if it is null, will use api_get_course_int_id()
5118
 *
5119
 * @return array
5120
 */
5121
function clearAttachedFiles($postId = 0, $courseId = 0)
5122
{
5123
    // Init variables
5124
    $courseId = (int) $courseId;
5125
    $postId = (int) $postId;
5126
    $array = [];
5127
    if (empty($courseId)) {
5128
        // $courseId can be null, use api method
5129
        $courseId = api_get_course_int_id();
5130
    }
5131
    if (-1 === $postId) {
5132
        // If post ID is -1 then delete course's attachment data from $_SESSION
5133
        if (!empty($_SESSION['forum']['upload_file'][$courseId])) {
5134
            $array = array_keys($_SESSION['forum']['upload_file'][$courseId]);
5135
            unset($_SESSION['forum']['upload_file'][$courseId]);
5136
        }
5137
    } else {
5138
        $attachIds = getAttachmentIdsByPostId($postId, $courseId);
5139
        if (!empty($_SESSION['forum']['upload_file'][$courseId]) &&
5140
            is_array($_SESSION['forum']['upload_file'][$courseId])) {
5141
            foreach ($_SESSION['forum']['upload_file'][$courseId] as $attachId => $attach) {
5142
                if (!in_array($attachId, $attachIds)) {
5143
                    // If attach ID is not into specified post, delete attachment
5144
                    // Save deleted attachment ID
5145
                    $array[] = $attachId;
5146
                    if (0 !== $postId) {
5147
                        // Post 0 is temporal, delete them from file system and DB
5148
                        delete_attachment(0, $attachId);
5149
                    }
5150
                    // Delete attachment data from $_SESSION
5151
                    unset($_SESSION['forum']['upload_file'][$courseId][$attachId]);
5152
                }
5153
            }
5154
        }
5155
    }
5156
5157
    return $array;
5158
}
5159
5160
/**
5161
 * Returns an array of forum attachment ids into a course and forum post.
5162
 *
5163
 * @param int $postId
5164
 * @param int $courseId
5165
 *
5166
 * @return array
5167
 */
5168
function getAttachmentIdsByPostId($postId, $courseId = 0)
5169
{
5170
    $array = [];
5171
    $courseId = (int) $courseId;
5172
    $postId = (int) $postId;
5173
    if (empty($courseId)) {
5174
        // $courseId can be null, use api method
5175
        $courseId = api_get_course_int_id();
5176
    }
5177
    if ($courseId > 0) {
5178
        $forumAttachmentTable = Database::get_course_table(TABLE_FORUM_ATTACHMENT);
5179
        $sql = "SELECT iid FROM $forumAttachmentTable
5180
                WHERE c_id = $courseId AND post_id = $postId";
5181
        $result = Database::query($sql);
5182
        if (false !== $result && Database::num_rows($result) > 0) {
5183
            while ($row = Database::fetch_assoc($result)) {
5184
                $array[] = $row['iid'];
5185
            }
5186
        }
5187
    }
5188
5189
    return $array;
5190
}
5191
5192
function getPostStatus(CForum $forum, array $row, bool $addWrapper = true): string
5193
{
5194
    $statusIcon = '';
5195
    if ($forum->isModerated()) {
5196
        if ($addWrapper) {
5197
            $statusIcon = '<br /><br /><span id="status_post_'.$row['iid'].'">';
5198
        }
5199
        $row['status'] = empty($row['status']) ? 2 : $row['status'];
5200
5201
        $addUrl = false;
5202
        $showStatus = false;
5203
        if (api_is_allowed_to_edit(false, true)) {
5204
            $addUrl = true;
5205
        } else {
5206
            if ($row['user_id'] == api_get_user_id()) {
5207
                $showStatus = true;
5208
            }
5209
        }
5210
5211
        $label = '';
5212
        $icon = '';
5213
        $buttonType = '';
5214
        switch ($row['status']) {
5215
            case CForumPost::STATUS_VALIDATED:
5216
                $label = get_lang('Validated');
5217
                $icon = 'check-circle';
5218
                $buttonType = 'success';
5219
5220
                break;
5221
            case CForumPost::STATUS_WAITING_MODERATION:
5222
                $label = get_lang('Waiting for moderation');
5223
                $icon = 'alert';
5224
                $buttonType = 'warning';
5225
5226
                break;
5227
            case CForumPost::STATUS_REJECTED:
5228
                $label = get_lang('Rejected');
5229
                $icon = 'minus-circle';
5230
                $buttonType = 'danger';
5231
5232
                break;
5233
        }
5234
5235
        if ($addUrl) {
5236
            $statusIcon .= Display::toolbarButton(
5237
                $label.'&nbsp;',
5238
                'javascript:void(0)',
5239
                $icon,
5240
                $buttonType,
5241
                ['class' => 'change_post_status']
5242
            );
5243
        } else {
5244
            if ($showStatus) {
5245
                $statusIcon .= Display::label(
5246
                    Display::getMdiIcon($icon).$label,
5247
                    $buttonType
5248
                );
5249
            }
5250
        }
5251
5252
        if ($addWrapper) {
5253
            $statusIcon .= '</span>';
5254
        }
5255
    }
5256
5257
    return $statusIcon;
5258
}
5259
5260
/**
5261
 * @param CForum $forum
5262
 * @param int    $threadId
5263
 * @param int    $status
5264
 */
5265
function getCountPostsWithStatus($status, $forum, $threadId = null)
5266
{
5267
    $em = Database::getManager();
5268
    $criteria = Criteria::create();
5269
    $criteria
5270
        ->where(Criteria::expr()->eq('status', $status))
5271
        //->andWhere(Criteria::expr()->eq('cId', $forum->getCId()))
5272
        ->andWhere(Criteria::expr()->eq('visible', 1))
5273
    ;
5274
5275
    if (!empty($threadId)) {
5276
        $criteria->andWhere(Criteria::expr()->eq('thread', $threadId));
5277
    }
5278
5279
    $qb = $em->getRepository(CForumPost::class)->createQueryBuilder('p');
5280
    $qb->select('count(p.iid)')
5281
        ->addCriteria($criteria);
5282
5283
    return $qb->getQuery()->getSingleScalarResult();
5284
}
5285
5286
/**
5287
 * @param CForum     $forum
5288
 * @param CForumPost $post
5289
 *
5290
 * @return bool
5291
 */
5292
/**
5293
 * Student editability guard that accepts either an entity or the array shape used in views.
5294
 *
5295
 * @param CForum              $forum
5296
 * @param CForumPost|array    $post
5297
 */
5298
function postIsEditableByStudent(CForum $forum, $post): bool
5299
{
5300
    // Normalize $post to an entity
5301
    /** @var CForumPost|null $entity */
5302
    $entity = null;
5303
5304
    if ($post instanceof CForumPost) {
5305
        $entity = $post;
5306
    } elseif (is_array($post)) {
5307
        // Prefer the embedded entity from getPosts()
5308
        if (isset($post['entity']) && $post['entity'] instanceof CForumPost) {
5309
            $entity = $post['entity'];
5310
        } else {
5311
            // Fallback: fetch by id if present in the array
5312
            $postId = $post['post_id'] ?? $post['iid'] ?? null;
5313
            if ($postId) {
5314
                $em = Database::getManager();
5315
                $entity = $em->getRepository(CForumPost::class)->find($postId);
5316
            }
5317
        }
5318
    }
5319
5320
    // If we still don't have a valid entity, be conservative: deny student edit
5321
    if (!$entity instanceof CForumPost) {
5322
        return false;
5323
    }
5324
5325
    // Admins/teachers can always edit
5326
    if (api_is_platform_admin() || api_is_allowed_to_edit()) {
5327
        return true;
5328
    }
5329
5330
    $isModerated = (bool) $forum->isModerated();
5331
    $userId      = (int) api_get_user_id();
5332
    $ownerId     = $entity->getUser() ? (int) $entity->getUser()->getId() : 0;
5333
    $isOwner     = ($ownerId === $userId);
5334
    $status      = $entity->getStatus();
5335
5336
    // Unmoderated forum: student can edit only own posts
5337
    if (!$isModerated) {
5338
        return $isOwner;
5339
    }
5340
5341
    // editable by owner when status is NULL or WAITING or REJECTED.
5342
    if ($isOwner) {
5343
        if ($status === null) {
5344
            return true;
5345
        }
5346
5347
        return in_array(
5348
            $status,
5349
            [
5350
                CForumPost::STATUS_WAITING_MODERATION,
5351
                CForumPost::STATUS_REJECTED,
5352
            ],
5353
            true
5354
        );
5355
    }
5356
5357
    // Not owner and not a teacher/admin
5358
    return false;
5359
}
5360
5361
5362
/**
5363
 * @return bool
5364
 */
5365
function savePostRevision(CForumPost $post)
5366
{
5367
    $userId = api_get_user_id();
5368
5369
    if ($post->getUser()->getId() != $userId) {
5370
        return false;
5371
    }
5372
5373
    $status = (int) !postNeedsRevision($post);
5374
    $extraFieldValue = new ExtraFieldValue('forum_post');
5375
    $params = [
5376
        'item_id' => $post->getIid(),
5377
        'extra_ask_for_revision' => ['extra_ask_for_revision' => $status],
5378
    ];
5379
    if (empty($status)) {
5380
        unset($params['extra_ask_for_revision']);
5381
    }
5382
    $extraFieldValue->saveFieldValues(
5383
        $params,
5384
        true,
5385
        false,
5386
        ['ask_for_revision']
5387
    );
5388
}
5389
5390
/**
5391
 * @param int $postId
5392
 *
5393
 * @return string
5394
 */
5395
function getPostRevision($postId)
5396
{
5397
    $extraFieldValue = new ExtraFieldValue('forum_post');
5398
    $value = $extraFieldValue->get_values_by_handler_and_field_variable(
5399
        $postId,
5400
        'revision_language'
5401
    );
5402
    $revision = '';
5403
    if ($value && isset($value['value'])) {
5404
        $revision = $value['value'];
5405
    }
5406
5407
    return $revision;
5408
}
5409
5410
function postNeedsRevision(CForumPost $post): bool
5411
{
5412
    $postId = $post->getIid();
5413
    $extraFieldValue = new ExtraFieldValue('forum_post');
5414
    $value = $extraFieldValue->get_values_by_handler_and_field_variable(
5415
        $postId,
5416
        'ask_for_revision'
5417
    );
5418
    $hasRevision = false;
5419
    if ($value && isset($value['value'])) {
5420
        return 1 == $value['value'];
5421
    }
5422
5423
    return $hasRevision;
5424
}
5425
5426
/**
5427
 * Generates an HTML button to ask for a review
5428
 */
5429
function getAskRevisionButton(CForumPost $post, CForumThread $threadInfo): string
5430
{
5431
    if ('false' === api_get_setting('forum.allow_forum_post_revisions')) {
5432
        return '';
5433
    }
5434
5435
    $postId = $post->getIid();
5436
5437
    $status = 'btn--plain';
5438
    if (postNeedsRevision($post)) {
5439
        $status = 'btn--success';
5440
    }
5441
5442
    return Display::url(
5443
        get_lang('Ask for a revision'),
5444
        api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.
5445
        api_get_cidreq().'&action=ask_revision&post_id='.$postId.'&forum='.$threadInfo->getForum()->getIid().'&thread='.$threadInfo->getIid(),
5446
        ['class' => "btn $status", 'title' => get_lang('Ask for a revision')]
5447
    );
5448
}
5449
5450
/**
5451
 * Generates an HTML button to give a review
5452
 */
5453
function getGiveRevisionButton(int $postId, CForumThread $threadInfo): string
5454
{
5455
    return Display::toolbarButton(
5456
        get_lang('Give revision'),
5457
        api_get_path(WEB_CODE_PATH).'forum/reply.php?'.api_get_cidreq().'&'.http_build_query(
5458
            [
5459
                'forum' => $threadInfo->getForum()->getIid(),
5460
                'thread' => $threadInfo->getIid(),
5461
                'post' => $postId,
5462
                'action' => 'replymessage',
5463
                'give_revision' => 1,
5464
            ]
5465
        ),
5466
        'reply',
5467
        'primary',
5468
        ['id' => "reply-to-post-{$postId}"]
5469
    );
5470
}
5471
5472
/**
5473
 * Generates an HTML button to report a post as offensive
5474
 */
5475
function getReportButton(int $postId, CForumThread $threadInfo): string
5476
{
5477
    return Display::url(
5478
        Display::getMdiIcon('flag'),
5479
        api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.
5480
        api_get_cidreq().'&action=report&post_id='.$postId.
5481
        '&forum='.$threadInfo->getForum()->getIid().'&thread='.$threadInfo->getIid(),
5482
        ['class' => 'btn btn--danger', 'title' => get_lang('Report')]
5483
    );
5484
}
5485
5486
/**
5487
 * @return bool
5488
 */
5489
function reportAvailable()
5490
{
5491
    $extraFieldValue = new ExtraFieldValue('course');
5492
    $value = $extraFieldValue->get_values_by_handler_and_field_variable(
5493
        api_get_course_int_id(),
5494
        'allow_forum_report_button'
5495
    );
5496
    $allowReport = false;
5497
    if ($value && isset($value['value']) && 1 == $value['value']) {
5498
        $allowReport = true;
5499
    }
5500
5501
    return $allowReport;
5502
}
5503
5504
/**
5505
 * @return array
5506
 */
5507
function getReportRecipients()
5508
{
5509
    $extraFieldValue = new ExtraFieldValue('course');
5510
    $value = $extraFieldValue->get_values_by_handler_and_field_variable(
5511
        api_get_course_int_id(),
5512
        'forum_report_recipients'
5513
    );
5514
    $users = [];
5515
    if ($value && isset($value['value'])) {
5516
        $usersType = explode(';', $value['value']);
5517
5518
        foreach ($usersType as $type) {
5519
            switch ($type) {
5520
                case 'teachers':
5521
                    $teachers = CourseManager::get_teacher_list_from_course_code(api_get_course_id());
5522
                    if (!empty($teachers)) {
5523
                        $users = array_merge($users, array_column($teachers, 'user_id'));
5524
                    }
5525
5526
                break;
5527
                case 'admins':
5528
                    $admins = UserManager::get_all_administrators();
5529
                    if (!empty($admins)) {
5530
                        $users = array_merge($users, array_column($admins, 'user_id'));
5531
                    }
5532
5533
                    break;
5534
                case 'community_managers':
5535
                    $managers = api_get_setting('forum.community_managers_user_list', true);
5536
                    if (!empty($managers) && isset($managers['users'])) {
5537
                        $users = array_merge($users, $managers['users']);
5538
                    }
5539
5540
                    break;
5541
            }
5542
        }
5543
5544
        $users = array_unique(array_filter($users));
5545
    }
5546
5547
    return $users;
5548
}
5549
5550
/**
5551
 * Sends an e-mail to all users from getReportRecipients() (users with extra field 'forum_report_recipients')
5552
 */
5553
function reportPost(CForumPost $post, CForum $forumInfo, CForumThread $threadInfo): bool
5554
{
5555
    if (!reportAvailable()) {
5556
        return false;
5557
    }
5558
5559
    if (empty($forumInfo) || empty($threadInfo)) {
5560
        return false;
5561
    }
5562
5563
    $postId = $post->getIid();
5564
5565
    $currentUser = api_get_user_info();
5566
    $users = getReportRecipients();
5567
    if (!empty($users)) {
5568
        $url = api_get_path(WEB_CODE_PATH).
5569
            'forum/viewthread.php?forum='.$forumInfo->getIid().'&thread='.$threadInfo->getIid().'&'.api_get_cidreq(true, true, false).'&post_id='.$postId.'#post_id_'.$postId;
5570
        $postLink = Display::url(
5571
            $post->getTitle(),
5572
            $url
5573
        );
5574
        $subject = get_lang('Post reported');
5575
        $content = sprintf(
5576
            get_lang('User %s has reported the message %s in the forum %s'),
5577
            $currentUser['complete_name'],
5578
            $postLink,
5579
            $forumInfo->getTitle()
5580
        );
5581
        foreach ($users as $userId) {
5582
            MessageManager::send_message_simple($userId, $subject, $content);
5583
        }
5584
    }
5585
5586
    return true;
5587
}
5588
5589
function getVisibleForums($courseId, $sessionId): array
5590
{
5591
    $forums = get_forums($courseId, $sessionId);
5592
    $visibleForums = [];
5593
5594
    foreach ($forums as $forum) {
5595
        $forumSession = $forum->getFirstResourceLink()->getSession();
5596
        if ($sessionId > 0) {
5597
            if (null === $forumSession) {
5598
                $threads = get_threads($forum->getIid(), $courseId, $sessionId, true);
5599
                if (!empty($threads)) {
5600
                    $visibleForums[] = $forum;
5601
                }
5602
            } else {
5603
                $visibleForums[] = $forum;
5604
            }
5605
        } else {
5606
            $visibleForums[] = $forum;
5607
        }
5608
    }
5609
5610
    return $visibleForums;
5611
}
5612
5613
function getVisibleForumsInCategory($categoryId, $courseId, $sessionId): array
5614
{
5615
    $forumsInCategory = get_forums_in_category($categoryId, $courseId, $sessionId);
5616
    $visibleForums = [];
5617
5618
    foreach ($forumsInCategory as $forum) {
5619
        $forumSession = $forum->getFirstResourceLink()->getSession();
5620
        if ($sessionId > 0) {
5621
            if (null === $forumSession) {
5622
                $threads = get_threads($forum->getIid(), $courseId, $sessionId, true);
5623
                if (!empty($threads)) {
5624
                    $visibleForums[] = $forum;
5625
                }
5626
            } else {
5627
                $visibleForums[] = $forum;
5628
            }
5629
        } else {
5630
            $visibleForums[] = $forum;
5631
        }
5632
    }
5633
5634
    return $visibleForums;
5635
}
5636