MyBB Community Forums

Full Version: MyBB Plugin: Thread Viewers & Visit Counter
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
<?php
/**
 * MyBB Plugin: Thread Viewers & Visit Counter
 * Author: CreativeDev
 * Website: https://example.com
 * License: GPLv3
 * Version: 1.1
 * Description: Shows a list of users who viewed the thread along with their personal view counts and profile links.
 */

if (!defined('IN_MYBB')) {
    die('Direct initialization of this file is not allowed.');
}

/**
 * Plugin info (must match filename 'thread_viewers.php')
 */
function thread_viewers_info()
{
    return [
        'name'           => 'Thread Viewers & Visit Counter',
        'description'    => 'Displays users who viewed a thread, their individual view counts, and profile links.',
        'website'        => 'https://example.com',
        'author'         => 'CreativeDev',
        'authorsite'     => 'https://example.com',
        'version'        => '1.1',
        'compatibility'  => '18*'
    ];
}

// Register hooks (global scope)
$plugins->add_hook('install', 'thread_viewers_install');
$plugins->add_hook('uninstall', 'thread_viewers_uninstall');
$plugins->add_hook('showthread_start', 'thread_viewers_track_view');
// Display before the first post (between forum header and posts)
$plugins->add_hook('showthread_start', 'thread_viewers_display');
$plugins->add_hook('template_global_end', 'thread_viewers_css');

/**
 * Install: create table
 */
function thread_viewers_install()
{
    global $db;
    if (!$db->table_exists('thread_viewers')) {
        $collation = $db->build_create_table_collation();
        $db->query(
            "CREATE TABLE `{$db->table_prefix}thread_viewers` (
                `tid` INT(10) UNSIGNED NOT NULL,
                `uid` INT(10) UNSIGNED NOT NULL,
                `username` VARCHAR(120) NOT NULL,
                `views` INT(10) UNSIGNED NOT NULL DEFAULT 1,
                `last_view` INT(10) UNSIGNED NOT NULL,
                PRIMARY KEY (`tid`, `uid`)
            ) ENGINE=MyISAM {$collation};"
        );
    }
}

/**
 * Uninstall: drop table
 */
function thread_viewers_uninstall()
{
    global $db;
    if ($db->table_exists('thread_viewers')) {
        $db->drop_table('thread_viewers');
    }
}

/**
 * Track views per user
 * Auto-creates table if missing
 */
function thread_viewers_track_view()
{
    global $db, $thread, $mybb;
    $tid = intval($thread['tid']);
    $uid = intval($mybb->user['uid']);

    if ($tid <= 0 || $uid <= 0) {
        return;
    }

    if (!$db->table_exists('thread_viewers')) {
        thread_viewers_install();
    }

    $time = TIME_NOW;
    $existing = $db->simple_select('thread_viewers', 'views', "tid={$tid} AND uid={$uid}");
    if ($db->num_rows($existing) > 0) {
        $db->update_query(
            'thread_viewers',
            ['views' => 'views + 1', 'last_view' => $time],
            "tid={$tid} AND uid={$uid}",
            '',
            true
        );
    } else {
        $db->insert_query('thread_viewers', [
            'tid'       => $tid,
            'uid'       => $uid,
            'username'  => $db->escape_string($mybb->user['username']),
            'views'     => 1,
            'last_view' => $time
        ]);
    }
}

/**
 * Display viewers list before posts
 */
function thread_viewers_display()
{
    global $db, $thread, $mybb;
    $tid = intval($thread['tid']);
    if ($tid <= 0 || !$db->table_exists('thread_viewers')) {
        return;
    }

    // Fetch top 10 viewers with UID, username, views
    $query = $db->query(
        "SELECT uid, username, views FROM {$db->table_prefix}thread_viewers WHERE tid={$tid} ORDER BY views DESC, last_view DESC LIMIT 10"
    );
    $viewers = [];
    while ($row = $db->fetch_array($query)) {
        $uid_row = intval($row['uid']);
        $views_text = intval($row['views']) . ' view' . ($row['views'] > 1 ? 's' : '');
        $profile_url = $mybb->settings['bburl'] . "/member.php?action=profile&uid={$uid_row}";
        $viewers[] = '<li><a href="' . $profile_url . '" target="_blank"><strong>' .
                      htmlspecialchars_uni($row['username']) . '</strong></a>: ' . $views_text . '</li>';
    }

    if (!empty($viewers)) {
        echo '<div class="thread-viewers-box">'
           . '<h3>Top Viewers</h3>'
           . '<ul>' . implode('', $viewers) . '</ul>'
           . '</div>';
    }
}

/**
 * Inject CSS
 */
function thread_viewers_css()
{
    echo "<style>
        .thread-viewers-box { margin:20px 0; padding:15px; background:#f9f9f9; border:1px solid #ddd; border-radius:5px; }
        .thread-viewers-box h3 { margin:0 0 10px; font-size:1.1em; }
        .thread-viewers-box ul { list-style:none; margin:0; padding:0; }
        .thread-viewers-box li { margin-bottom:5px; }
        .thread-viewers-box li a { text-decoration:none; }
        .thread-viewers-box li a:hover { text-decoration:underline; }
    </style>";
}

?>
In this line: ['views' => 'views + 1', 'last_view' => $time] it will not interpret views + 1 as an SQL expression — it will treat it as a string literal
you can use this

$db->write_query("UPDATE {$db->table_prefix}thread_viewers SET views = views + 1, last_view = {$time} WHERE tid = {$tid} AND uid = {$uid}");
As a suggestion, ideally, you should hook to showthread_end to set some global variable, then process the view in the pre_output_page hook.

Users could still be blocked at the showthread_start hook, or the user might not be visiting the thread itself, etc.

Regards.