· 10 years ago · Aug 30, 2016, 01:44 AM
1<?php
2/* by Tomasz 'Devilshakerz' Mlynski [devilshakerz.com]; Copyright (C) 2014-2016
3 released under Creative Commons BY-NC-SA 4.0 license: https://creativecommons.org/licenses/by-nc-sa/4.0/ */
4
5$plugins->add_hook('global_start', ['dvz_shoutbox', 'global_start']); // cache shoutbox templates
6$plugins->add_hook('global_end', ['dvz_shoutbox', 'global_end']); // catch archive page
7$plugins->add_hook('xmlhttp', ['dvz_shoutbox', 'xmlhttp']); // xmlhttp.php listening
8$plugins->add_hook('index_end', ['dvz_shoutbox', 'load_window']); // load Shoutbox window to {$dvz_shoutbox} variable
9
10$plugins->add_hook('admin_config_settings_change', ['dvz_shoutbox', 'admin_config_settings_change']);
11$plugins->add_hook('admin_user_users_merge_commit', ['dvz_shoutbox', 'user_merge']);
12
13$plugins->add_hook('fetch_wol_activity_end', ['dvz_shoutbox', 'activity']); // catch activity
14$plugins->add_hook('build_friendly_wol_location_end', ['dvz_shoutbox', 'activity_translate']); // translate activity
15
16$plugins->add_hook('misc_clearcookies', ['dvz_shoutbox', 'clearcookies']);
17
18function dvz_shoutbox_info()
19{
20 return [
21 'name' => 'DVZ Shoutbox',
22 'description' => 'Lightweight AJAX chat.',
23 'website' => 'https://devilshakerz.com/',
24 'author' => 'Tomasz \'Devilshakerz\' Mlynski',
25 'authorsite' => 'https://devilshakerz.com/',
26 'version' => '2.3.1',
27 'codename' => 'dvz_shoutbox',
28 'compatibility' => '18*',
29 ];
30}
31
32function dvz_shoutbox_install()
33{
34 global $mybb, $db;
35
36 $mybb->binary_fields['dvz_shoutbox'] = ['ipaddress' => true];
37
38 // table
39 switch ($db->type) {
40 case 'pgsql':
41 $db->write_query("
42 CREATE TABLE IF NOT EXISTS " . TABLE_PREFIX . "dvz_shoutbox (
43 id serial,
44 uid int NOT NULL,
45 text text NULL,
46 date int NOT NULL,
47 modified int NULL DEFAULT NULL,
48 ipaddress bytea NOT NULL,
49 PRIMARY KEY (id)
50 )
51 ");
52 break;
53 case 'sqlite':
54 $db->write_query("
55 CREATE TABLE IF NOT EXISTS " . TABLE_PREFIX . "dvz_shoutbox (
56 id integer primary key,
57 uid integer NOT NULL,
58 text text NULL,
59 date integer NOT NULL,
60 modified integer NULL DEFAULT NULL,
61 ipaddress bytea NOT NULL
62 )
63 ");
64 break;
65 default:
66 $query = $db->query("SELECT SUPPORT FROM INFORMATION_SCHEMA.ENGINES WHERE ENGINE = 'InnoDB'");
67 $innodbSupport = $db->num_rows($query) && in_array($db->fetch_field($query, 'SUPPORT'), ['DEFAULT', 'YES']);
68
69 $db->write_query("
70 CREATE TABLE IF NOT EXISTS `" . TABLE_PREFIX . "dvz_shoutbox` (
71 `id` int(11) NOT NULL auto_increment,
72 `uid` int(11) NOT NULL,
73 `text` text NULL,
74 `date` int(11) NOT NULL,
75 `modified` int(11) NULL DEFAULT NULL,
76 `ipaddress` varbinary(16) NOT NULL,
77 PRIMARY KEY (`id`)
78 ) " . ($innodbSupport ? "ENGINE=InnoDB" : null) . " " . $db->build_create_table_collation() . "
79 ");
80 break;
81 }
82
83 // example shout
84 $db->insert_query('dvz_shoutbox', [
85 'uid' => 1,
86 'text' => 'DVZ Shoutbox!',
87 'date' => TIME_NOW,
88 'ipaddress' => $db->escape_binary( my_inet_pton('127.0.0.1') ),
89 ]);
90
91 // settings
92 $settingGroupId = $db->insert_query('settinggroups', [
93 'name' => 'dvz_shoutbox',
94 'title' => 'DVZ Shoutbox',
95 'description' => 'Settings for DVZ Shoutbox.',
96 ]);
97
98 $settings = [
99 [
100 'name' => 'dvz_sb_num',
101 'title' => 'Shouts to display',
102 'description' => 'Number of shouts to be displayed in the Shoutbox window.',
103 'optionscode' => 'numeric',
104 'value' => '20',
105 ],
106 [
107 'name' => 'dvz_sb_num_archive',
108 'title' => 'Shouts to display on archive',
109 'description' => 'Number of shouts to be displayed per page on Archive view.',
110 'optionscode' => 'numeric',
111 'value' => '20',
112 ],
113 [
114 'name' => 'dvz_sb_reversed',
115 'title' => 'Reversed order',
116 'description' => 'Reverses the shouts display order in the Shoutbox window so that new ones appear on the bottom. You may also want to move the <b>{$panel}</b> variable below the window in the <i>dvz_shoutbox</i> template.',
117 'optionscode' => 'yesno',
118 'value' => '0',
119 ],
120 [
121 'name' => 'dvz_sb_height',
122 'title' => 'Shoutbox height',
123 'description' => 'Height of the Shoutbox window in pixels.',
124 'optionscode' => 'numeric',
125 'value' => '160',
126 ],
127 [
128 'name' => 'dvz_sb_dateformat',
129 'title' => 'Date format',
130 'description' => 'Format of the date displayed. This format uses the PHP\'s <a href="https://secure.php.net/manual/en/function.date.php#refsect1-function.date-parameters">date() function syntax</a>.',
131 'optionscode' => 'text',
132 'value' => 'd M H:i',
133 ],
134 [
135 'name' => 'dvz_sb_maxlength',
136 'title' => 'Maximum message length',
137 'description' => 'Set 0 to disable the limit.',
138 'optionscode' => 'numeric',
139 'value' => '0',
140 ],
141 [
142 'name' => 'dvz_sb_mycode',
143 'title' => 'Parse MyCode',
144 'description' => '',
145 'optionscode' => 'yesno',
146 'value' => '1',
147 ],
148 [
149 'name' => 'dvz_sb_smilies',
150 'title' => 'Parse smilies',
151 'description' => '',
152 'optionscode' => 'yesno',
153 'value' => '1',
154 ],
155 [
156 'name' => 'dvz_sb_interval',
157 'title' => 'Refresh interval',
158 'description' => 'Number of seconds between Shoutbox updates (lower values provide better synchronization but cause higher server load). Set 0 to disable the auto-refreshing feature.',
159 'optionscode' => 'numeric',
160 'value' => '5',
161 ],
162 [
163 'name' => 'dvz_sb_away',
164 'title' => 'Away mode',
165 'description' => 'Number of seconds after last user action (e.g. click) after which shoutbox will be minimized to prevent unnecessary usage of server resources. Set 0 to disable this feature.',
166 'optionscode' => 'numeric',
167 'value' => '600',
168 ],
169 [
170 'name' => 'dvz_sb_antiflood',
171 'title' => 'Anti-flood interval',
172 'description' => 'Forces a minimum number of seconds to last between user\'s shouts (this does not apply to Shoutbox moderators).',
173 'optionscode' => 'numeric',
174 'value' => '5',
175 ],
176 [
177 'name' => 'dvz_sb_sync',
178 'title' => 'Moderation synchronization',
179 'description' => 'Applies moderation actions without refreshing the Shoutbox window page.',
180 'optionscode' => 'onoff',
181 'value' => '1',
182 ],
183 [
184 'name' => 'dvz_sb_mark_unread',
185 'title' => 'Mark unread messages',
186 'description' => 'Marks messages that appeared after user\'s last visit.',
187 'optionscode' => 'onoff',
188 'value' => '1',
189 ],
190 [
191 'name' => 'dvz_sb_lazyload',
192 'title' => 'Lazy load',
193 'description' => 'Start loading data only when the Shoutbox window is actually being displayed on the screen (the page is scrolled to the Shoutbox position).',
194 'optionscode' => 'select
195off=Disabled
196start=Check if on display to start
197always=Always check if on display to refresh',
198 'value' => 'off',
199 ],
200 [
201 'name' => 'dvz_sb_status',
202 'title' => 'Shoutbox default status',
203 'description' => 'Choose whether Shoutbox window should be expanded or collapsed by default.',
204 'optionscode' => 'onoff',
205 'value' => '1',
206 ],
207 [
208 'name' => 'dvz_sb_minposts',
209 'title' => 'Minimum posts required to shout',
210 'description' => 'Set 0 to allow everyone.',
211 'optionscode' => 'numeric',
212 'value' => '0',
213 ],
214 [
215 'name' => 'dvz_sb_groups_view',
216 'title' => 'Group permissions: View',
217 'description' => 'User groups that can view Shoutbox.',
218 'optionscode' => 'groupselect',
219 'value' => '-1',
220 ],
221 [
222 'name' => 'dvz_sb_groups_refresh',
223 'title' => 'Group permissions: Auto-refresh',
224 'description' => 'User groups that shoutbox will be refreshing for.',
225 'optionscode' => 'groupselect',
226 'value' => '-1',
227 ],
228 [
229 'name' => 'dvz_sb_groups_shout',
230 'title' => 'Group permissions: Shout',
231 'description' => 'User groups that can post shouts in Shoutbox (logged in users only).',
232 'optionscode' => 'groupselect',
233 'value' => '-1',
234 ],
235 [
236 'name' => 'dvz_sb_groups_recall',
237 'title' => 'Group permissions: Scroll back to show past shouts',
238 'description' => 'User groups that shoutbox will load previous posts when scrolling back for.',
239 'optionscode' => 'groupselect',
240 'value' => '-1',
241 ],
242 [
243 'name' => 'dvz_sb_groups_mod',
244 'title' => 'Group permissions: Moderation',
245 'description' => 'User groups that can moderate the Shoutbox (edit and delete shouts).',
246 'optionscode' => 'groupselect',
247 'value' => '',
248 ],
249 [
250 'name' => 'dvz_sb_groups_mod_own',
251 'title' => 'Group permissions: Moderation of own shouts',
252 'description' => 'Users groups whose members can edit and delete their own shouts.',
253 'optionscode' => 'groupselect',
254 'value' => '',
255 ],
256 [
257 'name' => 'dvz_sb_supermods',
258 'title' => 'Super moderators are Shoutbox moderators',
259 'description' => 'Automatically allow forum super moderators to moderate Shoutbox as well.',
260 'optionscode' => 'yesno',
261 'value' => '1',
262 ],
263 [
264 'name' => 'dvz_sb_blocked_users',
265 'title' => 'Banned users',
266 'description' => 'Comma-separated list of user IDs that are banned from posting messages.',
267 'optionscode' => 'textarea',
268 'value' => '',
269 ],
270 ];
271
272 $i = 1;
273
274 foreach ($settings as &$row) {
275 $row['gid'] = $settingGroupId;
276 $row['title'] = $db->escape_string($row['title']);
277 $row['description'] = $db->escape_string($row['description']);
278 $row['disporder'] = $i++;
279 }
280
281 $db->insert_query_multiple('settings', $settings);
282
283 rebuild_settings();
284
285 // templates
286 $templates = [
287 'dvz_shoutbox_panel' => '<div class="panel">
288<form>
289<input type="text" class="text" placeholder="{$lang->dvz_sb_default}" maxlength="{$maxlength}" autocomplete="off" />
290<input type="submit" style="display:none" />
291</form>
292</div>',
293
294 'dvz_shoutbox' => '<div id="shoutbox" class="front{$classes}">
295
296<div class="head">
297<strong>{$lang->dvz_sb_shoutbox}</strong>
298<p class="right"><a href="{$mybb->settings[\'bburl\']}/index.php?action=shoutbox_archive">« {$lang->dvz_sb_archivelink}</a></p>
299</div>
300
301<div class="body">
302
303{$panel}
304
305<div class="window" style="height:{$mybb->settings[\'dvz_sb_height\']}px">
306<div class="data">
307{$html}
308</div>
309</div>
310
311</div>
312
313<script type="text/javascript" src="{$mybb->settings[\'bburl\']}/jscripts/dvz_shoutbox.js"></script>
314{$javascript}
315
316</div>',
317
318 'dvz_shoutbox_archive' => '<html>
319<head>
320<title>{$lang->dvz_sb_archive}</title>
321{$headerinclude}
322</head>
323<body>
324{$header}
325
326<script type="text/javascript" src="{$mybb->settings[\'bburl\']}/jscripts/dvz_shoutbox.js"></script>
327{$javascript}
328
329{$modoptions}
330
331{$multipage}
332
333<br />
334
335<div id="shoutbox">
336
337<div class="head">
338<strong>{$lang->dvz_sb_archive}</strong>
339{$last_read_link}
340</div>
341
342<div class="data">
343{$archive}
344</div>
345</div>
346
347<br />
348
349{$multipage}
350
351{$footer}
352</body>
353</html>',
354
355 'dvz_shoutbox_last_read_link' => '<p class="right"><a href="{$last_read_url}">{$lang->dvz_sb_last_read_link}</a> | <a href="{$unmark_all_url}">{$lang->dvz_sb_last_read_unmark_all}</a></p>',
356
357 'dvz_shoutbox_archive_modoptions' => '<table border="0" cellspacing="{$theme[\'borderwidth\']}" cellpadding="{$theme[\'tablespace\']}" class="tborder">
358<tr><td class="thead" colspan="2"><strong>{$lang->dvz_sb_mod}</strong></td></tr>
359<tr><td class="tcat">{$lang->dvz_sb_mod_banlist}</td><td class="tcat">{$lang->dvz_sb_mod_clear}</td></tr>
360<tr>
361<td class="trow1">
362<form action="" method="post">
363<input type="text" class="textbox" style="width:80%" name="banlist" value="{$blocked_users}" />
364<input type="hidden" name="postkey" value="{$mybb->post_code}" />
365<input type="submit" class="button" value="{$lang->dvz_sb_mod_banlist_button}" />
366</form>
367</td>
368<td class="trow1">
369<form action="" method="post">
370<select name="days">
371<option value="2">2 {$lang->days}</option>
372<option value="7">7 {$lang->days}</option>
373<option value="30">30 {$lang->days}</option>
374<option value="90">90 {$lang->days}</option>
375<option value="all">* {$lang->dvz_sb_mod_clear_all} *</option>
376</select>
377<input type="hidden" name="postkey" value="{$mybb->post_code}" />
378<input type="submit" class="button" value="{$lang->dvz_sb_mod_clear_button}" />
379</form>
380</td>
381</tr>
382</table>
383<br />',
384 ];
385
386 $data = [];
387
388 foreach ($templates as $name => $content) {
389 $data[] = [
390 'title' => $name,
391 'template' => $db->escape_string($content),
392 'sid' => -1,
393 'version' => 1,
394 'status' => '',
395 'dateline' => TIME_NOW,
396 ];
397 }
398
399 $db->insert_query_multiple('templates', $data);
400}
401
402function dvz_shoutbox_uninstall()
403{
404 global $db;
405
406 $settingGroupId = $db->fetch_field(
407 $db->simple_select('settinggroups', 'gid', "name='dvz_shoutbox'"),
408 'gid'
409 );
410
411 // delete settings
412 $db->delete_query('settinggroups', 'gid=' . (int)$settingGroupId);
413 $db->delete_query('settings', 'gid=' . (int)$settingGroupId);
414
415 rebuild_settings();
416
417 // delete templates
418 $db->delete_query('templates', "title IN('dvz_shoutbox', 'dvz_shoutbox_panel', 'dvz_shoutbox_archive', 'dvz_shoutbox_archive_modoptions')");
419
420 // delete data
421 if ($db->type == 'sqlite') {
422 $db->close_cursors();
423 }
424 $db->drop_table('dvz_shoutbox');
425}
426
427function dvz_shoutbox_is_installed()
428{
429 global $mybb;
430 return $mybb->settings['dvz_sb_num'] !== null;
431}
432
433
434class dvz_shoutbox
435{
436
437 // hooks
438 static function global_start()
439 {
440 global $mybb, $templatelist;
441
442 $mybb->binary_fields['dvz_shoutbox'] = ['ipaddress' => true];
443
444 if (defined('THIS_SCRIPT') && THIS_SCRIPT == 'index.php' && self::access_view()) {
445
446 if (!empty($templatelist)) {
447 $templatelist .= ',';
448 }
449
450 if ($mybb->get_input('action') == 'shoutbox_archive') {
451 // archive templates
452
453 $templatelist .= 'dvz_shoutbox_archive,dvz_shoutbox_last_read_link,multipage,multipage_page,multipage_page_current,multipage_prevpage,multipage_nextpage,multipage_start,multipage_end,multipage_jump_page';
454
455 if (self::access_mod()) {
456 $templatelist .= ',dvz_shoutbox_archive_modoptions';
457 }
458
459 } else {
460 // index templates
461 $templatelist .= 'dvz_shoutbox,dvz_shoutbox_panel';
462 }
463
464 }
465 }
466
467 static function global_end()
468 {
469 global $mybb;
470
471 if ($mybb->get_input('action') == 'shoutbox_archive' && self::access_view()) {
472 return self::show_archive();
473 }
474 }
475
476 static function xmlhttp()
477 {
478 global $mybb, $db, $charset, $plugins;
479
480 $mybb->binary_fields['dvz_shoutbox'] = ['ipaddress' => true];
481
482 switch ($mybb->get_input('action')) {
483
484 case 'dvz_sb_get_updates':
485
486 $permissions = (
487 self::access_view() &&
488 self::access_refresh()
489 );
490
491 $handler = function () use ($mybb, $db, $plugins) {
492
493 $syncConditions = $mybb->settings['dvz_sb_sync']
494 ? "OR (s.modified >= " . (time() - $mybb->settings['dvz_sb_interval']) . " AND s.id BETWEEN " . abs($mybb->get_input('first', MyBB::INPUT_INT)) . " AND " . abs($mybb->get_input('last', MyBB::INPUT_INT)) . ")"
495 : null
496 ;
497
498 $data = self::get_multiple("WHERE (s.id > " . abs($mybb->get_input('last', MyBB::INPUT_INT)) . " AND s.text IS NOT NULL) " . $syncConditions . " ORDER BY s.id DESC LIMIT " . self::async_limit());
499
500 $html = null; // JS-handled empty response
501 $sync = [];
502 $firstId = 0;
503 $lastId = 0;
504
505 while ($row = $db->fetch_array($data)) {
506
507 if ($row['id'] <= $mybb->get_input('last', MyBB::INPUT_INT)) {
508 // sync update
509
510 $sync[ $row['id'] ] = $row['text'] === null
511 ? null
512 : self::parse($row['text'], $row['username'])
513 ;
514
515 } else {
516 // new shout
517
518 $firstId = $row['id'];
519
520 if ($lastId == 0) {
521 $lastId = $row['id'];
522 }
523
524 $shout = self::render_shout($row);
525
526 $html = $mybb->settings['dvz_sb_reversed']
527 ? $shout . $html
528 : $html . $shout
529 ;
530
531 }
532
533 }
534
535 if ($html != null || !empty($sync)) {
536
537 $response = [];
538
539 if ($html != null) {
540
541 $response['html'] = $html;
542 $response['last'] = $lastId;
543
544 if ($mybb->get_input('first', MyBB::INPUT_INT) == 0) {
545 $response['first'] = $firstId;
546 }
547
548 }
549
550 if (!empty($sync)) {
551 $response['sync'] = $sync;
552 }
553
554 $plugins->run_hooks('dvz_shoutbox_get_updates', $response);
555
556 echo json_encode($response);
557
558 }
559 };
560
561 break;
562
563 case 'dvz_sb_recall':
564
565 $permissions = (
566 self::access_view() &&
567 self::access_refresh() &&
568 self::access_recall()
569 );
570
571 $handler = function () use ($mybb, $db, $plugins) {
572
573 $data = self::get_multiple("WHERE s.id < " . abs($mybb->get_input('first', MyBB::INPUT_INT)) . " AND s.text IS NOT NULL ORDER BY s.id DESC LIMIT " . abs((int)$mybb->settings['dvz_sb_num']));
574
575 $response = [];
576
577 $html = null; // JS-handled empty response
578 $firstId = 0;
579
580 while ($row = $db->fetch_array($data)) {
581
582 $firstId = $row['id'];
583
584 $shout = self::render_shout($row);
585
586 $html = $mybb->settings['dvz_sb_reversed']
587 ? $shout . $html
588 : $html . $shout
589 ;
590 }
591
592 if ($html != null) {
593 $response['html'] = $html;
594 }
595
596 if ($db->num_rows($data) < abs((int)$mybb->settings['dvz_sb_num'])) {
597 $response['end'] = 1;
598 }
599
600 if ($response) {
601 $response['first'] = $firstId;
602 }
603
604 $plugins->run_hooks('dvz_shoutbox_recall', $response);
605
606 echo json_encode($response);
607
608 };
609
610 break;
611
612 case 'dvz_sb_shout':
613
614 $permissions = (
615 self::access_shout() &&
616 verify_post_check($mybb->get_input('key'), true)
617 );
618
619 $handler = function () use ($mybb, $db, $plugins) {
620
621 if (!self::antiflood_pass() && !self::access_mod()) {
622 die('A'); // JS-handled error (Anti-flood)
623 }
624
625 $data = [
626 'uid' => (int)$mybb->user['uid'],
627 'text' => $mybb->get_input('text'),
628 'ipaddress' => $db->escape_binary( my_inet_pton(get_ip()) ),
629 ];
630
631 $plugins->run_hooks('dvz_shoutbox_shout', $data);
632
633 $data['shout_id'] = self::shout($data);
634
635 $plugins->run_hooks('dvz_shoutbox_shout_commit', $data);
636
637 };
638
639 break;
640
641 case 'dvz_sb_get':
642
643 $data = self::get($mybb->get_input('id', MyBB::INPUT_INT));
644
645 $permissions = (
646 (
647 self::access_mod() ||
648 (self::access_mod_own() && $data['uid'] == $mybb->user['uid'])
649 ) &&
650 verify_post_check($mybb->get_input('key'), true)
651 );
652
653 $handler = function () use ($data, $plugins) {
654
655 $plugins->run_hooks('dvz_shoutbox_get', $data);
656
657 echo json_encode([
658 'text' => $data['text'],
659 ]);
660
661 };
662
663 break;
664
665 case 'dvz_sb_update':
666
667 $data = self::get($mybb->get_input('id', MyBB::INPUT_INT));
668
669 $permissions = (
670 $data &&
671 self::can_mod($data) &&
672 verify_post_check($mybb->get_input('key'), true)
673 );
674
675 $handler = function () use ($mybb, $data, $plugins) {
676
677 $plugins->run_hooks('dvz_shoutbox_update', $data);
678
679 self::update($mybb->get_input('id', MyBB::INPUT_INT), $mybb->get_input('text'));
680
681 $data['text'] = $mybb->get_input('text');
682
683 $plugins->run_hooks('dvz_shoutbox_update_commit', $data);
684
685 echo self::parse($mybb->get_input('text'), self::get_username($mybb->get_input('id', MyBB::INPUT_INT)));
686
687 };
688
689 break;
690
691 case 'dvz_sb_delete':
692
693 $permissions = (
694 self::can_mod($mybb->get_input('id', MyBB::INPUT_INT)) &&
695 verify_post_check($mybb->get_input('key'), true)
696 );
697
698 $handler = function () use ($mybb, $plugins) {
699
700 $plugins->run_hooks('dvz_shoutbox_delete');
701
702 $result = self::delete($mybb->get_input('id', MyBB::INPUT_INT));
703
704 $plugins->run_hooks('dvz_shoutbox_delete_commit', $result);
705
706 };
707
708 break;
709
710 }
711
712 if (isset($permissions)) {
713
714 if ($permissions == false) {
715 echo 'P'; // JS-handled error (Permissions)
716 } else {
717
718 header('Content-type: text/plain; charset=' . $charset);
719 header('Cache-Control: no-store'); // force update on load
720 $handler();
721
722 }
723
724 }
725 }
726
727 static function load_window()
728 {
729 global $templates, $dvz_shoutbox, $lang, $mybb, $db, $theme;
730
731 $lang->load('dvz_shoutbox');
732
733 // MyBB template
734 $dvz_shoutbox = null;
735
736 // dvz_shoutbox template
737 $javascript = null;
738 $panel = null;
739 $classes = null;
740
741 if (self::access_view()) {
742
743 if (self::is_user()) {
744
745 // message: blocked
746 if (self::is_blocked()) {
747 $panel = '<div class="panel blocked"><p>' . $lang->dvz_sb_user_blocked . '</p></div>';
748 }
749 // message: minimum posts
750 else if (!self::access_minposts() && !self::access_mod()) {
751 $panel = '<div class="panel minposts"><p>' . str_replace('{MINPOSTS}', (int)$mybb->settings['dvz_sb_minposts'], $lang->dvz_sb_minposts) . '</p></div>';
752 }
753 // shout form
754 else if (self::access_shout()) {
755 $maxlength = $mybb->settings['dvz_sb_maxlength'] ? (int)$mybb->settings['dvz_sb_maxlength'] : null;
756 eval('$panel = "' . $templates->get('dvz_shoutbox_panel') . '";');
757 }
758
759 }
760
761 $js = null;
762
763 // configuration
764 $js .= 'dvz_shoutbox.interval = ' . (self::access_refresh() ? (float)$mybb->settings['dvz_sb_interval'] : 0) . ';' . PHP_EOL;
765 $js .= 'dvz_shoutbox.antiflood = ' . (self::access_mod() ? 0 : (float)$mybb->settings['dvz_sb_antiflood']) . ';' . PHP_EOL;
766 $js .= 'dvz_shoutbox.maxShouts = ' . (int)$mybb->settings['dvz_sb_num'] . ';' . PHP_EOL;
767 $js .= 'dvz_shoutbox.awayTime = ' . (float)$mybb->settings['dvz_sb_away'] . '*1000;' . PHP_EOL;
768 $js .= 'dvz_shoutbox.lang = [\'' . $lang->dvz_sb_delete_confirm . '\', \'' . str_replace('{ANTIFLOOD}', (float)$mybb->settings['dvz_sb_antiflood'], $lang->dvz_sb_antiflood) . '\', \''.$lang->dvz_sb_permissions.'\'];' . PHP_EOL;
769
770 // mark unread
771 if ($mybb->settings['dvz_sb_mark_unread']) {
772 $js .= 'dvz_shoutbox.markUnread = true;' . PHP_EOL;
773 }
774
775 // reversed order
776 if ($mybb->settings['dvz_sb_reversed']) {
777 $js .= 'dvz_shoutbox.reversed = true;' . PHP_EOL;
778 }
779
780 // lazyload
781 if (in_array($mybb->settings['dvz_sb_lazyload'], ['off', 'start', 'always'])) {
782 $js .= 'dvz_shoutbox.lazyMode = \'' . $mybb->settings['dvz_sb_lazyload'] . '\';' . PHP_EOL;
783 $js .= '$(window).bind(\'scroll resize\', dvz_shoutbox.checkVisibility);' . PHP_EOL;
784 }
785
786 // away mode
787 if ($mybb->settings['dvz_sb_away']) {
788 $js .= '$(window).on(\'mousemove click dblclick keydown scroll\', dvz_shoutbox.updateActivity);' . PHP_EOL;
789 }
790
791 // shoutbox status
792 $status =
793 (!isset($mybb->cookies['dvz_sb_status']) && $mybb->settings['dvz_sb_status'] == 1) ||
794 $mybb->cookies['dvz_sb_status'] == '1'
795 ;
796
797 $js .= 'dvz_shoutbox.status = ' . (int)$status . ';' . PHP_EOL;
798
799 if ($status == false) {
800 $classes .= ' collapsed';
801 }
802
803 $html = null;
804 $firstId = 0;
805 $lastId = 0;
806
807 if ($status == true) {
808
809 // preloaded shouts
810 $data = self::get_multiple("WHERE s.text IS NOT NULL ORDER BY s.id DESC LIMIT " . abs((int)$mybb->settings['dvz_sb_num']));
811
812 while ($row = $db->fetch_array($data)) {
813
814 $firstId = $row['id'];
815
816 if ($lastId == 0) {
817 $lastId = $row['id'];
818 }
819
820 $shout = self::render_shout($row);
821
822 $html = $mybb->settings['dvz_sb_reversed']
823 ? $shout . $html
824 : $html . $shout
825 ;
826 }
827
828 }
829
830 if (self::access_recall()) {
831 $js .= 'dvz_shoutbox.recalling = true;' . PHP_EOL;
832 }
833
834 if (self::access_refresh()) {
835 $js .= 'setTimeout(\'dvz_shoutbox.loop()\', ' . (float)$mybb->settings['dvz_sb_interval'] . ' * 1000);' . PHP_EOL;
836 }
837
838 $javascript = '
839<script>
840' . $js . '
841dvz_shoutbox.firstId = ' . $firstId . ';
842dvz_shoutbox.lastId = ' . $lastId . ';
843dvz_shoutbox.parseEntries();
844dvz_shoutbox.updateActivity();
845</script>';
846
847 eval('$dvz_shoutbox = "' . $templates->get('dvz_shoutbox') . '";');
848
849 }
850 }
851
852 static function show_archive()
853 {
854 global $db, $mybb, $templates, $lang, $theme, $footer, $headerinclude, $header, $charset;
855
856 $lang->load('dvz_shoutbox');
857
858 header('Content-type: text/html; charset=' . $charset);
859
860 add_breadcrumb($lang->dvz_sb_shoutbox, "index.php?action=shoutbox_archive");
861
862 // moderation panel
863 if (self::access_mod()) {
864
865 if (isset($mybb->input['banlist']) && verify_post_check($mybb->get_input('postkey'))) {
866 self::banlist_update($mybb->get_input('banlist'));
867 }
868
869 if ($mybb->get_input('days') && verify_post_check($mybb->get_input('postkey'))) {
870 if ($mybb->get_input('days') == 'all') {
871 self::clear();
872 } else {
873 $allowed = [2, 7, 30, 90];
874 if (in_array($mybb->get_input('days'), $allowed)) {
875 self::clear($mybb->get_input('days'));
876 }
877 }
878 }
879
880 $blocked_users = htmlspecialchars_uni($mybb->settings['dvz_sb_blocked_users']);
881 eval('$modoptions = "' . $templates->get("dvz_shoutbox_archive_modoptions") . '";');
882
883 } else {
884 $modoptions = null;
885 }
886
887 // unmark all unread messages
888 if ($mybb->get_input('unmark_all') && verify_post_check($mybb->get_input('postkey'))) {
889 my_unsetcookie('dvz_sb_last_read');
890 }
891
892 // pagination
893 $perPage = abs((int)$mybb->settings['dvz_sb_num_archive']);
894 $items = self::count();
895
896 $requestedId = $mybb->get_input('sid', MyBB::INPUT_INT);
897
898 if ($requestedId && self::get($requestedId)) {
899
900 if ($perPage == 0) {
901 $page = 0;
902 } else {
903 $itemsAfter = self::count('id > ' . $requestedId);
904 $itemPage = ceil( ($itemsAfter + 1) / $perPage );
905
906 $page = $itemPage;
907 }
908
909 } else {
910
911 $page = abs($mybb->get_input('page', MyBB::INPUT_INT));
912
913 if ($perPage == 0) {
914 $pages = 0;
915 } else {
916 $pages = ceil($items / $perPage);
917 }
918
919 if (!$page || $page < 1 || $page > $pages) {
920 $page = 1;
921 }
922
923 }
924
925 $limitStart = ($page - 1) * $perPage;
926
927 if ($items > $perPage && $perPage > 0) {
928 $multipage = multipage($items, $perPage, $page, 'index.php?action=shoutbox_archive');
929 }
930
931 $limit = $perPage;
932
933 if ($mybb->settings['dvz_sb_mark_unread'] && isset($mybb->cookies['dvz_sb_last_read'])) {
934 $limit += 1;
935 }
936
937 $data = self::get_multiple("WHERE s.text IS NOT NULL ORDER by s.id DESC LIMIT $limitStart,$limit");
938
939 $firstId = null;
940 $lastId = null;
941
942 $archive = null;
943
944 $rowCount = 1;
945
946 while ($row = $db->fetch_array($data)) {
947
948 if ($rowCount > $perPage) {
949
950 $nextPageLastId = $row['id'];
951
952 } else {
953
954 if ($mybb->settings['dvz_sb_mark_unread'] && isset($mybb->cookies['dvz_sb_last_read']) && $row['id'] > $mybb->cookies['dvz_sb_last_read']) {
955 $row['unread'] = true;
956 }
957
958 $archive .= self::render_shout($row, true);
959
960 if ($lastId == null) {
961 $lastId = $row['id'];
962 }
963
964 $firstId = $row['id'];
965
966 $rowCount++;
967
968 }
969
970 }
971
972 // update last read information
973 if ($mybb->settings['dvz_sb_mark_unread']) {
974 if (
975 !isset($mybb->cookies['dvz_sb_last_read']) ||
976 (
977 $lastId > $mybb->cookies['dvz_sb_last_read'] &&
978 (!isset($nextPageLastId) || $mybb->cookies['dvz_sb_last_read'] >= $nextPageLastId)
979 )
980 ) {
981 my_setcookie('dvz_sb_last_read', $lastId);
982 }
983 }
984
985 // last read link
986 if (
987 $mybb->settings['dvz_sb_mark_unread'] &&
988 isset($mybb->cookies['dvz_sb_last_read']) &&
989 !($page == 1 && $lastId == abs((int)$mybb->cookies['dvz_sb_last_read']))
990 ) {
991
992 $sid = abs((int)$mybb->cookies['dvz_sb_last_read']);
993 $last_read_url = $mybb->settings['bburl'] . '/index.php?action=shoutbox_archive&sid=' . $sid . '#sid' . $sid;
994 $unmark_all_url = $mybb->settings['bburl'] . '/index.php?action=shoutbox_archive&unmark_all=1&postkey=' . $mybb->post_code;
995
996 eval('$last_read_link = "' . $templates->get('dvz_shoutbox_last_read_link') . '";');
997
998 } else {
999 $last_read_link = null;
1000 }
1001
1002 $javascript = '
1003<script>
1004dvz_shoutbox.lang = [\'' . $lang->dvz_sb_delete_confirm . '\', \'' . str_replace('{ANTIFLOOD}', (float)$mybb->settings['dvz_sb_antiflood'], $lang->dvz_sb_antiflood) . '\', \'' . $lang->dvz_sb_permissions . '\'];
1005</script>';
1006
1007 eval('$content = "' . $templates->get("dvz_shoutbox_archive") . '";');
1008
1009 output_page($content);
1010
1011 exit;
1012 }
1013
1014 static function user_merge()
1015 {
1016 global $db, $source_user, $destination_user;
1017 return $db->update_query('dvz_shoutbox', ['uid' => (int)$destination_user['uid']], 'uid=' . (int)$source_user['uid']);
1018 }
1019
1020 static function activity(&$user_activity)
1021 {
1022 $location = parse_url($user_activity['location']);
1023 $filename = basename($location['path']);
1024
1025 parse_str(html_entity_decode($location['query']), $parameters);
1026
1027 if ($filename == 'index.php' && $parameters['action'] == 'shoutbox_archive') {
1028 $user_activity['activity'] = 'dvz_shoutbox_archive';
1029 }
1030 }
1031
1032 static function activity_translate(&$data)
1033 {
1034 global $lang;
1035
1036 $lang->load('dvz_shoutbox');
1037
1038 if ($data['user_activity']['activity'] == 'dvz_shoutbox_archive') {
1039 $data['location_name'] = sprintf($lang->dvz_sb_activity, 'index.php?action=shoutbox_archive');
1040 }
1041 }
1042
1043 static function clearcookies()
1044 {
1045 global $remove_cookies;
1046 $remove_cookies[] = 'dvz_sb_status';
1047 $remove_cookies[] = 'dvz_sb_last_read';
1048 }
1049
1050 static function admin_config_settings_change()
1051 {
1052 global $lang;
1053 $lang->load('dvz_shoutbox');
1054 }
1055
1056 // data handling
1057 static function get($id)
1058 {
1059 global $db;
1060
1061 return $db->fetch_array(
1062 $db->simple_select('dvz_shoutbox s', '*', 'id=' . (int)$id . ' AND s.text IS NOT NULL')
1063 );
1064 }
1065
1066 static function get_multiple($clauses)
1067 {
1068 global $db;
1069 return $db->query("
1070 SELECT
1071 s.*, u.username, u.usergroup, u.displaygroup, u.avatar
1072 FROM
1073 " . TABLE_PREFIX . "dvz_shoutbox s
1074 LEFT JOIN " . TABLE_PREFIX . "users u ON u.uid = s.uid
1075 " . $clauses . "
1076 ");
1077 }
1078
1079 static function get_username($id)
1080 {
1081 global $db;
1082 return $db->fetch_field(
1083 $db->query("SELECT username FROM " . TABLE_PREFIX . "users u, " . TABLE_PREFIX . "dvz_shoutbox s WHERE u.uid=s.uid AND s.id=" . (int)$id),
1084 'username'
1085 );
1086 }
1087
1088 static function user_last_shout_time($uid)
1089 {
1090 global $db;
1091 return $db->fetch_field(
1092 $db->simple_select('dvz_shoutbox s', 'date', 'uid=' . (int)$uid . ' AND s.text IS NOT NULL', [
1093 'order_by' => 'date',
1094 'order_dir' => 'desc',
1095 'limit' => 1,
1096 ]),
1097 'date'
1098 );
1099 }
1100
1101 static function count($where = false)
1102 {
1103 global $db;
1104 return $db->fetch_field(
1105 $db->simple_select('dvz_shoutbox', 'COUNT(text) as n', $where),
1106 'n'
1107 );
1108 }
1109
1110 static function shout($data)
1111 {
1112 global $mybb, $db;
1113
1114 if ($mybb->settings['dvz_sb_maxlength'] > 0) {
1115 $data['text'] = mb_substr($data['text'], 0, $mybb->settings['dvz_sb_maxlength']);
1116 }
1117
1118 foreach ($data as $key => &$value) {
1119 if (!in_array($key, array_keys($mybb->binary_fields['dvz_shoutbox']))) {
1120 $value = $db->escape_string($value);
1121 }
1122 }
1123
1124 $data['date'] = TIME_NOW;
1125
1126 return $db->insert_query('dvz_shoutbox', $data);
1127 }
1128
1129 static function update($id, $text)
1130 {
1131 global $db;
1132 return $db->update_query('dvz_shoutbox', [
1133 'text' => $db->escape_string($text),
1134 'modified' => time(),
1135 ], 'id=' . (int)$id);
1136 }
1137
1138 static function banlist_update($new)
1139 {
1140 global $db;
1141
1142 $db->update_query('settings', ['value' => $db->escape_string($new)], "name='dvz_sb_blocked_users'");
1143
1144 rebuild_settings();
1145 }
1146
1147 static function delete($id)
1148 {
1149 global $mybb, $db;
1150
1151 if ($mybb->settings['dvz_sb_sync']) {
1152 return $db->update_query('dvz_shoutbox', [
1153 'text' => 'NULL',
1154 'modified' => time(),
1155 ], 'id=' . (int)$id, false, true);
1156 } else {
1157 return $db->delete_query('dvz_shoutbox', 'id=' . (int)$id);
1158 }
1159 }
1160
1161 static function clear($days = false)
1162 {
1163 global $db;
1164
1165 if ($days) {
1166 $where = 'date < ' . ( TIME_NOW - ((int)$days * 86400) );
1167 } else {
1168 $where = false;
1169 }
1170
1171 return $db->delete_query('dvz_shoutbox', $where);
1172 }
1173
1174 // permissions
1175 static function is_user()
1176 {
1177 global $mybb;
1178 return $mybb->user['uid'] != 0;
1179 }
1180
1181 static function is_blocked()
1182 {
1183 global $mybb;
1184 return in_array($mybb->user['uid'], self::settings_get_csv('blocked_users'));
1185 }
1186
1187 static function access_view()
1188 {
1189 $array = self::settings_get_csv('groups_view');
1190 return $array[0] == -1 || is_member($array);
1191 }
1192
1193 static function access_refresh()
1194 {
1195 $array = self::settings_get_csv('groups_refresh');
1196 return $array[0] == -1 || is_member($array);
1197 }
1198
1199 static function access_shout()
1200 {
1201 $array = self::settings_get_csv('groups_shout');
1202
1203 return (
1204 self::is_user() &&
1205 !self::is_blocked() &&
1206 (
1207 self::access_mod() ||
1208 (
1209 self::access_view() &&
1210 self::access_minposts() &&
1211 $array[0] == -1 || is_member($array)
1212 )
1213 )
1214 );
1215 }
1216
1217 static function access_recall()
1218 {
1219 $array = self::settings_get_csv('groups_recall');
1220 return $array[0] == -1 || is_member($array);
1221 }
1222
1223 static function access_mod()
1224 {
1225 global $mybb;
1226
1227 $array = self::settings_get_csv('groups_mod');
1228
1229 return (
1230 ($array[0] == -1 || is_member($array)) ||
1231 ($mybb->settings['dvz_sb_supermods'] && $mybb->usergroup['issupermod'])
1232 );
1233 }
1234
1235 static function access_mod_own()
1236 {
1237 $array = self::settings_get_csv('groups_mod_own');
1238
1239 return $array[0] == -1 || is_member($array);
1240 }
1241
1242 static function access_minposts()
1243 {
1244 global $mybb;
1245 return $mybb->user['postnum'] >= $mybb->settings['dvz_sb_minposts'];
1246 }
1247
1248 static function can_mod($data)
1249 {
1250 global $mybb;
1251
1252 if (self::access_mod()) {
1253 return true;
1254 } else if (self::access_mod_own() && self::access_shout()) {
1255
1256 if (is_int($data)) {
1257 $data = self::get($shoutId);
1258 }
1259
1260 if ($data['uid'] == $mybb->user['uid']) {
1261 return true;
1262 }
1263
1264 }
1265
1266 return false;
1267 }
1268
1269 // core
1270 static function render_shout($data, $static = false)
1271 {
1272 global $mybb;
1273
1274 $id = (int)$data['id'];
1275 $text = self::parse($data['text'], $data['username']);
1276 $date = htmlspecialchars_uni(my_date($mybb->settings['dvz_sb_dateformat'], $data['date']));
1277 $username = htmlspecialchars_uni($data['username']);
1278 $user = build_profile_link(format_name($username, $data['usergroup'], $data['displaygroup']), (int)$data['uid']);
1279 $avatar = '<img src="' . (empty($data['avatar']) ? htmlspecialchars_uni($mybb->settings['useravatar']) : htmlspecialchars_uni($data['avatar'])) . '" alt="avatar" />';
1280
1281 $staticLink = $mybb->settings['bburl'] . '/index.php?action=shoutbox_archive&sid=' . $id . '#sid' . $id;
1282
1283 $classes = 'entry';
1284 $notes = null;
1285 $attributes = null;
1286
1287 $own = $data['uid'] == $mybb->user['uid'];
1288
1289 if (!empty($data['unread'])) {
1290 $classes .= ' unread';
1291 }
1292
1293 if ($static) {
1294
1295 if (self::access_mod()) {
1296 $notes .= '<span class="ip">' . my_inet_ntop($data['ipaddress']) . '</span>';
1297 }
1298
1299 if (
1300 self::access_mod() ||
1301 (self::access_mod_own() && $own)
1302 ) {
1303 $notes .= '<a href="" class="mod edit">E</a><a href="" class="mod del">X</a>';
1304 }
1305
1306 $attributes .= ' id="sid' . $id . '"';
1307
1308 }
1309
1310 if (
1311 self::access_mod() ||
1312 (self::access_mod_own() && $own)
1313 ) {
1314 $attributes .= ' data-mod';
1315 }
1316
1317 if ($own) {
1318 $attributes .= ' data-own';
1319 }
1320
1321 return '
1322<div class="' . $classes . '" data-id="' . $id . '" data-username="' . $username . '"' . $attributes . '>
1323 <div class="avatar">' . $avatar . '</div>
1324 <div class="user">' . $user . '</div>
1325 <div class="text">' . $text . '</div>
1326 <div class="info">' . $notes . '<a href="' . $staticLink . '"><span class="date">' . $date . '</span></a></div>
1327</div>';
1328 }
1329
1330 static function parse($message, $me_username)
1331 {
1332 global $mybb;
1333
1334 require_once MYBB_ROOT . 'inc/class_parser.php';
1335
1336 $parser = new postParser;
1337 $options = [
1338 'allow_mycode' => $mybb->settings['dvz_sb_mycode'],
1339 'allow_smilies' => $mybb->settings['dvz_sb_smilies'],
1340 'allow_imgcode' => 0,
1341 'filter_badwords' => 1,
1342 'me_username' => $me_username,
1343 ];
1344
1345 return $parser->parse_message($message, $options);
1346 }
1347
1348 static function antiflood_pass()
1349 {
1350 global $mybb;
1351 return (
1352 !$mybb->settings['dvz_sb_antiflood'] ||
1353 ( TIME_NOW - self::user_last_shout_time($mybb->user['uid']) ) > $mybb->settings['dvz_sb_antiflood']
1354 );
1355 }
1356
1357 static function settings_get_csv($name)
1358 {
1359 global $mybb;
1360 return array_filter( explode(',', $mybb->settings['dvz_sb_' . $name]) );
1361 }
1362
1363 static function async_limit()
1364 {
1365 global $mybb;
1366 return max(
1367 abs((int)$mybb->settings['dvz_sb_num']),
1368 abs((int)$mybb->settings['dvz_sb_num_archive'])
1369 );
1370 }
1371
1372}