<?php
/* by Tomasz 'Devilshakerz' Mlynski [devilshakerz.com]; Copyright (C) 2014
released under Creative Commons BY-NC-SA 3.0 license: http://creativecommons.org/licenses/by-nc-sa/3.0/ */
// add hooks
$plugins->hooks['global_end'][10]['dvz_stream_global_end'] = array('function' => array('dvz_stream', 'global_end')); // load language file, catch Stream page
$plugins->hooks['index_end'] [10]['dvz_stream_window'] = array('function' => array('dvz_stream', 'window')); // load Stream window to {$dvz_stream} variable
$plugins->hooks['xmlhttp'] [10]['dvz_stream_xmlhttp'] = array('function' => array('dvz_stream', 'xmlhttp')); // xmlhttp.php listening
// register streams
dvz_stream::register('posts', function ($limit, $lastItemId = 0) {
global $db;
$items = array();
require_once MYBB_ROOT.'inc/functions_search.php';
$query_where = null;
$excluded = get_unsearchable_forums();
if ($excluded) {
$query_where .= ' AND p.fid NOT IN ('.$excluded.')';
}
$excluded = get_inactive_forums();
if ($excluded) {
$query_where .= ' AND p.fid NOT IN ('.$excluded.')';
}
$excluded = get_unviewable_forums(true);
if($excluded) {
$query_where .= ' AND p.fid NOT IN ('.$excluded.')';
}
$data = $db->query("
SELECT
p.pid, p.dateline,
u.uid, u.username, u.usergroup, u.displaygroup,
t.tid, t.subject, t.fid,
f.name
FROM
".TABLE_PREFIX."posts p
LEFT JOIN ".TABLE_PREFIX."users u ON p.uid = u.uid
LEFT JOIN ".TABLE_PREFIX."threads t ON p.tid = t.tid
LEFT JOIN ".TABLE_PREFIX."forums f ON p.fid = f.fid
WHERE p.pid > " . (int)$lastItemId . " AND p.replyto != 0 " . $query_where . "
ORDER BY p.pid DESC
LIMIT " . (int)$limit . "
");
while ($row = $db->fetch_array($data)) {
$forum = '<a href="' . $mybb->settings['bburl'] . '/' . get_forum_link($row['fid']) . '">' . $row['name'] . '</a>';
$thread = '<a href="' . get_post_link($row['pid'], $row['tid']) . '#pid' . $row['pid'] . '">' . htmlspecialchars($row['subject']) . '</a></strong>';
$location = $forum . ' → <strong>' . $thread . '</strong>';
$items[] = array(
'id' => $row['pid'],
'date' => $row['dateline'],
'uid' => $row['uid'],
'username' => $row['username'],
'usergroup' => $row['usergroup'],
'displaygroup' => $row['displaygroup'],
'location' => $location,
);
}
return $items;
});
dvz_stream::register('threads', function ($limit, $lastItemId = 0) {
global $db;
$items = array();
require_once MYBB_ROOT.'inc/functions_search.php';
$query_where = null;
$excluded = get_unsearchable_forums();
if ($excluded) {
$query_where .= ' AND t.fid NOT IN ('.$excluded.')';
}
$excluded = get_inactive_forums();
if ($excluded) {
$query_where .= ' AND t.fid NOT IN ('.$excluded.')';
}
$excluded = get_unviewable_forums(true);
if($excluded) {
$query_where .= ' AND t.fid NOT IN ('.$excluded.')';
}
$data = $db->query("
SELECT
t.tid, t.subject, t.fid, t.dateline,
u.uid, u.username, u.usergroup, u.displaygroup,
f.name
FROM
".TABLE_PREFIX."threads t
LEFT JOIN ".TABLE_PREFIX."users u ON t.uid = u.uid
LEFT JOIN ".TABLE_PREFIX."forums f ON t.fid = f.fid
WHERE t.tid > " . (int)$lastItemId . " AND t.visible = 1 " . $query_where . "
ORDER BY t.tid DESC
LIMIT " . (int)$limit . "
");
while ($row = $db->fetch_array($data)) {
$forum = '<a href="' . $mybb->settings['bburl'] . '/' . get_forum_link($row['fid']) . '">' . $row['name'] . '</a>';
$thread = '<a href="' . get_thread_link($row['tid']) . '">' . htmlspecialchars($row['subject']) . '</a></strong>';
$items[] = array(
'id' => $row['tid'],
'date' => $row['dateline'],
'uid' => $row['uid'],
'username' => $row['username'],
'usergroup' => $row['usergroup'],
'displaygroup' => $row['displaygroup'],
'location' => $forum,
'item' => $thread,
);
}
return $items;
});
dvz_stream::register('users', function ($limit, $lastItemId = 0) {
global $db;
$items = array();
$data = $db->query("
SELECT
u.uid, u.regdate, u.username, u.usergroup, u.displaygroup
FROM
".TABLE_PREFIX."users u
WHERE u.uid > " . (int)$lastItemId . "
ORDER BY u.uid DESC
LIMIT " . (int)$limit . "
");
while ($row = $db->fetch_array($data)) {
$items[] = array(
'id' => $row['uid'],
'date' => $row['regdate'],
'uid' => $row['uid'],
'username' => $row['username'],
'usergroup' => $row['usergroup'],
'displaygroup' => $row['displaygroup'],
);
}
return $items;
}, true);
class dvz_stream {
// internal cache
static $streamsMemory = false;
static $userGroups = false;
static $streams = array();
// hooks
function global_end () {
global $mybb, $lang;
$lang->load('dvz_stream');
if ($mybb->input['action'] == 'stream') {
return dvz_stream::page();
}
}
function xmlhttp () {
global $mybb, $lang, $charset;
if (
$mybb->input['action'] == 'dvz_st_get'
&& (dvz_stream::access_update() || dvz_stream::access_view('page'))
) {
$lang->load("dvz_stream");
$request = is_array($mybb->input['streams'])
? $mybb->input['streams']
: array()
;
// combined items list with streams information
$packet = dvz_stream::get_data($request, $mybb->input['limit']);
$html = dvz_stream::getHtml($packet['items']);
header('Content-type: text/plain; charset='.$charset);
header('Cache-Control: no-store'); // Chrome request caching issue
echo json_encode(array(
'streams' => $packet['streams'],
'html' => $html,
));
}
}
// immediate output
static function window () {
global $templates, $dvz_stream, $lang, $mybb, $theme;
// MyBB template
$dvz_stream = null;
// dvz_stream template
$javascript = null;
if (dvz_stream::access_view('window')) {
$packet = dvz_stream::get_data(false, $mybb->settings['dvz_st_entries_window']);
$html = dvz_stream::getHtml($packet['items']);
$pagelink = dvz_stream::access_view('page')
? '<p class="right"><a href="' . $mybb->settings['bburl'] . '/index.php?action=stream">« ' . $lang->dvz_st_pagelink . '</a></p>'
: null
;
if ((float)$mybb->settings['dvz_st_interval_window'] > 0 && dvz_stream::access_update()) {
$javascript = '
<script type="text/javascript" src="' . $mybb->settings['bburl'] . '/jscripts/dvz_stream.js"></script>
<script>
' . dvz_stream::js('window', $packet['streams']) . '
</script>';
} else {
$javascript = null;
}
eval('$dvz_stream = "' . $templates->get('dvz_stream_window') . '";');
}
}
static function page () {
global $mybb, $templates, $lang, $theme, $footer, $headerinclude, $header, $charset;
if (!dvz_stream::access_view('page')) return false;
header('Content-type: text/html; charset='.$charset);
add_breadcrumb($lang->dvz_st_title, "index.php?action=stream");
$packet = dvz_stream::get_data(false, $mybb->settings['dvz_st_entries_page']);
$html = dvz_stream::getHtml($packet['items']);
$javascript = '
<script type="text/javascript" src="' . $mybb->settings['bburl'] . '/jscripts/dvz_stream.js"></script>
<script>
' . dvz_stream::js('page', $packet['streams']) . '
</script>';
// streams list
$streams = null;
foreach (dvz_stream::$streams as $streamId => $function) {
$streams .= '<label data-stream="' . $streamId . '" class="active"><input type="checkbox" checked="checked" autocomplete="off"> ' . $lang->{'dvz_st_stream_'.$streamId} . '</label>';
}
eval('$content = "'.$templates->get("dvz_stream_page").'";');
output_page($content);
exit;
}
static function render_entry ($data) {
global $mybb, $lang;
$event = $lang->{'dvz_st_event_'.$data['stream']};
$date = my_date($mybb->settings['dateformat'].' '.$mybb->settings['timeformat'], $data['date']);
$user = isset($data['uid'])
? ' — <a href="' . get_profile_link($data['uid']) . '">' . format_name($data['username'], $data['usergroup'], $data['displaygroup']) . '</a>'
: null
;
$location = isset($data['location'])
? ' (' . $data['location'] . ')'
: null
;
$item = isset($data['item']) ? ': <span class="item">' . $data['item'] . '</span>' : null;
return '
<div class="entry" data-stream="' . $data['stream'] . '">
<span class="event">' . $event . '</span> <span class="date">' . $date . '</span>' . $user . $location . $item . '
</div>';
}
// streams handling
static function get_data ($request = false, $limit = false) {
global $mybb;
$streamsData = array();
$items = array();
// get all streams
if ($request === false) {
$request = array_fill_keys(array_keys(dvz_stream::$streams), 0);
}
// limit for total returned items
$hardLimit = max($mybb->settings['dvz_st_entries_window'], $mybb->settings['dvz_st_entries_page']);
$limit = $limit
? min($limit, $hardLimit)
: $hardLimit
;
foreach ($request as $streamId => $lastItemId) {
if ( isset(dvz_stream::$streams[ $streamId ]) ) {
$stream = dvz_stream::get_stream($streamId, $limit, $lastItemId);
$items = array_merge($items, $stream);
// last entry ID
if (!isset( $streamsData[ $streamId ] )) {
$streamsData[ $streamId ] = $stream ? reset($stream)['id'] : 0;
}
}
}
// sort descending
usort($items, function($a, $b){ return $b['date'] - $a['date']; });
// limit items
$items = array_slice($items, 0, $limit);
return array(
'streams' => $streamsData,
'items' => $items,
);
}
static function get_stream ($streamId, $limit, $lastItemId = false) {
global $mybb;
if (
$mybb->settings['dvz_st_cache']
&& dvz_stream::$streams[ $streamId ]['cacheable']
) {
$stream = dvz_stream::get_stream_cached($streamId, $limit, $lastItemId);
} else {
$stream = dvz_stream::get_stream_fresh($streamId, $limit, $lastItemId);
}
return $stream;
}
static function get_stream_fresh ($streamId, $limit, $lastItemId = false) {
global $mybb;
// get data from stream functions
$f = dvz_stream::$streams[ $streamId ]['function'];
$streamItems = $f($limit, $lastItemId);
// assign streamId to each item
array_walk($streamItems, function(&$item) use ($streamId){ $item['stream'] = $streamId; });
return $streamItems;
}
static function get_stream_cached ($streamId, $limit, $lastItemId = false) {
global $mybb;
// get stream from memory or cache
if (isset( dvz_stream::$streamsMemory[ $streamId ] )) {
$stream = dvz_stream::$streamsMemory[ $streamId ];
} else {
$streams = dvz_stream::get_cache($limit);
dvz_stream::$streamsMemory = $streams;
$stream = $streams[ $streamId ];
}
$filteredStream = array();
// filter stream
foreach ($stream as $item) {
if ($item['id'] <= $lastItemId) break;
$filteredStream[] = $item;
}
// limit items
$filteredStream = array_slice($filteredStream, 0, $limit);
return $filteredStream;
}
static function get_cache ($limit) {
global $cache;
$cached = $cache->read('dvz_stream');
if ($cached && time() - $cached['time'] <= $mybb->settings['dvz_st_cache']) {
// cache exists and considered up to date
return $cached['streams'];
} else {
// update cache
$streams = array();
foreach (dvz_stream::$streams as $streamId => $stream) {
if ($stream['cacheable']) {
$streams[ $streamId ] = dvz_stream::get_stream_fresh($streamId, $limit);
}
}
$cache->update('dvz_stream', array(
'time' => time(),
'streams' => $streams,
));
return $streams;
}
}
// internal
static function register ($streamId, $function, $cacheable = false) {
dvz_stream::$streams[ $streamId ] = array(
'function' => $function,
'cacheable' => $cacheable,
);
}
static function js ($location, $ownStreams = false) {
global $mybb;
$interval = dvz_stream::access_update()
? (float)$mybb->settings['dvz_st_interval_' . $location]
: 0
;
$items = (int)$mybb->settings['dvz_st_entries_' . $location];
$streams = $ownStreams
? json_encode($ownStreams)
: json_encode(array_keys(dvz_stream::$streams))
;
$js = null;
$js .= 'dvz_stream.interval = ' . $interval . ';' . PHP_EOL;
$js .= 'dvz_stream.items = ' . $items . ';' . PHP_EOL;
$js .= 'dvz_stream.streams = ' . $streams . ';' . PHP_EOL;
if ($ownStreams) {
$js .= 'dvz_stream.ownStreams = true;' . PHP_EOL;
$js .= 'setTimeout(\'dvz_stream.loop()\', ' . ($interval * 1000) . ');' . PHP_EOL;
}
// lazyLoad
if ($mybb->settings['dvz_st_lazyload']) {
$js .= 'dvz_stream.lazyMode = \'' . $mybb->settings['dvz_st_lazyload'] . '\';' . PHP_EOL;
$js .= 'jQuery(window).bind(\'scroll resize\', dvz_stream.checkVisibility);' . PHP_EOL;
}
return $js;
}
static function getHtml ($entries) {
$html = null;
foreach ($entries as $entry) {
$html .= dvz_stream::render_entry($entry);
}
return $html;
}
// permissions
static function access_view ($location) {
global $mybb;
$array = dvz_stream::settings_get_csv('groups_view_' . $location);
return (
empty($array) ||
dvz_stream::member_of($array)
);
}
static function access_update () {
global $mybb;
$array = dvz_stream::settings_get_csv('groups_update');
return (
empty($array) ||
dvz_stream::member_of($array)
);
}
// core library
static function member_of ($groupsArray) {
global $mybb;
if (dvz_stream::$userGroups == false) {
dvz_stream::$userGroups = explode(',', $mybb->user['additionalgroups']);
dvz_stream::$userGroups[] = $mybb->user['usergroup'];
}
return array_intersect(dvz_stream::$userGroups, $groupsArray);
}
static function settings_get_csv ($name) {
global $mybb;
$items = explode(',', $mybb->settings['dvz_st_'.$name]);
if (count($items) == 1 && $items[0] == '') {
return array();
} else
return $items;
}
}
// MyBB handling
function dvz_stream_info () {
return array(
'name' => 'DVZ Stream',
'description' => 'Real-time stream of new content appearing on the forum.',
'website' => 'http://devilshakerz.com/',
'author' => 'Tomasz \'Devilshakerz\' Mlynski',
'authorsite' => 'http://devilshakerz.com/',
'version' => '1.1',
'guid' => 'd34bf2410fc76b73c43b3380c2c6038a',
'compatibility' => '18*',
);
}
function dvz_stream_install () {
global $db;
// settings
$db->write_query("INSERT INTO `".TABLE_PREFIX."settinggroups` VALUES (NULL, 'dvz_stream', 'DVZ Stream', 'Settings for DVZ Stream.', 1, 0)");
$sgID = $db->insert_id();
$db->write_query("INSERT INTO `".TABLE_PREFIX."settings` VALUES
(NULL, 'dvz_st_entries_window', 'Number of entries in Stream window', '', 'text', '6', 1, $sgID, 0),
(NULL, 'dvz_st_entries_page', 'Number of entries on Stream page', '', 'text', '20', 2, $sgID, 0),
(NULL, 'dvz_st_interval_window', 'Refresh interval: Window (seconds)', 'Interval between AJAX Stream update requests (lower values provide better synchronization but cause higher server load). Set 0 to disable the auto-refreshing feature.', 'text', '5', 3, $sgID, 0),
(NULL, 'dvz_st_interval_page', 'Refresh interval: Stream page (seconds)', '', 'text', '5', 4, $sgID, 0),
(NULL, 'dvz_st_lazyload', 'Lazy load', 'Start loading data only when the Stream window is actually being displayed on the screen (the page is scrolled to the Stream window position).', 'select
off=Disabled
start=Check if on display to start
always=Always check if on display to refresh', 'off', 5, $sgID, 0),
(NULL, 'dvz_st_cache', 'Caching time (seconds)', 'If not set to <i>0</i>, certain streams will be cached for specified amount of time. Note that not every stream can be cached.', 'text', '0', 6, $sgID, 0),
(NULL, 'dvz_st_groups_view_window', 'Group permissions: View window', 'Comma-separated list of user groups that Stream is visible to. Leave empty to let everyone view (including guests).', 'text', '', 7, $sgID, 0),
(NULL, 'dvz_st_groups_update', 'Group permissions: Window auto-update', 'Comma-separated list of user groups that Stream will be automatically updating for in the Stream window. Leave empty to let update for everyone.', 'text', '', 8, $sgID, 0),
(NULL, 'dvz_st_groups_view_page', 'Group permissions: View page', 'Comma-separated list of user groups that auto-updating Stream will be available on the Stream page. Leave empty to let everyone view the Stream page.', 'text', '', 9, $sgID, 0)
");
rebuild_settings();
// templates
$template_window = '
<div id="stream">
<table border="0" cellspacing="{$theme[\'borderwidth\']}" cellpadding="{$theme[\'tablespace\']}" class="tborder">
<tr>
<td class="thead">
<strong>{$lang->dvz_st_title}</strong>
{$pagelink}
</td>
</tr>
<tr>
<td class="trow1">
<div class="data">{$html}</div>
</td>
</tr>
</table>
{$javascript}
</div>
<br />';
$template_page = '<html>
<head>
<title>{$lang->dvz_st_title}</title>
{$headerinclude}
</head>
<body>
{$header}
<br />
<div id="stream">
<table border="0" cellspacing="{$theme[\'borderwidth\']}" cellpadding="{$theme[\'tablespace\']}" class="tborder">
<tr>
<td class="thead"><strong>{$lang->dvz_st_title}</strong></td>
</tr>
<tr>
<td class="tcat">
<span class="smalltext"><strong>{$lang->dvz_st_select}</strong></span>
<div class="streams">{$streams}</div>
</td>
</tr>
<tr>
<td class="trow1">
<div class="data">{$html}</div>
</td>
</tr>
</table>
{$javascript}
</div>
<br />
{$footer}
</body>
</html>';
$db->write_query("INSERT INTO `".TABLE_PREFIX."templates` VALUES (NULL, 'dvz_stream_window', '".$db->escape_string($template_window)."', '-1', '1', '', '".time()."')");
$db->write_query("INSERT INTO `".TABLE_PREFIX."templates` VALUES (NULL, 'dvz_stream_page', '".$db->escape_string($template_page)."', '-1', '1', '', '".time()."')");
}
function dvz_stream_uninstall () {
global $db;
$groupID = $db->fetch_field(
$db->simple_select('settinggroups', 'gid', "name='dvz_stream'"),
'gid'
);
// delete settings
$db->delete_query('settinggroups', "name='dvz_stream'");
$db->delete_query('settings', 'gid='.$groupID);
// delete templates
$db->query("DELETE FROM ".TABLE_PREFIX."templates WHERE title IN('dvz_stream', 'dvz_stream_window', 'dvz_stream_page')");
// delete datacache entry
$db->delete_query('datacache', "title='dvz_stream'");
}
function dvz_stream_is_installed () {
global $db;
$query = $db->simple_select('settinggroups', '*', "name='dvz_stream'");
return $db->num_rows($query);
}
?>