*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
/*
* status codes
* 0 - Kicked/Banned
* 1 - Guest
* 2 - Applicant
* 3 - Member
* 4 - System message
* 5 - Moderator
* 6 - Super-Moderator
* 7 - Admin
* 8 - Super-Admin
* 9 - Private messages
*/
// initialize and load variables/configuration
const LANGUAGES = [
'ar' => ['name' => 'العربية', 'locale' => 'ar', 'dir' => 'rtl'],
'bg' => ['name' => 'Български', 'locale' => 'bg_BG', 'dir' => 'ltr'],
'cs' => ['name' => 'čeština', 'locale' => 'cs_CZ', 'dir' => 'ltr'],
'de' => ['name' => 'Deutsch', 'locale' => 'de_DE', 'dir' => 'ltr'],
'en' => ['name' => 'English', 'locale' => 'en_GB', 'dir' => 'ltr'],
'es' => ['name' => 'Español', 'locale' => 'es_ES', 'dir' => 'ltr'],
'fi' => ['name' => 'Suomi', 'locale' => 'fi_FI', 'dir' => 'ltr'],
'fr' => ['name' => 'Français', 'locale' => 'fr_FR', 'dir' => 'ltr'],
'hi' => ['name' => 'हिन्दी', 'locale' => 'hi', 'dir' => 'ltr'],
'id' => ['name' => 'Bahasa Indonesia', 'locale' => 'id_ID', 'dir' => 'ltr'],
'it' => ['name' => 'Italiano', 'locale' => 'it_IT', 'dir' => 'ltr'],
'pt' => ['name' => 'Português', 'locale' => 'pt_PT', 'dir' => 'ltr'],
'ru' => ['name' => 'Русский', 'locale' => 'ru_RU', 'dir' => 'ltr'],
'tr' => ['name' => 'Türkçe', 'locale' => 'tr_TR', 'dir' => 'ltr'],
'uk' => ['name' => 'Українська', 'locale' => 'uk_UA', 'dir' => 'ltr'],
'zh-Hans' => ['name' => '简体中文', 'locale' => 'zh_CN', 'dir' => 'ltr'],
'zh-Hant' => ['name' => '正體中文', 'locale' => 'zh_TW', 'dir' => 'ltr'],
];
load_config();
$U=[];// This user data
$db = null;// Database connection
$memcached = null;// Memcached connection
$language = LANG;// user selected language
$locale = LANGUAGES[LANG]['locale'];// user selected locale
$dir = LANGUAGES[LANG]['dir'];// user selected language direction
$scripts = []; //js enhancements
$styles = []; //css styles
$session = $_REQUEST['session'] ?? ''; //requested session
// set session variable to cookie if cookies are enabled
if(!isset($_REQUEST['session']) && isset($_COOKIE[COOKIENAME])){
$session = $_COOKIE[COOKIENAME];
}
$session = preg_replace('/[^0-9a-zA-Z]/', '', $session);
load_lang();
check_db();
cron();
route();
// main program: decide what to do based on queries
function route(): void
{
global $U;
if(!isset($_REQUEST['action'])){
send_login();
}elseif($_REQUEST['action']==='view'){
check_session();
send_messages();
}elseif($_REQUEST['action']==='redirect' && !empty($_GET['url'])){
send_redirect($_GET['url']);
}elseif($_REQUEST['action']==='wait'){
parse_sessions();
send_waiting_room();
}elseif($_REQUEST['action']==='post'){
check_session();
if(isset($_POST['kick']) && isset($_POST['sendto']) && $_POST['sendto']!=='s *'){
if($U['status']>=5 || ($U['status']>=3 && (get_setting('memkickalways') || (get_count_mods()==0 && get_setting('memkick'))))){
if(isset($_POST['what']) && $_POST['what']==='purge'){
kick_chatter([$_POST['sendto']], $_POST['message'], true);
}else{
kick_chatter([$_POST['sendto']], $_POST['message'], false);
}
}
}elseif(isset($_POST['message']) && isset($_POST['sendto'])){
send_post(validate_input());
}
send_post();
}elseif($_REQUEST['action']==='login'){
check_login();
show_fails();
send_frameset();
}elseif($_REQUEST['action']==='controls'){
check_session();
send_controls();
}elseif($_REQUEST['action']==='greeting'){
check_session();
send_greeting();
}elseif($_REQUEST['action']==='delete'){
check_session();
if(!isset($_POST['what'])){
}elseif($_POST['what']==='all'){
if(isset($_POST['confirm'])){
del_all_messages('', (int) ($U['status']==1 ? $U['entry'] : 0));
}else{
send_del_confirm();
}
}elseif($_POST['what']==='last'){
del_last_message();
}
send_post();
}elseif($_REQUEST['action']==='profile'){
check_session();
$arg='';
if(!isset($_POST['do'])){
}elseif($_POST['do']==='save'){
$arg=save_profile();
}elseif($_POST['do']==='delete'){
if(isset($_POST['confirm'])){
delete_account();
}else{
send_delete_account();
}
}
send_profile($arg);
}elseif($_REQUEST['action']==='logout' && $_SERVER['REQUEST_METHOD'] === 'POST'){
kill_session();
send_logout();
}elseif($_REQUEST['action']==='colours'){
check_session();
send_colours();
}elseif($_REQUEST['action']==='notes'){
check_session();
if(!isset($_POST['do'])){
}elseif($_POST['do']==='admin' && $U['status']>6){
send_notes(0);
}elseif($_POST['do']==='staff' && $U['status']>=5){
send_notes(1);
}elseif($_POST['do']==='public' && $U['status']>=3){
send_notes(3);
}
if($U['status']<3 || (!get_setting('personalnotes') && !get_setting('publicnotes'))){
send_access_denied();
}
send_notes(2);
}elseif($_REQUEST['action']==='help'){
check_session();
send_help();
}elseif($_REQUEST['action']==='viewpublicnotes'){
check_session();
view_publicnotes();
}elseif($_REQUEST['action']==='inbox'){
check_session();
if(isset($_POST['do'])){
clean_inbox_selected();
}
send_inbox();
}elseif($_REQUEST['action']==='download'){
send_download();
}elseif($_REQUEST['action']==='admin'){
check_session();
send_admin(route_admin());
}elseif($_REQUEST['action']==='setup'){
route_setup();
}elseif($_REQUEST['action']==='sa_password_reset'){
send_sa_password_reset();
}else{
send_login();
}
}
function route_admin() : string {
global $U, $db;
if($U['status']<5){
send_access_denied();
}
if(!isset($_POST['do'])){
return '';
}elseif($_POST['do']==='clean'){
if($_POST['what']==='choose'){
send_choose_messages();
}elseif($_POST['what']==='selected'){
clean_selected((int) $U['status'], $U['nickname']);
}elseif($_POST['what']==='room'){
clean_room();
}elseif($_POST['what']==='nick'){
$stmt=$db->prepare('SELECT null FROM ' . PREFIX . 'members WHERE nickname=? AND status>=?;');
$stmt->execute([$_POST['nickname'], $U['status']]);
if(!$stmt->fetch(PDO::FETCH_ASSOC)){
del_all_messages($_POST['nickname'], 0);
}
}
}elseif($_POST['do']==='kick'){
if(isset($_POST['name'])){
if(isset($_POST['what']) && $_POST['what']==='purge'){
kick_chatter($_POST['name'], $_POST['kickmessage'], true);
}else{
kick_chatter($_POST['name'], $_POST['kickmessage'], false);
}
}
}elseif($_POST['do']==='logout'){
if(isset($_POST['name'])){
logout_chatter($_POST['name']);
}
}elseif($_POST['do']==='sessions'){
if(isset($_POST['kick']) && isset($_POST['nick'])){
kick_chatter([$_POST['nick']], '', false);
}elseif(isset($_POST['logout']) && isset($_POST['nick'])){
logout_chatter([$_POST['nick']]);
}
send_sessions();
}elseif($_POST['do']==='register'){
return register_guest(3, $_POST['name']);
}elseif($_POST['do']==='superguest'){
return register_guest(2, $_POST['name']);
}elseif($_POST['do']==='status'){
return change_status($_POST['name'], $_POST['set']);
}elseif($_POST['do']==='regnew'){
return register_new($_POST['name'], $_POST['pass']);
}elseif($_POST['do']==='approve'){
approve_session();
send_approve_waiting();
}elseif($_POST['do']==='guestaccess'){
if(isset($_POST['guestaccess']) && preg_match('/^[0123]$/', $_POST['guestaccess'])){
update_setting('guestaccess', $_POST['guestaccess']);
change_guest_access(intval($_POST['guestaccess']));
}
}elseif($_POST['do']==='filter'){
send_filter(manage_filter());
}elseif($_POST['do']==='linkfilter'){
send_linkfilter(manage_linkfilter());
}elseif($_POST['do']==='topic'){
if(isset($_POST['topic'])){
update_setting('topic', htmlspecialchars($_POST['topic']));
}
}elseif($_POST['do']==='passreset'){
return passreset($_POST['name'], $_POST['pass']);
}
return '';
}
function route_setup(): void
{
global $U;
if(!valid_admin()){
send_alogin();
}
$C['bool_settings']=[
'suguests' => _('Enable applicants'),
'imgembed' => _('Embed images'),
'timestamps' => _('Show Timestamps'),
'trackip' => _('Show session-IP'),
'memkick' => _('Members can kick, if no moderator is present'),
'memkickalways' => _('Members can always kick'),
'forceredirect' => _('Force redirection'),
'incognito' => _('Incognito mode'),
'sendmail' => _('Send mail on new public message'),
'modfallback' => _('Fallback to waiting room, if no moderator is present to approve guests'),
'disablepm' => _('Disable private messages'),
'eninbox' => _('Enable offline inbox'),
'enablegreeting' => _('Show a greeting message before showing the messages'),
'sortupdown' => _('Sort messages from top to bottom'),
'hidechatters' => _('Hide list of chatters'),
'personalnotes' => _('Personal notes'),
'publicnotes' => _('Public notes'),
'filtermodkick' => _('Apply kick filter on moderators'),
'namedoers' => _('Show who kicks people or purges all messages.'),
'hide_reload_post_box' => _('Hide reload post box button'),
'hide_reload_messages' => _('Hide reload messages button'),
'hide_profile' => _('Hide profile button'),
'hide_admin' => _('Hide admin button'),
'hide_notes' => _('Hide notes button'),
'hide_clone' => _('Hide clone button'),
'hide_rearrange' => _('Hide rearrange button'),
'hide_help' => _('Hide help button'),
'postbox_delete_globally' => _('Apply postbox delete button globally'),
'allow_js' => _('Allow enhancing functionality with JavaScript'),
];
$C['colour_settings']=[
'colbg' => _('Background colour'),
'coltxt' => _('Font colour'),
];
$C['msg_settings']=[
'msgenter' => _('Entrance'),
'msgexit' => _('Leaving'),
'msgmemreg' => _('Member registered'),
'msgsureg' => _('Applicant registered'),
'msgkick' => _('Kicked'),
'msgmultikick' => _('Multiple kicked'),
'msgallkick' => _('All kicked'),
'msgclean' => _('Room cleaned'),
'msgsendall' => _('Message to all'),
'msgsendmem' => _('Message to members only'),
'msgsendmod' => _('Message to staff only'),
'msgsendadm' => _('Message to admins only'),
'msgsendprv' => _('Private message'),
'msgattache' => _('Attachement'),
];
$C['number_settings']=[
'memberexpire' => _('Member timeout (minutes)'),
'guestexpire' => _('Guest timeout (minutes)'),
'kickpenalty' => _('Kick penalty (minutes)'),
'entrywait' => _('Waiting room time (seconds)'),
'captchatime' => _('Captcha timeout (seconds)'),
'messageexpire' => _('Message timeout (minutes)'),
'messagelimit' => _('Message limit (public)'),
'maxmessage' => _('Maximal message length'),
'maxname' => _('Maximal nickname length'),
'minpass' => _('Minimal password length'),
'defaultrefresh' => _('Default message reload time (seconds)'),
'numnotes' => _('Number of notes revisions to keep'),
'maxuploadsize' => _('Maximum upload size in KB'),
'enfileupload' => _('Enable file uploads'),
'max_refresh_rate' => _('Lowest refresh rate'),
'min_refresh_rate' => _('Highest refresh rate'),
];
$C['textarea_settings']=[
'rulestxt' => _('Rules (html)'),
'css' => _('CSS Style'),
'disabletext' => _('Chat disabled message (html)'),
];
$C['text_settings']=[
'dateformat' => _('Date formating'),
'captchachars' => _('Characters used in Captcha'),
'redirect' => _('Custom redirection script'),
'chatname' => _('Chat name'),
'mailsender' => _('Send mail using this address'),
'mailreceiver' => _('Send mail to this address'),
'nickregex' => _('Nickname regex'),
'passregex' => _('Password regex'),
'externalcss' => _('Link to external CSS file (on your own server)'),
'metadescription' => _('Meta description (best 50 - 160 characters for SEO)'),
'sysmessagetxt' => _('Prepend this text to system messages'),
];
$extra_settings=[
'guestaccess' => _('Change Guestaccess'),
'englobalpass' => _('Enable global Password'),
'globalpass' => _('Global Password:'),
'captcha' => _('Captcha'),
'dismemcaptcha' => _('Only for guests'),
'topic' => _('Topic'),
'guestreg' => _('Let guests register themselves'),
'defaulttz' => _('Default time zone'),
];
$C['settings']=array_keys(array_merge($extra_settings, $C['bool_settings'], $C['colour_settings'], $C['msg_settings'], $C['number_settings'], $C['textarea_settings'], $C['text_settings'])); // All settings in the database
if(!isset($_POST['do'])){
}elseif($_POST['do']==='save'){
save_setup($C);
}elseif($_POST['do']==='backup' && $U['status']==8){
send_backup($C);
}elseif($_POST['do']==='restore' && $U['status']==8){
restore_backup($C);
send_backup($C);
}elseif($_POST['do']==='destroy' && $U['status']==8){
if(isset($_POST['confirm'])){
destroy_chat($C);
}else{
send_destroy_chat();
}
}
send_setup($C);
}
// html output subs
function prepare_stylesheets(string $class): void
{
global $U, $db, $scripts, $styles;
if($class === 'fatal_error') {
$styles[ 'fatal_error' ] = 'body{background-color:#000000;color:#FF0033}';
}
$styles['default'] = 'body,iframe{background-color:#000000;color:#FFFFFF;font-size:14px;text-align:center;width:100%;height:100%;margin:0;padding:0;border:none}';
$styles['default'] .= 'a:visited{color:#B33CB4} a:link{color:#00A2D4} a:active{color:#55A2D4}';
$styles['default'] .= 'input,select,textarea{color:#FFFFFF;background-color:#000000} ';
$styles['default'] .= '.error{color:#FF0033;text-align:left} .delbutton{background-color:#660000} .backbutton{background-color:#004400} #exitbutton{background-color:#AA0000} ';
$styles['default'] .= '.setup table table,.admin table table,.profile table table{width:100%;text-align:left} ';
$styles['default'] .= '.alogin table,.init table,.destroy_chat table,.delete_account table,.sessions table,.filter table,.linkfilter table,.notes table,.approve_waiting table,.del_confirm table,.profile table,.admin table,.backup table,.setup table{margin-left:auto;margin-right:auto} ';
$styles['default'] .= '.setup table table table,.admin table table table,.profile table table table{border-spacing:0px;margin-left:auto;margin-right:unset;width:unset} ';
$styles['default'] .= '.setup table table td,.backup #restoresubmit,.backup #backupsubmit,.admin table table td,.profile table table td,.login td+td,.alogin td+td{text-align:right} ';
$styles['default'] .= '.init td,.backup #restorecheck td,.admin #clean td,.admin #regnew td,.session td,.messages,.inbox,.approve_waiting td,.choose_messages,.greeting,.help,.login td,.alogin td{text-align:left} ';
$styles['default'] .= '.approve_waiting #action td:only-child,.help #backcredit,.login td:only-child,.alogin td:only-child,.init td:only-child{text-align:center} .sessions td,.sessions th,.approve_waiting td,.approve_waiting th{padding: 5px} ';
$styles['default'] .= '.sessions td td{padding: 1px} .notes textarea{height:80vh;width:80%} ';
$styles['default'] .= '.post table,.controls table,.login table{border-spacing:0px;margin-left:auto;margin-right:auto} .login table{border:2px solid} .controls{overflow-y:none} ';
if($class === 'init' || ! $db instanceof PDO){
return;
}
if($class === 'frameset'){
if(($U['status']>=5 || ($U['status']>2 && get_count_mods()==0)) && get_setting('enfileupload')>0 && get_setting('enfileupload')<=$U['status']){
$postheight='120px';
}else{
$postheight='100px';
}
if((!isset($_REQUEST['sort']) && !$U['sortupdown']) || (isset($_REQUEST['sort']) && $_REQUEST['sort']==0)) {
$styles[ 'frameset' ] = "#frameset-mid{position:fixed;top:$postheight;bottom:45px;left:0;right:0;margin:0;padding:0;overflow:hidden}";
$styles[ 'frameset' ] .= "#frameset-top{position:fixed;top:0;left:0;right:0;height:$postheight;margin:0;padding:0;overflow:hidden;border-bottom: 1px solid}";
$styles[ 'frameset' ] .= "#frameset-bot{position:fixed;bottom:0;left:0;right:0;height:45px;margin:0;padding:0;overflow:hidden;border-top:1px solid}";
} else{
$styles[ 'frameset' ] =" #frameset-mid{position:fixed;top:45px;bottom:$postheight;left:0;right:0;margin:0;padding:0;overflow:hidden}";
$styles[ 'frameset' ] .= "#frameset-top{position:fixed;top:0;left:0;right:0;height:45px;margin:0;padding:0;overflow:hidden;border-bottom:1px solid}";
$styles[ 'frameset' ] .= "#frameset-bot{position:fixed;bottom:0;left:0;right:0;height:$postheight;margin:0;padding:0;overflow:hidden;border-top:1px solid}";
}
}
if($class === 'filter'){
$styles['filter'] = 'table table{width:100%} ';
$styles['filter'] .= 'table table td:nth-child(1){width:8em;font-weight:bold} ';
$styles['filter'] .= 'table table td:nth-child(2),table table td:nth-child(3){width:12em} ';
$styles['filter'] .= 'table table td:nth-child(4){width:9em} ';
$styles['filter'] .= 'table table td:nth-child(5),table table td:nth-child(6),table table td:nth-child(7),table table td:nth-child(8){width:5em} ';
}
if($class === 'linkfilter'){
$styles['linkfilter'] = 'table table{width:100%} ';
$styles['linkfilter'] .= 'table table td:nth-child(1){width:8em;font-weight:bold} ';
$styles['linkfilter'] .= 'table table td:nth-child(2),table table td:nth-child(3){width:12em} ';
$styles['linkfilter'] .= 'table table td:nth-child(4),table table td:nth-child(5){width:5em} ';
}
if($class === 'post'){
$styles['post'] = '.spacer{width:10px} #firstline{vertical-align:top}';
}
if($class === 'messages'){
$styles['messages'] = '.nicklink{text-decoration:none}.channellink{text-decoration:underline}';
$styles['messages'] .= '#chatters{max-height:100px;overflow-y:auto} #chatters, #chatters table{border-spacing:0px} ';
$styles['messages'] .= '#manualrefresh{display:block;position:fixed;text-align:center;left:25%;width:50%;top:-200%;animation:timeout_messages ';
$styles['messages'] .= $U['refresh']+20;
$styles['messages'] .= 's forwards;z-index:2;background-color:#500000;border:2px solid #ff0000} ';
$styles['messages'] .= '@keyframes timeout_messages{0%{top:-200%} 99%{top:-200%} 100%{top:0%}} ';
$styles['messages'] .= '.msg{max-height:180px;overflow-y:auto} #bottom_link{position:fixed;top:0.5em;right:0.5em} #top_link{position:fixed;bottom:0.5em;right:0.5em} ';
$styles['messages'] .= '#chatters th,#chatters td{vertical-align:top} a img{width:15%} a:hover img{width:35%}';
$styles['messages'] .= '#messages{word-wrap:break-word}';
}
$css=get_setting('css');
$coltxt=get_setting('coltxt');
if(!empty($U['bgcolour'])){
$colbg=$U['bgcolour'];
}else{
$colbg=get_setting('colbg');
}
$styles['custom'] = preg_replace("/(\r?\n|\r\n?)/u", '', "body,iframe{background-color:#$colbg;color:#$coltxt} $css");
$allow_js = (bool) get_setting('allow_js');
if($allow_js){
$scripts['default'] = 'if(window.history.replaceState){window.history.replaceState(null,"");}';
if($class === 'frameset') {
$scripts[ 'frameset' ] = 'window.addEventListener("message", (e)=>{
if(e.data === "post_box_loaded"){
let autofocus = document.querySelector("iframe[name=post").contentDocument.querySelector("input[autofocus]");
if(autofocus){
autofocus.focus();
}
}
});';
}
if($class === 'post') {
$scripts[ 'post' ] = 'window.addEventListener("load", _=>{
window.top.postMessage("post_box_loaded", window.location.origin);
})';
}
}
}
function print_stylesheet(string $class): void
{
global $scripts, $styles;
//default css
echo "";
if ( $class === 'init' ) {
return;
}
if(isset($styles[$class])) {
echo "";
}
//overwrite with custom css
echo "";
$allow_js = (bool) get_setting( 'allow_js' );
if ( $allow_js ) {
echo "";
if(isset($scripts[$class])) {
echo "";
}
}
}
function print_end(): void
{
echo '