topic.php 17.1 KB
Newer Older
Clare Lenihan's avatar
Clare Lenihan committed
1 2 3 4 5
<?php
/**
 *
 * @package    mahara
 * @subpackage interaction-forum
6
 * @author     Catalyst IT Ltd
7 8
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later
 * @copyright  For copyright information on Mahara, please see the README file distributed with this software.
Clare Lenihan's avatar
Clare Lenihan committed
9 10 11 12
 *
 */

define('INTERNAL', 1);
13
define('PUBLIC', 1);
14
define('MENUITEM', 'groups/forums');
15 16 17 18
define('SECTION_PLUGINTYPE', 'interaction');
define('SECTION_PLUGINNAME', 'forum');
define('SECTION_PAGE', 'topic');

Clare Lenihan's avatar
Clare Lenihan committed
19 20 21
require(dirname(dirname(dirname(__FILE__))) . '/init.php');
safe_require('interaction', 'forum');
require_once('group.php');
22 23
require_once(get_config('docroot') . 'interaction/lib.php');
require_once('pieforms/pieform.php');
Clare Lenihan's avatar
Clare Lenihan committed
24 25 26

$topicid = param_integer('id');

27
$topic = get_record_sql(
28
    'SELECT p.subject, p.poster, p.id AS firstpost, ' . db_format_tsfield('p.ctime', 'ctime') . ', t.id, f.group AS groupid, g.name AS groupname, f.id AS forumid, f.title AS forumtitle, t.closed, sf.forum AS forumsubscribed, st.topic AS topicsubscribed
Clare Lenihan's avatar
Clare Lenihan committed
29
    FROM {interaction_forum_topic} t
30
    INNER JOIN {interaction_instance} f ON (t.forum = f.id AND f.deleted != 1)
31
    INNER JOIN {group} g ON (g.id = f.group AND g.deleted = 0)
32 33 34
    INNER JOIN {interaction_forum_post} p ON (p.topic = t.id AND p.parent IS NULL)
    LEFT JOIN {interaction_forum_subscription_forum} sf ON (sf.forum = f.id AND sf.user = ?)
    LEFT JOIN {interaction_forum_subscription_topic} st ON (st.topic = t.id AND st.user = ?)
35 36
    WHERE t.id = ?
    AND t.deleted != 1',
37
    array($USER->get('id'), $USER->get('id'), $topicid)
Clare Lenihan's avatar
Clare Lenihan committed
38
);
Clare Lenihan's avatar
Clare Lenihan committed
39

40
if (!$topic) {
41
    throw new NotFoundException(get_string('cantfindtopic', 'interaction.forum', $topicid));
Clare Lenihan's avatar
Clare Lenihan committed
42 43
}

44 45
define('GROUP', $topic->groupid);

46 47 48
$group = get_record('group', 'id', $topic->groupid);
$publicgroup = $group->public;
$ineditwindow = group_within_edit_window($group);
Evan Goldenberg's avatar
Evan Goldenberg committed
49 50
$feedlink = get_config('wwwroot') . 'interaction/forum/atom.php?type=t&id=' . $topic->id;

51
$membership = user_can_access_forum((int)$topic->forumid);
52
$moderator = $ineditwindow && (bool)($membership & INTERACTION_FORUM_MOD);
Clare Lenihan's avatar
Clare Lenihan committed
53

Josh Schmidt's avatar
Josh Schmidt committed
54
$forumconfig = get_records_assoc('interaction_forum_instance_config', 'forum', $topic->forumid, '', 'field,value');
55 56 57
$indentmode = isset($forumconfig['indentmode']) ? $forumconfig['indentmode']->value : 'full_indent';
$maxindentdepth = isset($forumconfig['maxindent']) ? $forumconfig['maxindent']->value : 10;

58 59
if (!$membership
    && !get_field('group', 'public', 'id', $topic->groupid)) {
60 61 62
    $objection = param_integer('objection', 0);
    $errorstr = ($objection) ? get_string('accessdeniedobjection', 'error') : get_string('cantviewtopic', 'interaction.forum');
    throw new GroupAccessDeniedException($errorstr, $objection);
Clare Lenihan's avatar
Clare Lenihan committed
63
}
64
$topic->canedit = ($moderator || user_can_edit_post($topic->poster, $topic->ctime)) && $ineditwindow;
65

66 67
define('TITLE', $topic->forumtitle . ' - ' . $topic->subject);

68 69
$groupadmins = group_get_admin_ids($topic->groupid);

70
if ($membership && !$topic->forumsubscribed) {
71
    $topic->subscribe = pieform(array(
Clare Lenihan's avatar
Clare Lenihan committed
72
        'name'     => 'subscribe_topic',
73
        'renderer' => 'div',
Clare Lenihan's avatar
Clare Lenihan committed
74 75
        'plugintype' => 'interaction',
        'pluginname' => 'forum',
76
        'class' => 'btn-group btn-group-top',
Clare Lenihan's avatar
Clare Lenihan committed
77
        'autofocus' => false,
78 79
        'elements' => array(
            'submit' => array(
Pat Kira's avatar
Pat Kira committed
80
               'type'  => 'button',
81
               'usebuttontag' => true,
82
               'class' => 'btn-default',
83
               'value' => $topic->topicsubscribed ? '<span class="icon icon-times icon-lg text-danger left" role="presentation" aria-hidden="true"></span>'. get_string('unsubscribefromtopic', 'interaction.forum') : '<span class="icon icon-star icon-lg left" role="presentation" aria-hidden="true"></span>' . get_string('subscribetotopic', 'interaction.forum'),
84
               'help' => false
85 86 87 88 89 90 91 92
            ),
            'topic' => array(
                'type' => 'hidden',
                'value' => $topicid
            ),
            'type' => array(
                'type' => 'hidden',
                'value' => $topic->topicsubscribed ? 'unsubscribe' : 'subscribe'
93 94 95 96
            )
        )
   ));
}
97 98 99
// posts pagination params
$offset = param_integer('offset', 0);
$limit  = param_integer('limit', 10);
100 101 102 103 104 105 106 107 108 109 110 111
$postid = param_integer('post', 0);
if (!empty($postid)) {
    // validates the $postid
    $post = get_record('interaction_forum_post', 'id', $postid, 'deleted', '0', null, null, 'id, path');
    if (!$post) {
        throw new NotFoundException("The post with ID '$postid' is not found or deleted!");
    }
    // caculated offset value to jump to the page of the post
    $offset = count_records_select('interaction_forum_post', 'topic = ? AND path < ?', array($topicid, $post->path));
    $offset = $offset - ($offset % $limit);
    redirect(get_config('wwwroot') . 'interaction/forum/topic.php?id=' . $topicid . '&offset=' . $offset . '&limit=' . $limit . '#post' . $postid);
}
Clare Lenihan's avatar
Clare Lenihan committed
112

113
$order = ($indentmode == 'no_indent') ? 'p.ctime, p.id' : 'p.path, p.ctime, p.id';
Clare Lenihan's avatar
Clare Lenihan committed
114
$posts = get_records_sql_array(
115
    'SELECT p.id, p.parent, p.path, p.poster, p.subject, p.body, ' . db_format_tsfield('p.ctime', 'ctime') . ', p.deleted
116 117
    FROM {interaction_forum_post} p
    WHERE p.topic = ?
118
    ORDER BY ' . $order,
119 120 121
    array($topicid),
    $offset,
    $limit
Clare Lenihan's avatar
Clare Lenihan committed
122
);
123 124 125 126 127 128
// This is only needed for the 'no_indent' option
$lastpostid = null;
if ($indentmode == 'no_indent') {
    $lastpost = get_record_select('interaction_forum_post', 'topic = ? ORDER by ctime DESC, id DESC LIMIT 1', array($topicid));
    $lastpostid = $lastpost->id;
}
129
// Get extra info of posts
130 131
$prevdeletedid = false;
foreach ($posts as $postid => $post) {
132 133 134
    // Get the number of posts
    $post->postcount = get_postcount($post->poster);

135
    $post->canedit = $post->parent && ($moderator || user_can_edit_post($post->poster, $post->ctime)) && $ineditwindow;
136 137 138 139 140 141 142
    $post->ctime = relative_date(get_string('strftimerecentfullrelative', 'interaction.forum'), get_string('strftimerecentfull'), $post->ctime);
    // Get post edit records
    $post->edit = get_postedits($post->id);
    // Get moderator info
    $post->moderator = is_moderator($post->poster)? $post->poster : null;
    // Update the subject of posts
    $post->subject = !empty($post->subject) ? $post->subject : get_string('re', 'interaction.forum', get_ancestorpostsubject($post->id));
143 144 145
    // If this is the own post
    $post->ownpost = ($USER->get('id') == $post->poster) ? true : false;
    // Reported reason data
146 147 148
    $post->reports = get_records_select_array('objectionable',
                        'objecttype = ? AND objectid = ? AND resolvedby IS NULL AND resolvedtime IS NULL',
                        array('forum', $post->id));
149

150 151 152 153 154 155 156 157 158 159 160 161 162 163 164

    // Consolidate deleted message posts by the same author into one "X posts by Spammer Joe were deleted"
    if ($post->deleted) {
        if ($prevdeletedid && $posts[$prevdeletedid]->poster == $post->poster) {
            $posts[$prevdeletedid]->deletedcount++;
            unset($posts[$postid]);
        }
        else {
            $prevdeletedid = $postid;
            $post->deletedcount = 1;
        }
    }
    else {
        $prevdeletedid = false;
    }
Clare Lenihan's avatar
Clare Lenihan committed
165
}
166

167 168 169 170 171
// If the user has internal notifications for this topic, mark them
// all as read.  Obviously there's no guarantee the user will actually
// read all the posts on this page, but better than letting the unread
// notifications grow too fast.  Unfortunately the only way to find
// notifications on this topic is to look for the url of this page.
Richard Mansfield's avatar
Richard Mansfield committed
172 173 174 175
execute_sql('
    UPDATE {notification_internal_activity}
    SET "read" = 1
    WHERE "read" = 0 AND usr = ? AND url LIKE ? || \'%\' AND type = (
176
        SELECT id FROM {activity_type} WHERE name = ?
Richard Mansfield's avatar
Richard Mansfield committed
177
    )',
178 179
    array(
        $USER->get('id'),
180
        get_config('wwwroot') . 'interaction/forum/topic.php?id=' . $topicid . '&post=',
181 182 183 184
        'newpost',
    )
);

185
// renders a page of posts
186
$posts = buildpostlist($posts, $indentmode, $maxindentdepth);
187 188 189 190 191 192 193 194
// adds posts pagination
$postcount = count_records_sql('SELECT COUNT(id) FROM {interaction_forum_post} WHERE topic = ?', array($topicid));
$pagination = build_pagination(array(
        'url' => get_config('wwwroot') . 'interaction/forum/topic.php?id=' . $topicid,
        'count' => $postcount,
        'limit' => $limit,
        'offset' => $offset,
));
Clare Lenihan's avatar
Clare Lenihan committed
195

Evan Goldenberg's avatar
Evan Goldenberg committed
196 197
$headers = array();
if ($publicgroup) {
198
    $headers[] = '<link rel="alternate" type="application/atom+xml" href="' . $feedlink . '">';
Evan Goldenberg's avatar
Evan Goldenberg committed
199 200
}

201

202
$smarty = smarty(array(), $headers);
203
$smarty->assign('topic', $topic);
204
$smarty->assign('membership', $membership);
Clare Lenihan's avatar
Clare Lenihan committed
205
$smarty->assign('moderator', $moderator);
206
$smarty->assign('lastpostid', $lastpostid);
207
$smarty->assign('posts', $posts);
208
$smarty->assign('pagination', $pagination['html']);
Clare Lenihan's avatar
Clare Lenihan committed
209 210
$smarty->display('interaction:forum:topic.tpl');

211 212 213 214 215 216 217 218
/*
 * Render a page of posts
 *
 * @param array $posts list of posts
 * @param string $mode ('no_indent', 'max_indent', 'full_indent')
 * @param int $max_depth the maximum depth to indent to
 */
function buildpostlist($posts, $mode, $max_depth) {
219 220
    switch ($mode) {
        case 'no_indent':
221
            $max_depth = 1;
222 223 224 225 226
            break;
        case 'max_indent':
            break;
        case 'full_indent':
        default:
227
            $max_depth = -1;
228 229
            break;
    }
230 231 232 233
    $html = '';
    foreach ($posts as $post) {
        // calculates the indent tabs for the post
        $indent = ($max_depth == 1) ? 1 : count(explode('/', $post->path, $max_depth));
234
        $html .= renderpost($post, $indent, $mode);
235
    }
236
    return $html;
237 238
}

239

240
/*
241
 * Renders a post
242 243
 *
 * @param object $post post object
244
 * @param int $indent indent value
245
 * @param char $mode the indenttion mode. Can be 'no_indent', 'max_indent', 'full_indent'
246 247
 * @return string html output
 */
248
function renderpost($post, $indent, $mode) {
249
    global $moderator, $topic, $groupadmins, $membership, $ineditwindow, $USER;
250
    $reportedaction = ($moderator && !empty($post->reports));
251 252 253 254 255
    $highlightreported = false;

    if ($reportedaction) {
        $highlightreported = true;
        $reportedreason = array();
256 257 258
        $objections = array();
        foreach ($post->reports as $report) {
            $reportedreason['msg_' . strtotime($report->reportedtime)] = array(
259
                'type' => 'html',
260 261
                'value' => get_string('reportedpostdetails', 'interaction.forum', display_default_name($report->reportedby),
                            strftime(get_string('strftimedaydatetime'), strtotime($report->reportedtime)), $report->report),
262
            );
263
            $objections[] = $report->id;
264 265 266 267 268 269
        }
        $post->postnotobjectionableform = pieform(array(
            'name'     => 'postnotobjectionable_' . $post->id,
            'validatecallback' => 'postnotobjectionable_validate',
            'successcallback'  => 'postnotobjectionable_submit',
            'renderer' => 'div',
270
            'class' => 'form-condensed',
271 272 273 274
            'plugintype' => 'interaction',
            'pluginname' => 'forum',
            'autofocus' => false,
            'elements' => array(
275 276 277 278
                'objection' => array(
                    'type' => 'hidden',
                    'value' => implode(',', $objections),
                ),
279 280
                'text' => array(
                    'type' => 'html',
281
                    'class' => 'postnotobjectionable',
282 283 284 285
                    'value' => get_string('postnotobjectionable', 'interaction.forum'),
                ),
                'submit' => array(
                   'type'  => 'submit',
286
                   'class' => 'btn-default',
287 288 289 290 291 292 293 294
                   'value' => get_string('postnotobjectionablesubmit', 'interaction.forum'),
                ),
                'postid' => array(
                    'type' => 'hidden',
                    'value' => $post->id,
                ),
                'details' => array(
                    'type'         => 'fieldset',
295
                    'class' => 'last',
296 297 298 299 300 301 302 303
                    'collapsible'  => true,
                    'collapsed'    => true,
                    'legend'       => get_string('reporteddetails', 'interaction.forum'),
                    'elements'     => $reportedreason,
                ),
            )
        ));
    }
304 305 306
    else if (!empty($post->reports)) {
        foreach ($post->reports as $report) {
            if ($report->reportedby == $USER->get('id')) {
307 308 309 310 311
                $highlightreported = true;
                break;
            }
        }
    }
312

313
    $smarty = smarty_core();
314
    $smarty->assign('LOGGEDIN', $USER->is_logged_in());
315
    $smarty->assign('post', $post);
316
    $smarty->assign('width', 100 - $indent*2);
317 318 319
    $smarty->assign('groupadmins', $groupadmins);
    $smarty->assign('moderator', $moderator);
    $smarty->assign('membership', $membership);
320
    $smarty->assign('chronological', ($mode == 'no_indent') ? true : false);
321
    $smarty->assign('closed', $topic->closed);
322
    $smarty->assign('ineditwindow', $ineditwindow);
323 324
    $smarty->assign('highlightreported', $highlightreported);
    $smarty->assign('reportedaction', $reportedaction);
325 326 327
    return $smarty->fetch('interaction:forum:post.tpl');
}

328 329
function subscribe_topic_validate(Pieform $form, $values) {
    if (!is_logged_in()) {
Aaron Wells's avatar
Aaron Wells committed
330
        // This page is public, so the access denied exception will cause a
331 332 333 334 335
        // login attempt
        throw new AccessDeniedException();
    }
}

Clare Lenihan's avatar
Clare Lenihan committed
336
function subscribe_topic_submit(Pieform $form, $values) {
337
    global $USER;
338
    if ($values['type'] == 'subscribe') {
339 340 341
        insert_record(
            'interaction_forum_subscription_topic',
            (object)array(
342
                'topic' => $values['topic'],
343
                'user'  => $USER->get('id'),
344
                'key'   => PluginInteractionForum::generate_unsubscribe_key(),
345 346 347 348 349 350
            )
        );
    }
    else {
        delete_records(
            'interaction_forum_subscription_topic',
351
            'topic', $values['topic'],
352 353 354
            'user', $USER->get('id')
        );
    }
355
    redirect('/interaction/forum/topic.php?id=' . $values['topic']);
Clare Lenihan's avatar
Clare Lenihan committed
356
}
357

358 359 360 361 362 363 364 365 366
function postnotobjectionable_validate(Pieform $form, $values) {
    global $moderator;
    if (!$moderator) {
        throw new AccessDeniedException(get_string('cantmakenonobjectionable', 'interaction.forum'));
    }
}

function postnotobjectionable_submit(Pieform $form, $values) {
    global $SESSION, $USER, $topicid;
367 368 369 370 371 372 373 374 375 376 377 378 379

    db_begin();

    $objections = explode(',', $values['objection']);

    // Mark records as resolved.
    foreach ($objections as $objection) {
        $todb = new stdClass();
        $todb->resolvedby = $USER->get('id');
        $todb->resolvedtime = db_format_timestamp(time());

        update_record('objectionable', $todb, array('id' => $objection));
    }
380 381 382 383 384 385 386 387 388 389

    // Trigger activity.
    $data = new StdClass();
    $data->postid     = $values['postid'];
    $data->message    = '';
    $data->reporter   = $USER->get('id');
    $data->ctime      = time();
    $data->event      = MAKE_NOT_OBJECTIONABLE;
    activity_occurred('reportpost', $data, 'interaction', 'forum');

390 391
    db_commit();

392 393 394 395 396 397
    $SESSION->add_ok_msg(get_string('postnotobjectionablesuccess', 'interaction.forum'));

    $redirecturl = get_config('wwwroot') . 'interaction/forum/topic.php?id=' . $topicid . '&post=' . $values['postid'];
    redirect($redirecturl);
}

398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453
/* Return the number of posts submitted by a poster
 *
 * @param int $posterid ID of the poster
 * @return int the number of posts
 */
function get_postcount($posterid) {
    return get_string('postsvariable', 'interaction.forum', count_records_sql(
       'SELECT COUNT(id)
        FROM {interaction_forum_post}
        WHERE deleted != 1 AND poster = ?', array($posterid)));
}

/* Return the edit records of a post
 *
 * @param int $postid ID of the post
 * @return array the edit records
 */
function get_postedits($postid) {
    ($postedits = get_records_sql_array(
       'SELECT ' . db_format_tsfield('e.ctime', 'edittime') . ', e.user AS editor, m2.user AS editormoderator
        FROM {interaction_forum_edit} e
        LEFT JOIN {interaction_forum_post} p ON p.id = e.post
        LEFT JOIN {interaction_forum_topic} t ON t.id = p.topic
        LEFT JOIN (
            SELECT m.forum, m.user
            FROM {interaction_forum_moderator} m
            INNER JOIN {usr} u ON (m.user = u.id AND u.deleted = 0)
        ) m2 ON (m2.forum = t.forum AND m2.user = e.user)
        WHERE e.post = ?
        ORDER BY e.ctime',
        array($postid)
    )) || ($postedits = array());
    $editrecs = array();
    foreach ($postedits as $postedit) {
        $postedit->edittime = relative_date(get_string('strftimerecentfullrelative', 'interaction.forum'), get_string('strftimerecentfull'), $postedit->edittime);
        $editrecs[] = array('editormoderator' => $postedit->editormoderator, 'editor' => $postedit->editor, 'edittime' => $postedit->edittime, );
    }
    return $editrecs;
}

/* Check if the poster is the moderator of the forum in which the post is
 *
 * @param int $postid ID of the post
 * @return true if yes, false if else
 */
function is_moderator($postid) {
    return (count_records_sql(
       'SELECT COUNT(m.user)
        FROM {interaction_forum_moderator} m
        INNER JOIN {usr} u ON (m.user = u.id AND u.deleted = 0)
        INNER JOIN {interaction_instance} f ON (m.forum = f.id AND f.deleted != 1)
        INNER JOIN {interaction_forum_topic} t ON (t.forum = f.id)
        INNER JOIN {interaction_forum_post} p ON (p.topic = t.id AND p.poster = m.user)
        WHERE p.id = ?',
        array($postid)) == 1);
}