· 5 years ago · Feb 07, 2020, 02:00 PM
1<?php
2/* by Tomasz 'Devilshakerz' Mlynski [devilshakerz.com]; Copyright (C) 2014-2017
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.2',
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(
419 'dvz_shoutbox',
420 'dvz_shoutbox_panel',
421 'dvz_shoutbox_archive',
422 'dvz_shoutbox_last_read_link',
423 'dvz_shoutbox_archive_modoptions'
424 ) AND sid = '-1'");
425
426 // delete data
427 if ($db->type == 'sqlite') {
428 $db->close_cursors();
429 }
430
431 $db->drop_table('dvz_shoutbox');
432}
433
434function dvz_shoutbox_is_installed()
435{
436 global $mybb;
437 return $mybb->settings['dvz_sb_num'] !== null;
438}
439
440
441class dvz_shoutbox
442{
443
444 // hooks
445 static function global_start()
446 {
447 global $mybb, $templatelist;
448
449 $mybb->binary_fields['dvz_shoutbox'] = ['ipaddress' => true];
450
451 if (defined('THIS_SCRIPT') && THIS_SCRIPT == 'index.php' && self::access_view()) {
452
453 if (!empty($templatelist)) {
454 $templatelist .= ',';
455 }
456
457 if ($mybb->get_input('action') == 'shoutbox_archive') {
458 // archive templates
459
460 $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';
461
462 if (self::access_mod()) {
463 $templatelist .= ',dvz_shoutbox_archive_modoptions';
464 }
465
466 } else {
467 // index templates
468 $templatelist .= 'dvz_shoutbox,dvz_shoutbox_panel';
469 }
470
471 }
472 }
473
474 static function global_end()
475 {
476 global $mybb;
477
478 if ($mybb->get_input('action') == 'shoutbox_archive' && self::access_view()) {
479 return self::show_archive();
480 }
481 }
482
483 static function xmlhttp()
484 {
485 global $mybb, $db, $charset, $plugins;
486
487 $mybb->binary_fields['dvz_shoutbox'] = ['ipaddress' => true];
488
489 switch ($mybb->get_input('action')) {
490
491 case 'dvz_sb_get_updates':
492
493 $permissions = (
494 self::access_view() &&
495 self::access_refresh()
496 );
497
498 $handler = function () use ($mybb, $db, $plugins) {
499
500 $syncConditions = $mybb->settings['dvz_sb_sync']
501 ? "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)) . ")"
502 : null
503 ;
504
505 $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());
506
507 $html = null; // JS-handled empty response
508 $sync = [];
509 $firstId = 0;
510 $lastId = 0;
511
512 while ($row = $db->fetch_array($data)) {
513
514 if ($row['id'] <= $mybb->get_input('last', MyBB::INPUT_INT)) {
515 // sync update
516
517 $sync[ $row['id'] ] = $row['text'] === null
518 ? null
519 : self::parse($row['text'], $row['username'])
520 ;
521
522 } else {
523 // new shout
524
525 $firstId = $row['id'];
526
527 if ($lastId == 0) {
528 $lastId = $row['id'];
529 }
530
531 $shout = self::render_shout($row);
532
533 $html = $mybb->settings['dvz_sb_reversed']
534 ? $shout . $html
535 : $html . $shout
536 ;
537
538 }
539
540 }
541
542 if ($html != null || !empty($sync)) {
543
544 $response = [];
545
546 if ($html != null) {
547
548 $response['html'] = $html;
549 $response['last'] = $lastId;
550
551 if ($mybb->get_input('first', MyBB::INPUT_INT) == 0) {
552 $response['first'] = $firstId;
553 }
554
555 }
556
557 if (!empty($sync)) {
558 $response['sync'] = $sync;
559 }
560
561 $plugins->run_hooks('dvz_shoutbox_get_updates', $response);
562
563 echo json_encode($response);
564
565 }
566 };
567
568 break;
569
570 case 'dvz_sb_recall':
571
572 $permissions = (
573 self::access_view() &&
574 self::access_refresh() &&
575 self::access_recall()
576 );
577
578 $handler = function () use ($mybb, $db, $plugins) {
579
580 $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']));
581
582 $response = [];
583
584 $html = null; // JS-handled empty response
585 $firstId = 0;
586
587 while ($row = $db->fetch_array($data)) {
588
589 $firstId = $row['id'];
590
591 $shout = self::render_shout($row);
592
593 $html = $mybb->settings['dvz_sb_reversed']
594 ? $shout . $html
595 : $html . $shout
596 ;
597 }
598
599 if ($html != null) {
600 $response['html'] = $html;
601 }
602
603 if ($db->num_rows($data) < abs((int)$mybb->settings['dvz_sb_num'])) {
604 $response['end'] = 1;
605 }
606
607 if ($response) {
608 $response['first'] = $firstId;
609 }
610
611 $plugins->run_hooks('dvz_shoutbox_recall', $response);
612
613 echo json_encode($response);
614
615 };
616
617 break;
618
619 case 'dvz_sb_shout':
620
621 $permissions = (
622 self::access_shout() &&
623 verify_post_check($mybb->get_input('key'), true)
624 );
625
626 $handler = function () use ($mybb, $db, $plugins) {
627
628 if (!self::antiflood_pass() && !self::access_mod()) {
629 die('A'); // JS-handled error (Anti-flood)
630 }
631
632 $data = [
633 'uid' => (int)$mybb->user['uid'],
634 'text' => $mybb->get_input('text'),
635 'ipaddress' => $db->escape_binary( my_inet_pton(get_ip()) ),
636 ];
637
638 $plugins->run_hooks('dvz_shoutbox_shout', $data);
639
640 $data['shout_id'] = self::shout($data);
641
642 $plugins->run_hooks('dvz_shoutbox_shout_commit', $data);
643
644 };
645
646 break;
647
648 case 'dvz_sb_get':
649
650 $data = self::get($mybb->get_input('id', MyBB::INPUT_INT));
651
652 $permissions = (
653 (
654 self::access_mod() ||
655 (self::access_mod_own() && $data['uid'] == $mybb->user['uid'])
656 ) &&
657 verify_post_check($mybb->get_input('key'), true)
658 );
659
660 $handler = function () use ($data, $plugins) {
661
662 $plugins->run_hooks('dvz_shoutbox_get', $data);
663
664 echo json_encode([
665 'text' => $data['text'],
666 ]);
667
668 };
669
670 break;
671
672 case 'dvz_sb_update':
673
674 $data = self::get($mybb->get_input('id', MyBB::INPUT_INT));
675
676 $permissions = (
677 $data &&
678 self::can_mod($data) &&
679 verify_post_check($mybb->get_input('key'), true)
680 );
681
682 $handler = function () use ($mybb, $data, $plugins) {
683
684 $plugins->run_hooks('dvz_shoutbox_update', $data);
685
686 self::update($mybb->get_input('id', MyBB::INPUT_INT), $mybb->get_input('text'));
687
688 $data['text'] = $mybb->get_input('text');
689
690 $plugins->run_hooks('dvz_shoutbox_update_commit', $data);
691
692 echo self::parse($mybb->get_input('text'), self::get_username($mybb->get_input('id', MyBB::INPUT_INT)));
693
694 };
695
696 break;
697
698 case 'dvz_sb_delete':
699
700 $permissions = (
701 self::can_mod($mybb->get_input('id', MyBB::INPUT_INT)) &&
702 verify_post_check($mybb->get_input('key'), true)
703 );
704
705 $handler = function () use ($mybb, $plugins) {
706
707 $plugins->run_hooks('dvz_shoutbox_delete');
708
709 $result = self::delete($mybb->get_input('id', MyBB::INPUT_INT));
710
711 $plugins->run_hooks('dvz_shoutbox_delete_commit', $result);
712
713 };
714
715 break;
716
717 }
718
719 if (isset($permissions)) {
720
721 if ($permissions == false) {
722 echo 'P'; // JS-handled error (Permissions)
723 } else {
724
725 header('Content-type: text/plain; charset=' . $charset);
726 header('Cache-Control: no-store'); // force update on load
727 $handler();
728
729 }
730
731 }
732 }
733
734 static function load_window()
735 {
736 global $templates, $dvz_shoutbox, $lang, $mybb, $db, $theme;
737
738 $lang->load('dvz_shoutbox');
739
740 // MyBB template
741 $dvz_shoutbox = null;
742
743 // dvz_shoutbox template
744 $javascript = null;
745 $panel = null;
746 $classes = null;
747
748 if (self::access_view()) {
749
750 if (self::is_user()) {
751
752 // message: blocked
753 if (self::is_blocked()) {
754 $panel = '<div class="panel blocked"><p>' . $lang->dvz_sb_user_blocked . '</p></div>';
755 }
756 // message: minimum posts
757 else if (!self::access_minposts() && !self::access_mod()) {
758 $panel = '<div class="panel minposts"><p>' . str_replace('{MINPOSTS}', (int)$mybb->settings['dvz_sb_minposts'], $lang->dvz_sb_minposts) . '</p></div>';
759 }
760 // shout form
761 else if (self::access_shout()) {
762 $maxlength = $mybb->settings['dvz_sb_maxlength'] ? (int)$mybb->settings['dvz_sb_maxlength'] : null;
763 eval('$panel = "' . $templates->get('dvz_shoutbox_panel') . '";');
764 }
765
766 }
767
768 $js = null;
769
770 // configuration
771 $js .= 'dvz_shoutbox.interval = ' . (self::access_refresh() ? (float)$mybb->settings['dvz_sb_interval'] : 0) . ';' . PHP_EOL;
772 $js .= 'dvz_shoutbox.antiflood = ' . (self::access_mod() ? 0 : (float)$mybb->settings['dvz_sb_antiflood']) . ';' . PHP_EOL;
773 $js .= 'dvz_shoutbox.maxShouts = ' . (int)$mybb->settings['dvz_sb_num'] . ';' . PHP_EOL;
774 $js .= 'dvz_shoutbox.awayTime = ' . (float)$mybb->settings['dvz_sb_away'] . '*1000;' . PHP_EOL;
775 $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;
776
777 // mark unread
778 if ($mybb->settings['dvz_sb_mark_unread']) {
779 $js .= 'dvz_shoutbox.markUnread = true;' . PHP_EOL;
780 }
781
782 // reversed order
783 if ($mybb->settings['dvz_sb_reversed']) {
784 $js .= 'dvz_shoutbox.reversed = true;' . PHP_EOL;
785 }
786
787 // lazyload
788 if (in_array($mybb->settings['dvz_sb_lazyload'], ['off', 'start', 'always'])) {
789 $js .= 'dvz_shoutbox.lazyMode = \'' . $mybb->settings['dvz_sb_lazyload'] . '\';' . PHP_EOL;
790 $js .= '$(window).bind(\'scroll resize\', dvz_shoutbox.checkVisibility);' . PHP_EOL;
791 }
792
793 // away mode
794 if ($mybb->settings['dvz_sb_away']) {
795 $js .= '$(window).on(\'mousemove click dblclick keydown scroll\', dvz_shoutbox.updateActivity);' . PHP_EOL;
796 }
797
798 // shoutbox status
799 $status =
800 (!isset($mybb->cookies['dvz_sb_status']) && $mybb->settings['dvz_sb_status'] == 1) ||
801 $mybb->cookies['dvz_sb_status'] == '1'
802 ;
803
804 $js .= 'dvz_shoutbox.status = ' . (int)$status . ';' . PHP_EOL;
805
806 if ($status == false) {
807 $classes .= ' collapsed';
808 }
809
810 $html = null;
811 $firstId = 0;
812 $lastId = 0;
813
814 if ($status == true) {
815
816 // preloaded shouts
817 $data = self::get_multiple("WHERE s.text IS NOT NULL ORDER BY s.id DESC LIMIT " . abs((int)$mybb->settings['dvz_sb_num']));
818
819 while ($row = $db->fetch_array($data)) {
820
821 $firstId = $row['id'];
822
823 if ($lastId == 0) {
824 $lastId = $row['id'];
825 }
826
827 $shout = self::render_shout($row);
828
829 $html = $mybb->settings['dvz_sb_reversed']
830 ? $shout . $html
831 : $html . $shout
832 ;
833 }
834
835 }
836
837 if (self::access_recall()) {
838 $js .= 'dvz_shoutbox.recalling = true;' . PHP_EOL;
839 }
840
841 if (self::access_refresh()) {
842 $js .= 'setTimeout(\'dvz_shoutbox.loop()\', ' . (float)$mybb->settings['dvz_sb_interval'] . ' * 1000);' . PHP_EOL;
843 }
844
845 $javascript = '
846<script>
847' . $js . '
848dvz_shoutbox.firstId = ' . $firstId . ';
849dvz_shoutbox.lastId = ' . $lastId . ';
850dvz_shoutbox.parseEntries();
851dvz_shoutbox.updateActivity();
852</script>';
853
854 eval('$dvz_shoutbox = "' . $templates->get('dvz_shoutbox') . '";');
855
856 }
857 }
858
859 static function show_archive()
860 {
861 global $db, $mybb, $templates, $lang, $theme, $footer, $headerinclude, $header, $charset;
862
863 $lang->load('dvz_shoutbox');
864
865 header('Content-type: text/html; charset=' . $charset);
866
867 add_breadcrumb($lang->dvz_sb_shoutbox, "index.php?action=shoutbox_archive");
868
869 // moderation panel
870 if (self::access_mod()) {
871
872 if (isset($mybb->input['banlist']) && verify_post_check($mybb->get_input('postkey'))) {
873 self::banlist_update($mybb->get_input('banlist'));
874 }
875
876 if ($mybb->get_input('days') && verify_post_check($mybb->get_input('postkey'))) {
877 if ($mybb->get_input('days') == 'all') {
878 self::clear();
879 } else {
880 $allowed = [2, 7, 30, 90];
881 if (in_array($mybb->get_input('days'), $allowed)) {
882 self::clear($mybb->get_input('days'));
883 }
884 }
885 }
886
887 $blocked_users = htmlspecialchars_uni($mybb->settings['dvz_sb_blocked_users']);
888 eval('$modoptions = "' . $templates->get("dvz_shoutbox_archive_modoptions") . '";');
889
890 } else {
891 $modoptions = null;
892 }
893
894 // unmark all unread messages
895 if ($mybb->get_input('unmark_all') && verify_post_check($mybb->get_input('postkey'))) {
896 my_unsetcookie('dvz_sb_last_read');
897 }
898
899 // pagination
900 $perPage = abs((int)$mybb->settings['dvz_sb_num_archive']);
901 $items = self::count();
902
903 $requestedId = $mybb->get_input('sid', MyBB::INPUT_INT);
904
905 if ($requestedId && self::get($requestedId)) {
906
907 if ($perPage == 0) {
908 $page = 0;
909 } else {
910 $itemsAfter = self::count('id > ' . $requestedId);
911 $itemPage = ceil( ($itemsAfter + 1) / $perPage );
912
913 $page = $itemPage;
914 }
915
916 } else {
917
918 $page = abs($mybb->get_input('page', MyBB::INPUT_INT));
919
920 if ($perPage == 0) {
921 $pages = 0;
922 } else {
923 $pages = ceil($items / $perPage);
924 }
925
926 if (!$page || $page < 1 || $page > $pages) {
927 $page = 1;
928 }
929
930 }
931
932 $limitStart = ($page - 1) * $perPage;
933
934 if ($items > $perPage && $perPage > 0) {
935 $multipage = multipage($items, $perPage, $page, 'index.php?action=shoutbox_archive');
936 }
937
938 $limit = $perPage;
939
940 if ($mybb->settings['dvz_sb_mark_unread'] && isset($mybb->cookies['dvz_sb_last_read'])) {
941 $limit += 1;
942 }
943
944 $data = self::get_multiple("WHERE s.text IS NOT NULL ORDER by s.id DESC LIMIT $limitStart,$limit");
945
946 $firstId = null;
947 $lastId = null;
948
949 $archive = null;
950
951 $rowCount = 1;
952
953 while ($row = $db->fetch_array($data)) {
954
955 if ($rowCount > $perPage) {
956
957 $nextPageLastId = $row['id'];
958
959 } else {
960
961 if ($mybb->settings['dvz_sb_mark_unread'] && isset($mybb->cookies['dvz_sb_last_read']) && $row['id'] > $mybb->cookies['dvz_sb_last_read']) {
962 $row['unread'] = true;
963 }
964
965 $archive .= self::render_shout($row, true);
966
967 if ($lastId == null) {
968 $lastId = $row['id'];
969 }
970
971 $firstId = $row['id'];
972
973 $rowCount++;
974
975 }
976
977 }
978
979 // update last read information
980 if ($mybb->settings['dvz_sb_mark_unread']) {
981 if (
982 !isset($mybb->cookies['dvz_sb_last_read']) ||
983 (
984 $lastId > $mybb->cookies['dvz_sb_last_read'] &&
985 (!isset($nextPageLastId) || $mybb->cookies['dvz_sb_last_read'] >= $nextPageLastId)
986 )
987 ) {
988 my_setcookie('dvz_sb_last_read', $lastId);
989 }
990 }
991
992 // last read link
993 if (
994 $mybb->settings['dvz_sb_mark_unread'] &&
995 isset($mybb->cookies['dvz_sb_last_read']) &&
996 !($page == 1 && $lastId == abs((int)$mybb->cookies['dvz_sb_last_read']))
997 ) {
998
999 $sid = abs((int)$mybb->cookies['dvz_sb_last_read']);
1000 $last_read_url = $mybb->settings['bburl'] . '/index.php?action=shoutbox_archive&sid=' . $sid . '#sid' . $sid;
1001 $unmark_all_url = $mybb->settings['bburl'] . '/index.php?action=shoutbox_archive&unmark_all=1&postkey=' . $mybb->post_code;
1002
1003 eval('$last_read_link = "' . $templates->get('dvz_shoutbox_last_read_link') . '";');
1004
1005 } else {
1006 $last_read_link = null;
1007 }
1008
1009 $javascript = '
1010<script>
1011dvz_shoutbox.lang = [\'' . $lang->dvz_sb_delete_confirm . '\', \'' . str_replace('{ANTIFLOOD}', (float)$mybb->settings['dvz_sb_antiflood'], $lang->dvz_sb_antiflood) . '\', \'' . $lang->dvz_sb_permissions . '\'];
1012</script>';
1013
1014 eval('$content = "' . $templates->get("dvz_shoutbox_archive") . '";');
1015
1016 output_page($content);
1017
1018 exit;
1019 }
1020
1021 static function user_merge()
1022 {
1023 global $db, $source_user, $destination_user;
1024 return $db->update_query('dvz_shoutbox', ['uid' => (int)$destination_user['uid']], 'uid=' . (int)$source_user['uid']);
1025 }
1026
1027 static function activity(&$user_activity)
1028 {
1029 $location = parse_url($user_activity['location']);
1030 $filename = basename($location['path']);
1031
1032 parse_str(html_entity_decode($location['query']), $parameters);
1033
1034 if ($filename == 'index.php' && $parameters['action'] == 'shoutbox_archive') {
1035 $user_activity['activity'] = 'dvz_shoutbox_archive';
1036 }
1037 }
1038
1039 static function activity_translate(&$data)
1040 {
1041 global $lang;
1042
1043 $lang->load('dvz_shoutbox');
1044
1045 if ($data['user_activity']['activity'] == 'dvz_shoutbox_archive') {
1046 $data['location_name'] = sprintf($lang->dvz_sb_activity, 'index.php?action=shoutbox_archive');
1047 }
1048 }
1049
1050 static function clearcookies()
1051 {
1052 global $remove_cookies;
1053 $remove_cookies[] = 'dvz_sb_status';
1054 $remove_cookies[] = 'dvz_sb_last_read';
1055 }
1056
1057 static function admin_config_settings_change()
1058 {
1059 global $lang;
1060 $lang->load('dvz_shoutbox');
1061 }
1062
1063 // data handling
1064 static function get($id)
1065 {
1066 global $db;
1067
1068 return $db->fetch_array(
1069 $db->simple_select('dvz_shoutbox s', '*', 'id=' . (int)$id . ' AND s.text IS NOT NULL')
1070 );
1071 }
1072
1073 static function get_multiple($clauses)
1074 {
1075 global $db;
1076 return $db->query("
1077 SELECT
1078 s.*, u.username, u.usergroup, u.displaygroup, u.avatar
1079 FROM
1080 " . TABLE_PREFIX . "dvz_shoutbox s
1081 LEFT JOIN " . TABLE_PREFIX . "users u ON u.uid = s.uid
1082 " . $clauses . "
1083 ");
1084 }
1085
1086 static function get_username($id)
1087 {
1088 global $db;
1089 return $db->fetch_field(
1090 $db->query("SELECT username FROM " . TABLE_PREFIX . "users u, " . TABLE_PREFIX . "dvz_shoutbox s WHERE u.uid=s.uid AND s.id=" . (int)$id),
1091 'username'
1092 );
1093 }
1094
1095 static function user_last_shout_time($uid)
1096 {
1097 global $db;
1098 return $db->fetch_field(
1099 $db->simple_select('dvz_shoutbox s', 'date', 'uid=' . (int)$uid . ' AND s.text IS NOT NULL', [
1100 'order_by' => 'date',
1101 'order_dir' => 'desc',
1102 'limit' => 1,
1103 ]),
1104 'date'
1105 );
1106 }
1107
1108 static function count($where = false)
1109 {
1110 global $db;
1111 return $db->fetch_field(
1112 $db->simple_select('dvz_shoutbox', 'COUNT(text) as n', $where),
1113 'n'
1114 );
1115 }
1116
1117 static function shout($data)
1118 {
1119 global $mybb, $db;
1120
1121 if ($mybb->settings['dvz_sb_maxlength'] > 0) {
1122 $data['text'] = mb_substr($data['text'], 0, $mybb->settings['dvz_sb_maxlength']);
1123 }
1124
1125 foreach ($data as $key => &$value) {
1126 if (!in_array($key, array_keys($mybb->binary_fields['dvz_shoutbox']))) {
1127 $value = $db->escape_string($value);
1128 }
1129 }
1130
1131 $data['date'] = TIME_NOW;
1132
1133 return $db->insert_query('dvz_shoutbox', $data);
1134 }
1135
1136 static function update($id, $text)
1137 {
1138 global $db;
1139 return $db->update_query('dvz_shoutbox', [
1140 'text' => $db->escape_string($text),
1141 'modified' => time(),
1142 ], 'id=' . (int)$id);
1143 }
1144
1145 static function banlist_update($new)
1146 {
1147 global $db;
1148
1149 $db->update_query('settings', ['value' => $db->escape_string($new)], "name='dvz_sb_blocked_users'");
1150
1151 rebuild_settings();
1152 }
1153
1154 static function delete($id)
1155 {
1156 global $mybb, $db;
1157
1158 if ($mybb->settings['dvz_sb_sync']) {
1159 return $db->update_query('dvz_shoutbox', [
1160 'text' => 'NULL',
1161 'modified' => time(),
1162 ], 'id=' . (int)$id, false, true);
1163 } else {
1164 return $db->delete_query('dvz_shoutbox', 'id=' . (int)$id);
1165 }
1166 }
1167
1168 static function clear($days = false)
1169 {
1170 global $db;
1171
1172 if ($days) {
1173 $where = 'date < ' . ( TIME_NOW - ((int)$days * 86400) );
1174 } else {
1175 $where = false;
1176 }
1177
1178 return $db->delete_query('dvz_shoutbox', $where);
1179 }
1180
1181 // permissions
1182 static function is_user()
1183 {
1184 global $mybb;
1185 return $mybb->user['uid'] != 0;
1186 }
1187
1188 static function is_blocked()
1189 {
1190 global $mybb;
1191 return in_array($mybb->user['uid'], self::settings_get_csv('blocked_users'));
1192 }
1193
1194 static function access_view()
1195 {
1196 $array = self::settings_get_csv('groups_view');
1197 return $array[0] == -1 || is_member($array);
1198 }
1199
1200 static function access_refresh()
1201 {
1202 $array = self::settings_get_csv('groups_refresh');
1203 return $array[0] == -1 || is_member($array);
1204 }
1205
1206 static function access_shout()
1207 {
1208 $array = self::settings_get_csv('groups_shout');
1209
1210 return (
1211 self::is_user() &&
1212 !self::is_blocked() &&
1213 (
1214 self::access_mod() ||
1215 (
1216 self::access_view() &&
1217 self::access_minposts() &&
1218 $array[0] == -1 || is_member($array)
1219 )
1220 )
1221 );
1222 }
1223
1224 static function access_recall()
1225 {
1226 $array = self::settings_get_csv('groups_recall');
1227 return $array[0] == -1 || is_member($array);
1228 }
1229
1230 static function access_mod()
1231 {
1232 global $mybb;
1233
1234 $array = self::settings_get_csv('groups_mod');
1235
1236 return (
1237 ($array[0] == -1 || is_member($array)) ||
1238 ($mybb->settings['dvz_sb_supermods'] && $mybb->usergroup['issupermod'])
1239 );
1240 }
1241
1242 static function access_mod_own()
1243 {
1244 $array = self::settings_get_csv('groups_mod_own');
1245
1246 return $array[0] == -1 || is_member($array);
1247 }
1248
1249 static function access_minposts()
1250 {
1251 global $mybb;
1252 return $mybb->user['postnum'] >= $mybb->settings['dvz_sb_minposts'];
1253 }
1254
1255 static function can_mod($data)
1256 {
1257 global $mybb;
1258
1259 if (self::access_mod()) {
1260 return true;
1261 } else if (self::access_mod_own() && self::access_shout()) {
1262
1263 if (is_int($data)) {
1264 $data = self::get($data);
1265 }
1266
1267 if ($data['uid'] == $mybb->user['uid']) {
1268 return true;
1269 }
1270
1271 }
1272
1273 return false;
1274 }
1275
1276 // core
1277 static function render_shout($data, $static = false)
1278 {
1279 global $mybb;
1280
1281 $id = (int)$data['id'];
1282 $text = self::parse($data['text'], $data['username']);
1283 $date = htmlspecialchars_uni(my_date($mybb->settings['dvz_sb_dateformat'], $data['date']));
1284 $username = htmlspecialchars_uni($data['username']);
1285 $user = build_profile_link(format_name($username, $data['usergroup'], $data['displaygroup']), (int)$data['uid']);
1286 $avatar = '<img src="' . (empty($data['avatar']) ? htmlspecialchars_uni($mybb->settings['useravatar']) : htmlspecialchars_uni($data['avatar'])) . '" alt="avatar" />';
1287
1288 $staticLink = $mybb->settings['bburl'] . '/index.php?action=shoutbox_archive&sid=' . $id . '#sid' . $id;
1289
1290 $classes = 'entry';
1291 $notes = null;
1292 $attributes = null;
1293
1294 $own = $data['uid'] == $mybb->user['uid'];
1295
1296 if (!empty($data['unread'])) {
1297 $classes .= ' unread';
1298 }
1299
1300 if ($static) {
1301
1302 if (self::access_mod()) {
1303 $notes .= '<span class="ip">' . my_inet_ntop($data['ipaddress']) . '</span>';
1304 }
1305
1306 if (
1307 self::access_mod() ||
1308 (self::access_mod_own() && $own)
1309 ) {
1310 $notes .= '<a href="" class="mod edit">E</a><a href="" class="mod del">X</a>';
1311 }
1312
1313 $attributes .= ' id="sid' . $id . '"';
1314
1315 }
1316
1317 if (
1318 self::access_mod() ||
1319 (self::access_mod_own() && $own)
1320 ) {
1321 $attributes .= ' data-mod';
1322 }
1323
1324 if ($own) {
1325 $attributes .= ' data-own';
1326 }
1327
1328
1329 return '
1330 <div class="' . $classes . '" data-id="' . $id . '" data-username="' . $username . '"' . $attributes . '>
1331 <div class="avatar">
1332 ' . $avatar . '
1333 </div>
1334 <div class="shout-container">
1335 <div class="shout-top clearfix">
1336 <div class="left">
1337 <div class="user">' . $user . '</div>
1338 </div>
1339 <div class="right">
1340 <div class="info">' . $notes . '<a href="' . $staticLink . '"><span class="date">' . $date . '</span></a></div>
1341 </div>
1342 </div>
1343 <div class="text">
1344 ' . $text . '
1345 </div>
1346 </div>
1347 </div>
1348 ';
1349
1350// return '
1351// <div class="' . $classes . '" data-id="' . $id . '" data-username="' . $username . '"' . $attributes . '>
1352// <div class="avatar">' . $avatar . '</div>
1353// <div class="<a href="' . $staticLink . '"><span class="date">' . $date . '</span></a>">' . $user . '</div>
1354// <div class="text">' . $text . '</div>
1355// <div class="info">' . $notes . '<a href="' . $staticLink . '"><span class="date">' . $date . '</span></a></div>
1356// </div>';
1357 }
1358
1359 static function parse($message, $me_username)
1360 {
1361 global $mybb;
1362
1363 require_once MYBB_ROOT . 'inc/class_parser.php';
1364
1365 $parser = new postParser;
1366 $options = [
1367 'allow_mycode' => $mybb->settings['dvz_sb_mycode'],
1368 'allow_smilies' => $mybb->settings['dvz_sb_smilies'],
1369 'allow_imgcode' => 0,
1370 'filter_badwords' => 1,
1371 'me_username' => $me_username,
1372 ];
1373
1374 return $parser->parse_message($message, $options);
1375 }
1376
1377 static function antiflood_pass()
1378 {
1379 global $mybb;
1380 return (
1381 !$mybb->settings['dvz_sb_antiflood'] ||
1382 ( TIME_NOW - self::user_last_shout_time($mybb->user['uid']) ) > $mybb->settings['dvz_sb_antiflood']
1383 );
1384 }
1385
1386 static function settings_get_csv($name)
1387 {
1388 global $mybb;
1389 return array_filter( explode(',', $mybb->settings['dvz_sb_' . $name]) );
1390 }
1391
1392 static function async_limit()
1393 {
1394 global $mybb;
1395 return max(
1396 abs((int)$mybb->settings['dvz_sb_num']),
1397 abs((int)$mybb->settings['dvz_sb_num_archive'])
1398 );
1399 }
1400
1401}