From 98f254dc75d3586bb06d9752fc02c7db94c7354f Mon Sep 17 00:00:00 2001 From: anonymous Date: Wed, 5 May 2010 13:56:25 +0100 Subject: [PATCH] Vanilla commit. --- .htaccess | 51 ++++ INSTALL.sql | 241 +++++++++++++++ README | 37 +++ action.php | 372 +++++++++++++++++++++++ atbbs.lighttpd.conf | 59 ++++ back_up_id.php | 38 +++ content_management.php | 56 ++++ dashboard.php | 187 ++++++++++++ date_and_time.php | 29 ++ drop_id.php | 31 ++ edit_content.php | 133 ++++++++ edit_ignore_list.php | 59 ++++ exterminate.php | 106 +++++++ failed_postings.php | 76 +++++ favicon.png | Bin 0 -> 123 bytes history.php | 92 ++++++ includes/.htaccess | 2 + includes/config.php | 47 +++ includes/footer.php | 8 + includes/functions.php | 751 +++++++++++++++++++++++++++++++++++++++++++++ includes/header.php | 71 +++++ includes/template.php | 96 ++++++ index.php | 145 +++++++++ ip_address.php | 170 +++++++++++ javascript/main.js | 101 +++++++ post.php | 788 ++++++++++++++++++++++++++++++++++++++++++++++++ profile.php | 178 +++++++++++ recover_id_by_email.php | 82 +++++ replies.php | 61 ++++ restore_id.php | 153 ++++++++++ search.php | 175 +++++++++++ statistics.php | 134 ++++++++ stuff.php | 49 +++ style/global.css | 321 ++++++++++++++++++++ topic.php | 355 ++++++++++++++++++++++ topic_trivia.php | 92 ++++++ trash_can.php | 35 +++ url_handler.php | 25 ++ watchlist.php | 64 ++++ 39 files changed, 5470 insertions(+) create mode 100644 .htaccess create mode 100644 INSTALL.sql create mode 100644 README create mode 100644 action.php create mode 100644 atbbs.lighttpd.conf create mode 100644 back_up_id.php create mode 100644 content_management.php create mode 100644 dashboard.php create mode 100644 date_and_time.php create mode 100644 drop_id.php create mode 100644 edit_content.php create mode 100644 edit_ignore_list.php create mode 100644 exterminate.php create mode 100644 failed_postings.php create mode 100644 favicon.png create mode 100644 history.php create mode 100644 includes/.htaccess create mode 100644 includes/config.php create mode 100644 includes/footer.php create mode 100644 includes/functions.php create mode 100644 includes/header.php create mode 100644 includes/template.php create mode 100644 index.php create mode 100644 ip_address.php create mode 100644 javascript/main.js create mode 100644 post.php create mode 100644 profile.php create mode 100644 recover_id_by_email.php create mode 100644 replies.php create mode 100644 restore_id.php create mode 100644 search.php create mode 100644 statistics.php create mode 100644 stuff.php create mode 100644 style/global.css create mode 100644 topic.php create mode 100644 topic_trivia.php create mode 100644 trash_can.php create mode 100644 url_handler.php create mode 100644 watchlist.php diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..6ecb571 --- /dev/null +++ b/.htaccess @@ -0,0 +1,51 @@ +php_flag magic_quotes_gpc Off +ErrorDocument 404 /url_handler.php + +RewriteEngine On +RewriteRule ^topic/([0-9]+)$ topic.php?id=$1 [L] +RewriteRule ^topics/?([0-9]+)?$ index.php?p=$1 [L] +RewriteRule ^bumps/?([0-9]+)?$ index.php?bumps=1&p=$1 [L] +RewriteRule ^history/?([0-9]+)?$ history.php?p=$1 [L] +RewriteRule ^replies/?([0-9]+)?$ replies.php?p=$1 [L] +RewriteRule ^watchlist$ watchlist.php [L] +RewriteRule ^new_topic$ post.php [L] +RewriteRule ^new_reply/([0-9]+)$ post.php?reply=$1 [L] +RewriteRule ^new_reply/([0-9]+)/quote_topic$ post.php?reply=$1"e_topic=1 [L] +RewriteRule ^new_reply/([0-9]+)/quote_reply/([0-9]+)$ post.php?reply=$1"e_reply=$2 [L] +RewriteRule ^new_reply/([0-9]+)/cite_reply/([0-9]+)$ post.php?reply=$1&cite=$2 [L] +RewriteRule ^edit_topic/([0-9]+)$ post.php?&edit=$1 [L] +RewriteRule ^edit_reply/([0-9]+)/([0-9]+)$ post.php?reply=$1&edit=$2 [L] +RewriteRule ^search$ search.php [L] +RewriteRule ^quick_search/(.+)$ search.php?q=$1 [L] +RewriteRule ^deep_search/(.+)?$ search.php?q=$1&deep_search=1 [L] +RewriteRule ^stuff$ stuff.php [L] +RewriteRule ^dashboard$ dashboard.php [L] +RewriteRule ^trash_can$ trash_can.php [L] +RewriteRule ^statistics$ statistics.php [L] +RewriteRule ^date_and_time$ date_and_time.php [L] +RewriteRule ^back_up_ID$ back_up_id.php [L] +RewriteRule ^generate_ID_card$ back_up_id.php?action=generate_id_card +RewriteRule ^restore_ID$ restore_id.php [L] +RewriteRule ^restore_ID/([A-Za-z0-9.]+)/([A-Za-z0-9]+)$ restore_id.php?UID=$1&password=$2 [L] +RewriteRule ^recover_ID_by_email$ recover_id_by_email.php [L] +RewriteRule ^drop_ID$ drop_id.php [L] +RewriteRule ^profile/([0-9a-zA-Z.]+)$ profile.php?uid=$1 [L] +RewriteRule ^failed_postings$ failed_postings.php [L] +RewriteRule ^IP_address/([0-9.]+)$ ip_address.php?ip=$1 [L] +RewriteRule ^edit_ignore_list$ edit_ignore_list.php [L] +RewriteRule ^ban_poster/([0-9a-zA-Z.]+)$ action.php?action=ban_uid&id=$1 [L] +RewriteRule ^unban_poster/([0-9a-zA-Z.]+)$ action.php?action=unban_uid&id=$1 [L] +RewriteRule ^unban_IP/([0-9.]+)$ action.php?action=unban_ip&id=$1 [L] +RewriteRule ^delete_IP_IDs/([0-9.]+)$ action.php?action=delete_ip_ids&id=$1 [L] +RewriteRule ^nuke_IP/([0-9.]+)$ action.php?action=nuke_ip&id=$1 [L] +RewriteRule ^nuke_ID/([0-9a-zA-Z.]+)$ action.php?action=nuke_id&id=$1 [L] +RewriteRule ^delete_topic/([0-9]+)$ action.php?action=delete_topic&id=$1 [L] +RewriteRule ^delete_reply/([0-9]+)$ action.php?action=delete_reply&id=$1 [L] +RewriteRule ^delete_page/([0-9]+)$ action.php?action=delete_page&id=$1 [L] +RewriteRule ^CMS$ content_management.php [L] +RewriteRule ^edit_page/([0-9]+)$ edit_content.php?edit=$1 [L] +RewriteRule ^new_page$ edit_content.php [L] +RewriteRule ^watch_topic$ action.php?action=watch_topic [L] +RewriteRule ^watch_topic/([0-9]+)$ action.php?action=watch_topic&id=$1 [L] +RewriteRule ^trivia_for_topic/([0-9]+)$ topic_trivia.php?id=$1 [L] +RewriteRule ^exterminate$ exterminate.php [L] \ No newline at end of file diff --git a/INSTALL.sql b/INSTALL.sql new file mode 100644 index 0000000..853ad11 --- /dev/null +++ b/INSTALL.sql @@ -0,0 +1,241 @@ +-- Import this SQL file to install ATBBS. + +SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO"; + + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; + + +-- Create tables: + +-- -------------------------------------------------------- + +-- +-- Table structure for table `activity` +-- + +CREATE TABLE `activity` ( + `uid` varchar(23) NOT NULL, + `time` int(10) NOT NULL, + `action_name` varchar(60) NOT NULL, + `action_id` int(10) NOT NULL, + PRIMARY KEY (`uid`), + KEY `time` (`time`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `failed_postings` +-- + +CREATE TABLE `failed_postings` ( + `uid` varchar(23) NOT NULL, + `time` int(10) NOT NULL, + `reason` text NOT NULL, + `headline` varchar(100) NOT NULL, + `body` text NOT NULL, + KEY `time` (`time`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `ignore_lists` +-- + +CREATE TABLE `ignore_lists` ( + `uid` varchar(23) NOT NULL, + `ignored_phrases` text NOT NULL, + PRIMARY KEY (`uid`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `images` +-- + +CREATE TABLE IF NOT EXISTS `images` ( + `file_name` varchar(80) NOT NULL, + `md5` varchar(32) NOT NULL, + `topic_id` int(10) unsigned DEFAULT NULL, + `reply_id` int(10) unsigned DEFAULT NULL, + UNIQUE KEY `reply_id` (`reply_id`), + UNIQUE KEY `topic_id` (`topic_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `ip_bans` +-- + +CREATE TABLE `ip_bans` ( + `ip_address` varchar(100) NOT NULL, + `filed` int(10) NOT NULL, + `expiry` int(10) unsigned NOT NULL default '0', + PRIMARY KEY (`ip_address`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `last_actions` +-- + +CREATE TABLE `last_actions` ( + `feature` varchar(30) NOT NULL, + `time` int(11) NOT NULL, + PRIMARY KEY (`feature`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `pages` +-- + +CREATE TABLE `pages` ( + `id` int(6) unsigned NOT NULL auto_increment, + `url` varchar(100) NOT NULL, + `page_title` varchar(200) NOT NULL, + `content` text NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `url` (`url`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `replies` +-- + +CREATE TABLE `replies` ( + `id` int(10) unsigned NOT NULL auto_increment, + `parent_id` int(10) NOT NULL, + `poster_number` int(10) NOT NULL, + `author` varchar(23) character set latin1 NOT NULL, + `author_ip` varchar(100) character set latin1 NOT NULL, + `time` int(10) NOT NULL, + `body` text character set latin1 NOT NULL, + `edit_time` int(10) default NULL, + `edit_mod` tinyint(1) default NULL, + PRIMARY KEY (`id`), + KEY `parent_id` (`parent_id`,`author`,`author_ip`), + KEY `letter` (`poster_number`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `topics` +-- + +CREATE TABLE `topics` ( + `id` int(11) unsigned NOT NULL auto_increment, + `time` int(10) unsigned NOT NULL, + `author` varchar(23) character set latin1 collate latin1_spanish_ci NOT NULL, + `author_ip` varchar(100) character set latin1 NOT NULL, + `replies` int(10) NOT NULL, + `last_post` int(10) NOT NULL, + `visits` int(10) NOT NULL default '0', + `headline` varchar(100) character set latin1 NOT NULL, + `body` text character set latin1 NOT NULL, + `edit_time` int(10) default NULL, + `edit_mod` tinyint(1) default NULL, + PRIMARY KEY (`id`), + KEY `author` (`author`), + KEY `author_ip` (`author_ip`), + KEY `last_post` (`last_post`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `trash` +-- + +CREATE TABLE `trash` ( + `uid` varchar(23) NOT NULL, + `time` int(10) NOT NULL, + `headline` varchar(100) NOT NULL, + `body` text NOT NULL, + KEY `uid` (`uid`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `uid_bans` +-- + +CREATE TABLE `uid_bans` ( + `uid` varchar(23) NOT NULL, + `filed` int(10) NOT NULL, + PRIMARY KEY (`uid`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `users` +-- + +CREATE TABLE `users` ( + `uid` varchar(23) character set latin1 NOT NULL, + `password` varchar(32) character set latin1 NOT NULL, + `first_seen` int(10) NOT NULL, + `ip_address` varchar(100) character set latin1 NOT NULL, + PRIMARY KEY (`uid`), + KEY `first_seen` (`first_seen`), + KEY `ip_address` (`ip_address`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `user_settings` +-- + +CREATE TABLE `user_settings` ( + `uid` varchar(23) character set latin1 NOT NULL, + `memorable_name` varchar(100) character set latin1 NOT NULL, + `memorable_password` varchar(100) character set latin1 NOT NULL, + `email` varchar(100) character set latin1 NOT NULL, + `spoiler_mode` tinyint(1) NOT NULL default '0', + `snippet_length` smallint(3) NOT NULL default '80', + `topics_mode` tinyint(1) NOT NULL, + `ostrich_mode` tinyint(1) NOT NULL default '0', + PRIMARY KEY (`uid`), + KEY `memorable_name` (`memorable_name`), + KEY `email` (`email`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `watchlists` +-- + +CREATE TABLE `watchlists` ( + `uid` varchar(23) NOT NULL, + `topic_id` int(10) NOT NULL, + KEY `uid` (`uid`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1; + +-- -------------------------------------------------------- + + +-- Insert some needed rows + +INSERT INTO `last_actions` (`feature`, `time`) VALUES +('last_bump', 0), +('last_topic', 0); + +INSERT INTO `pages` (`id`, `url`, `page_title`, `content`) VALUES +(1, 'FAQ', 'Frequently Asked Questions', '

How do I edit the FAQ?

\n

Use the content manager.

'), +(2, 'markup_syntax', 'Markup syntax', '\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n
OutputInput
Emphasis''''Emphasis''''
Strong emphasis''''''Strong emphasis''''''

Header

==Header==
> Quote> Quote
Link text[http://example.com/ Link text]
> Block
> quote
[quote]Block
quote[/quote]
'); \ No newline at end of file diff --git a/README b/README new file mode 100644 index 0000000..c21cd96 --- /dev/null +++ b/README @@ -0,0 +1,37 @@ +============================ +INSTALLATION +============================ + +Your host must have PHP version 5.2 or greater. To check your host's version of PHP, create a page called test.php with the content "". If it's less than 5.2, do not install ATBBS. + +- Create a MySQL database. Navigate to the database in phpmyadmin. Click the "import" tab and, as the file to import, select INSTALL.sql from the zip. Click Go. +- Edit the database details in /includes/config.php. For example, if your database is named 'durrbbs', change line 8 to 'database' => 'durrbbs' . +- Edit the other settings in config.php. In particular, change the DOMAIN definition to your domain. +- Upload the ATBBS files (except INSTALL.sql) to your root public directory. +- Go to the back up ID page (yourdomain.com/back_up_ID). Replace ADMIN ID HERE in config.php with the ID found on that page. You are now the administrator. + +============================ +NOTES +============================ + +- File encoding must always be UTF-8 _without BOM_. I recommend modifying files with Notepad++. +- If image uploading is enabled, /img/ and /thumbs/ need to be chmodded to 777. Your FTP client can do this. + +============================ +LICENSE +============================ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/action.php b/action.php new file mode 100644 index 0000000..560a126 --- /dev/null +++ b/action.php @@ -0,0 +1,372 @@ +prepare('SELECT 1 FROM watchlists WHERE uid = ? AND topic_id = ?'); + $check_watchlist->bind_param('si', $_SESSION['UID'], $id); + $check_watchlist->execute(); + $check_watchlist->store_result(); + if($check_watchlist->num_rows == 0) + { + $add_watchlist = $link->prepare('INSERT INTO watchlists (uid, topic_id) VALUES (?, ?)'); + $add_watchlist->bind_param('si', $_SESSION['UID'], $_POST['id']); + $add_watchlist->execute(); + $add_watchlist->close(); + } + $check_watchlist->close(); + + redirect('Topic added to your watchlist.'); + } + + break; + + //Priveleged actions. + + case 'delete_page': + + if( ! $administrator) + { + add_error('You are not wise enough.', true); + } + + if( ! ctype_digit($_GET['id'])) + { + add_error('Invalid ID.', true); + } + + $id = $_GET['id']; + $page_title = 'Delete page'; + + if(isset($_POST['id'])) + { + $file_uid_ban = $link->prepare('DELETE FROM pages WHERE id = ?'); + $file_uid_ban->bind_param('i', $id); + $file_uid_ban->execute(); + $file_uid_ban->close(); + + redirect('Page deleted.'); + } + + break; + + case 'ban_uid': + + if( ! $moderator && ! $administrator) + { + add_error('You are not wise enough.', true); + } + + if( ! id_exists($_GET['id'])) + { + add_error('There is no such user.', true); + } + + $id = $_GET['id']; + $page_title = 'Ban poster ' . $id; + + if(isset($_POST['id'])) + { + $file_uid_ban = $link->prepare('INSERT INTO uid_bans (uid, filed) VALUES (?, ?) ON DUPLICATE KEY UPDATE filed = ?'); + $file_uid_ban->bind_param('sii', $id, $_SERVER['REQUEST_TIME'], $_SERVER['REQUEST_TIME']); + $file_uid_ban->execute(); + $file_uid_ban->close(); + + redirect('User ID banned.'); + } + + break; + + case 'unban_uid': + + if( ! $moderator && ! $administrator) + { + add_error('You are not wise enough.', true); + } + + if( ! id_exists($_GET['id'])) + { + add_error('There is no such user.', true); + } + + $id = $_GET['id']; + $page_title = 'Unban poster ' . $id; + + if(isset($_POST['id'])) + { + remove_id_ban($id); + + redirect('User ID unbanned.'); + } + + break; + + case 'unban_ip': + + if( ! $moderator && ! $administrator) + { + add_error('You are not wise enough.', true); + } + + if( ! filter_var($_GET['id'], FILTER_VALIDATE_IP)) + { + add_error('That is not a valid IP address.', true); + } + + $id = $_GET['id']; + $page_title = 'Unban IP address ' . $id; + + if(isset($_POST['id'])) + { + remove_ip_ban($id); + + redirect('IP address unbanned.'); + } + + break; + + case 'delete_topic': + + if( ! $moderator && ! $administrator) + { + add_error('You are not wise enough.', true); + } + if( ! ctype_digit($_GET['id'])) + { + add_error('Invalid topic ID.', true); + } + + $id = $_GET['id']; + $page_title = 'Delete topic'; + + if(isset($_POST['id'])) + { + // Move record to user's trash. + $archive_topic = $link->prepare('INSERT INTO trash (uid, headline, body, time) SELECT topics.author, topics.headline, topics.body, UNIX_TIMESTAMP() FROM topics WHERE topics.id = ?;'); + $archive_topic->bind_param('i', $id); + $archive_topic->execute(); + $archive_topic->close(); + + // And delete it from the main table. + $delete_topic = $link->prepare('DELETE FROM topics WHERE id = ?'); + $delete_topic->bind_param('i', $id); + $delete_topic->execute(); + $delete_topic->close(); + + redirect('Topic archived and deleted.', ''); + } + + break; + + case 'delete_reply': + + if( ! $moderator && ! $administrator) + { + add_error('You are not wise enough.', true); + } + if( ! ctype_digit($_GET['id'])) + { + add_error('Invalid reply ID.', true); + } + + $id = $_GET['id']; + $page_title = 'Delete reply'; + + if(isset($_POST['id'])) + { + $fetch_parent = $link->prepare('SELECT parent_id FROM replies WHERE id = ?'); + $fetch_parent->bind_param('i', $id); + $fetch_parent->execute(); + $fetch_parent->bind_result($parent_id); + $fetch_parent->fetch(); + $fetch_parent->close(); + + if( ! $parent_id) + { + add_error('No such reply.', true); + } + + // Move record to user's trash. + $archive_reply = $link->prepare('INSERT INTO trash (uid, body, time) SELECT replies.author, replies.body, UNIX_TIMESTAMP() FROM replies WHERE replies.id = ?;'); + $archive_reply->bind_param('i', $id); + $archive_reply->execute(); + $archive_reply->close(); + + // And delete it from the main table. + $delete_reply = $link->prepare('DELETE FROM replies WHERE id = ?'); + $delete_reply->bind_param('i', $id); + $delete_reply->execute(); + $delete_reply->close(); + + // Reduce the parent's reply count. + $decrement = $link->prepare('UPDATE topics SET replies = replies - 1 WHERE id = ?'); + $decrement->bind_param('i', $parent_id); + $decrement->execute(); + $decrement->close(); + + redirect('Reply archived and deleted.'); + } + + break; + + case 'delete_ip_ids': + + if( ! $moderator && ! $administrator) + { + add_error('You are not wise enough.', true); + } + + if( ! filter_var($_GET['id'], FILTER_VALIDATE_IP)) + { + add_error('That is not a valid IP address.', true); + } + + $id = $_GET['id']; + $page_title = 'Delete IDs assigned to ' . $id . ''; + + if(isset($_POST['id'])) + { + $delete_ids = $link->prepare('DELETE FROM users WHERE ip_address = ?'); + $delete_ids->bind_param('s', $id); + $delete_ids->execute(); + $delete_ids->close(); + + redirect('IDs deleted.'); + } + + break; + + case 'nuke_id': + + if( ! $moderator && ! $administrator) + { + add_error('You are not wise enough.', true); + } + + if( ! id_exists($_GET['id'])) + { + add_error('There is no such user.', true); + } + + $id = $_GET['id']; + $page_title = 'Nuke all posts by ' . $id . ''; + + if(isset($_POST['id'])) + { + // Delete replies. + $fetch_parents = $link->prepare('SELECT parent_id FROM replies WHERE author = ?'); + $fetch_parents->bind_param('s', $id); + $fetch_parents->execute(); + $fetch_parents->bind_result($parent_id); + + $victim_parents = array(); + while($fetch_parents->fetch()) + { + $victim_parents[] = $parent_id; + } + $fetch_parents->close(); + + $delete_replies = $link->prepare('DELETE FROM replies WHERE author = ?'); + $delete_replies->bind_param('s', $id); + $delete_replies->execute(); + $delete_replies->close(); + + $decrement = $link->prepare('UPDATE topics SET replies = replies - 1 WHERE id = ?'); + foreach($victim_parents as $parent_id) + { + $decrement->bind_param('i', $parent_id); + $decrement->execute(); + } + $decrement->close(); + + // Delete topics. + $delete_topics = $link->prepare('DELETE FROM topics WHERE author = ?'); + $delete_topics->bind_param('s', $id); + $delete_topics->execute(); + $delete_topics->close(); + + redirect('All topics and replies by ' . $id . ' have been deleted.'); + } + + break; + + case 'nuke_ip': + + if( ! $moderator && ! $administrator) + { + add_error('You are not wise enough.', true); + } + + if( ! filter_var($_GET['id'], FILTER_VALIDATE_IP)) + { + add_error('That is not a valid IP address.', true); + } + + $id = $_GET['id']; + $page_title = 'Nuke all posts by ' . $id . ''; + + if(isset($_POST['id'])) + { + // Delete replies. + $fetch_parents = $link->prepare('SELECT parent_id FROM replies WHERE author_ip = ?'); + $fetch_parents->bind_param('s', $id); + $fetch_parents->execute(); + $fetch_parents->bind_result($parent_id); + + $victim_parents = array(); + while($fetch_parents->fetch()) + { + $victim_parents[] = $parent_id; + } + $fetch_parents->close(); + + $delete_replies = $link->prepare('DELETE FROM replies WHERE author_ip = ?'); + $delete_replies->bind_param('s', $id); + $delete_replies->execute(); + $delete_replies->close(); + + $decrement = $link->prepare('UPDATE topics SET replies = replies - 1 WHERE id = ?'); + foreach($victim_parents as $parent_id) + { + $decrement->bind_param('i', $parent_id); + $decrement->execute(); + } + $decrement->close(); + + // Delete topics. + $delete_topics = $link->prepare('DELETE FROM topics WHERE author_ip = ?'); + $delete_topics->bind_param('s', $id); + $delete_topics->execute(); + $delete_topics->close(); + + redirect('All topics and replies by ' . $id . ' have been deleted.'); + } + + break; + + default: + add_error('No valid action specified.', true); +} + +echo '

Really?

'; + +require('includes/footer.php'); + +?> \ No newline at end of file diff --git a/atbbs.lighttpd.conf b/atbbs.lighttpd.conf new file mode 100644 index 0000000..9ae076a --- /dev/null +++ b/atbbs.lighttpd.conf @@ -0,0 +1,59 @@ +# ATBBS Lighttpd Redirect File +# +# Usage: +# include from /etc/lighttpd/lighttpd.conf: +# +# $HTTP["host"] == "yourdomain.com" { +# include "atbbs.conf" +# } +# +server.error-handler-404 = "/url_handler.php" +url.rewrite-once = ( + "^topic/([0-9]+)$" => "topic.php?id=$1", + "^topics/?([0-9]+)?$" => "index.php?p=$1", + "^bumps/?([0-9]+)?$" => "index.php?bumps=1&p=$1", + "^history/?([0-9]+)?$" => "history.php?p=$1", + "^replies/?([0-9]+)?$" => "replies.php?p=$1", + "^watchlist$" => "watchlist.php", + "^new_topic$" => "post.php", + "^new_reply/([0-9]+)$" => "post.php?reply=$1", + "^new_reply/([0-9]+)/quote_topic$" => "post.php?reply=$1"e_topic=1", + "^new_reply/([0-9]+)/quote_reply/([0-9]+)$" => "post.php?reply=$1"e_reply=$2", + "^new_reply/([0-9]+)/cite_reply/([0-9]+)$" => "post.php?reply=$1&cite=$2", + "^edit_topic/([0-9]+)$" => "post.php?&edit=$1", + "^edit_reply/([0-9]+)/([0-9]+)$" => "post.php?reply=$1&edit=$2", + "^search$" => "search.php", + "^quick_search/(.+)$" => "search.php?q=$1", + "^deep_search/(.+)?$" => "search.php?q=$1&deep_search=1", + "^stuff$" => "stuff.php", + "^dashboard$" => "dashboard.php", + "^trash_can$" => "trash_can.php", + "^statistics$" => "statistics.php", + "^date_and_time$" => "date_and_time.php", + "^back_up_ID$" => "back_up_id.php", + "^generate_ID_card$" => "back_up_id.php?action=generate_id_card", + "^restore_ID$" => "restore_id.php", + "^restore_ID/([A-Za-z0-9.]+)/([A-Za-z0-9]+)$" => "restore_id.php?UID=$1&password=$2", + "^recover_ID_by_email$" => "recover_id_by_email.php", + "^drop_ID$" => "drop_id.php", + "^profile/([0-9a-zA-Z.]+)$" => "profile.php?uid=$1", + "^failed_postings$" => "failed_postings.php", + "^IP_address/([0-9.]+)$" => "ip_address.php?ip=$1", + "^edit_ignore_list$" => "edit_ignore_list.php", + "^ban_poster/([0-9a-zA-Z.]+)$" => "action.php?action=ban_uid&id=$1", + "^unban_poster/([0-9a-zA-Z.]+)$" => "action.php?action=unban_uid&id=$1", + "^unban_IP/([0-9.]+)$" => "action.php?action=unban_ip&id=$1", + "^delete_IP_IDs/([0-9.]+)$" => "action.php?action=delete_ip_ids&id=$1", + "^nuke_IP/([0-9.]+)$" => "action.php?action=nuke_ip&id=$1", + "^nuke_ID/([0-9a-zA-Z.]+)$" => "action.php?action=nuke_id&id=$1", + "^delete_topic/([0-9]+)$" => "action.php?action=delete_topic&id=$1", + "^delete_reply/([0-9]+)$" => "action.php?action=delete_reply&id=$1", + "^delete_page/([0-9]+)$" => "action.php?action=delete_page&id=$1", + "^CMS$" => "content_management.php", + "^edit_page/([0-9]+)$" => "edit_content.php?edit=$1", + "^new_page$" => "edit_content.php", + "^watch_topic$" => "action.php?action=watch_topic", + "^watch_topic/([0-9]+)$" => "action.php?action=watch_topic&id=$1", + "^trivia_for_topic/([0-9]+)$" => "topic_trivia.php?id=$1", + "^exterminate$" => "exterminate.php" +) diff --git a/back_up_id.php b/back_up_id.php new file mode 100644 index 0000000..6c236fd --- /dev/null +++ b/back_up_id.php @@ -0,0 +1,38 @@ + + + + + + + + + + + +
Your unique ID
Your password
+ +

You may want to download your ID card as a file.

+ + \ No newline at end of file diff --git a/content_management.php b/content_management.php new file mode 100644 index 0000000..506addf --- /dev/null +++ b/content_management.php @@ -0,0 +1,56 @@ + + +

This feature can be used to edit and create non-dynamic pages.

+ +define_columns($columns, 'Content snippet'); +$table->add_td_class('Content snippet', 'snippet'); + +$result = $link->query('SELECT id, url, page_title, content FROM pages'); + +while( $row = $result->fetch_assoc() ) +{ + $values = array + ( + '' . $row['url'] . '', + $row['page_title'], + snippet($row['content']), + '', + '' + ); + + $table->row($values); +} +$result->close(); +echo $table->output('pages'); + +?> + + + + \ No newline at end of file diff --git a/dashboard.php b/dashboard.php new file mode 100644 index 0000000..9818396 --- /dev/null +++ b/dashboard.php @@ -0,0 +1,187 @@ + '', + 'memorable_password' => '', + 'email' => '', + 'topics_mode' => 0, + 'spoiler_mode' => 0, + 'ostrich_mode' => 0, + 'snippet_length' => 80 +); +$user_config = $defaults; + +// These inputs have no simple valid settings. +$text_inputs = array +( + 'memorable_name', + 'memorable_password', + 'email' +); + +// ...but these do. +$valid_settings = array +( + 'topics_mode' => array('0', '1'), + 'spoiler_mode' => array('0', '1'), + 'ostrich_mode' => array('0', '1'), + 'snippet_length' => array('80', '100', '120', '140', '160') +); + +// Get our user's settings from the database. +$stmt = $link->prepare('SELECT memorable_name, memorable_password, email, spoiler_mode, topics_mode, ostrich_mode, snippet_length FROM user_settings WHERE uid = ?'); +$stmt->bind_param('s', $_SESSION['UID']); +$stmt->execute(); +$stmt->bind_result($user_config_db['memorable_name'], $user_config_db['memorable_password'], $user_config_db['email'], $user_config_db['spoiler_mode'], $user_config_db['topics_mode'], $user_config_db['ostrich_mode'], $user_config_db['snippet_length']); +$stmt->fetch(); +$stmt->close(); + +// If the values were set in the database, overwrite the defaults. +foreach($user_config_db as $key => $value) +{ + if( ! empty($key)) + { + $user_config[$key] = $value; + } +} + +if($_POST['form_sent']) +{ + // Unticked checkboxes are not sent by the client, so we need to set them ourselves. + foreach($defaults as $option => $setting) + { + if( ! array_key_exists($option, $_POST['form'])) + { + $_POST['form'][$option] = $setting; + } + } + + // Make some specific validations ... + if( ! empty($_POST['form']['memorable_name']) && $_POST['form']['memorable_name'] != $user_config['memorable_name']) + { + // Check if the name is already being used. + $check_name = $link->prepare('SELECT 1 FROM user_settings WHERE LOWER(memorable_name) = LOWER(?)'); + $check_name->bind_param('s', $_POST['form']['memorable_name']); + $check_name->execute(); + + $check_name->store_result(); + if($check_name->num_rows > 0) + { + add_error('The memorable name "' . htmlspecialchars($_POST['form']['memorable_name']) . '" is already being used.'); + } + + $check_name->close(); + } + + if( ! $erred) + { + //Iterate over every sent form[] value. + foreach($_POST['form'] as $key => $value) + { + // Check if the settings are valid ... + if( ! in_array($key, $text_inputs) && ( ! array_key_exists($key, $defaults) || ! in_array($value, $valid_settings[$key]) ) ) + { + continue; + } + if(strlen($value) > 100) + { + continue; + } + + // If the submitted setting differs from the current setting, update it. + if($user_config[$key] != $value) + { + // Insert or update! + $link->query('INSERT INTO user_settings (uid, ' . $link->real_escape_string($key). ') VALUES (\'' . $link->real_escape_string($_SESSION['UID']). '\', \'' . $link->real_escape_string($value) . '\') ON DUPLICATE KEY UPDATE ' . $link->real_escape_string($key). ' = \'' . $link->real_escape_string($value) . '\''); + + // Reset the value so it displays correctly on this page load. + $user_config[$key] = $value; + + // Text inputs never need to be set as cookies. + if(!in_array($key, $text_inputs)) + { + setcookie($key, $value, $_SERVER['REQUEST_TIME'] + 315569260); + } + } + } + } + + $_SESSION['notice'] = 'Settings updated.'; +} + +print_errors(); + +?> + + +
+ + +
+
+ + + +

This information can be used to more easily restore your ID. Password is optional, but recommended.

+
+ +
+ + + +

Used to recover your internal ID via e-mail.

+
+ +
+ + +
+ +
+ + + +

+
+ +
+ + /> + +

When enabled, snippets of the bodies will show in the topic list. Not recommended unless you have a very high-resolution screen.

+
+ +
+ + /> + +

When enabled, any topic or reply that contains a phrase from your ignore list will be hidden.

+
+ +
+ +
+ +
+ + \ No newline at end of file diff --git a/date_and_time.php b/date_and_time.php new file mode 100644 index 0000000..ad5780c --- /dev/null +++ b/date_and_time.php @@ -0,0 +1,29 @@ + + +

Today is the of in the year , week out of 52 and day out of (~%). The time is (or ). In international standard format: .

+ + diff --git a/drop_id.php b/drop_id.php new file mode 100644 index 0000000..dea96dc --- /dev/null +++ b/drop_id.php @@ -0,0 +1,31 @@ + + +

"Dropping" your ID will simply remove the UID, password, and mode cookies from your browser, effectively logging you out. If you want to keep your post history, settings, etc., back up your ID or set a memorable password before doing this.

+ +
+ +
+ + \ No newline at end of file diff --git a/edit_content.php b/edit_content.php new file mode 100644 index 0000000..fb47a31 --- /dev/null +++ b/edit_content.php @@ -0,0 +1,133 @@ +prepare('SELECT url, page_title, content FROM pages WHERE id = ?'); + $stmt->bind_param('i', $_GET['edit']); + $stmt->execute(); + $stmt->store_result(); + if($stmt->num_rows < 1) + { + $page_title = 'Non-existent page'; + add_error('There is no page with that ID.', true); + } + if( ! $_POST['form_sent']) + { + $stmt->bind_result($page_data['url'], $page_data['title'], $page_data['content']); + $stmt->fetch(); + } + $stmt->close(); + + $editing = true; + $page_title = 'Editing page: ' . htmlspecialchars($page_data['title']) . ''; + + $page_data['id'] = $_GET['edit']; +} +else // new page +{ + $page_title = 'New page'; + if( ! empty($page_data['title'])) + { + $page_title .= ': ' . htmlspecialchars($page_data['title']); + } +} + +if($_POST['post']) +{ + check_token(); + + if(empty($page_data['url'])) + { + add_error('A path is required.'); + } + + if( ! $erred) + { + // Undo the effects of sanitize_for_textarea: + $page_data['content'] = str_replace('/textarea', '/textarea', $page_data['content']); + + if($editing) + { + $edit_page = $link->prepare('UPDATE pages SET url = ?, page_title = ?, content = ? WHERE id = ?'); + $edit_page->bind_param('sssi', $page_data['url'], $page_data['title'], $page_data['content'], $page_data['id']); + $edit_page->execute(); + $edit_page->close(); + + $notice = 'Page successfully edited.'; + } + else // new page + { + $add_page = $link->prepare('INSERT INTO pages (url, page_title, content) VALUES (?, ?, ?)'); + $add_page->bind_param('sss', $page_data['url'], $page_data['title'], $page_data['content']); + $add_page->execute(); + $add_page->close(); + + $notice = 'Page successfully created.'; + } + + redirect($notice, $page_data['url']); + } +} + +print_errors(); + +if( $_POST['preview'] && ! empty($page_data['content']) && check_token() ) +{ + echo '

Preview

' . $page_data['title'] . '

' . $page_data['content'] . '
'; +} + +?> + +
+ +
+ +
+ +
+ + +
+ +
+ + +
+ +
+ +

Use pure HTML.

+
+ +
+ + +
+
+ + \ No newline at end of file diff --git a/edit_ignore_list.php b/edit_ignore_list.php new file mode 100644 index 0000000..ffa4968 --- /dev/null +++ b/edit_ignore_list.php @@ -0,0 +1,59 @@ +prepare('INSERT INTO ignore_lists (uid, ignored_phrases) VALUES (?, ?) ON DUPLICATE KEY UPDATE ignored_phrases = ?;'); + $update_ignore_list->bind_param('sss', $_SESSION['UID'], $_POST['ignore_list'], $_POST['ignore_list']); + $update_ignore_list->execute(); + $update_ignore_list->close(); + + $_SESSION['notice'] = 'Ignore list updated.'; + if($_COOKIE['ostrich_mode'] != 1) + { + $_SESSION['notice'] .= ' You must enable ostrich mode for this to have any effect.'; + } + } + else + { + $ignored_phrases = $_POST['ignore_list']; + } +} + +$fetch_ignore_list = $link->prepare('SELECT ignored_phrases FROM ignore_lists WHERE uid = ?'); +$fetch_ignore_list->bind_param('s', $_COOKIE['UID']); +$fetch_ignore_list->execute(); +$fetch_ignore_list->bind_result($ignored_phrases); +$fetch_ignore_list->fetch(); +$fetch_ignore_list->close(); + +print_errors(); + +?> + +

When ostrich mode is enabled, any topic or reply that contains a phrase on your ignore list will be hidden. Citations to hidden replies will be replaced with "@hidden". Enter one (case insensitive) phrase per line.

+ +
+
+ +
+ +
+ +
+ +
+ + \ No newline at end of file diff --git a/exterminate.php b/exterminate.php new file mode 100644 index 0000000..b297e2e --- /dev/null +++ b/exterminate.php @@ -0,0 +1,106 @@ +prepare('SELECT id, parent_id FROM replies WHERE body LIKE ? AND time > ?'); + $fetch_parents->bind_param('si', $phrase, $affect_posts_after); + $fetch_parents->execute(); + $fetch_parents->bind_result($reply_id, $parent_id); + + $victim_parents = array(); + while($fetch_parents->fetch()) + { + $victim_parents[] = $parent_id; + } + $fetch_parents->close(); + + $delete_replies = $link->prepare('DELETE FROM replies WHERE body LIKE ? AND time > ?'); + $delete_replies->bind_param('si', $phrase, $affect_posts_after); + $delete_replies->execute(); + $delete_replies->close(); + + $decrement = $link->prepare('UPDATE topics SET replies = replies - 1 WHERE id = ?'); + foreach($victim_parents as $parent_id) + { + $decrement->bind_param('i', $parent_id); + $decrement->execute(); + } + $decrement->close(); + + // Delete topics. + $delete_topics = $link->prepare('DELETE FROM topics WHERE body LIKE ? OR headline LIKE ? AND time > ?'); + $delete_topics->bind_param('ssi', $phrase, $phrase, $affect_posts_after); + $delete_topics->execute(); + $delete_topics->close(); + + $_SESSION['notice'] = 'Finished.'; + } +} + +$start_time = $_SERVER['REQUEST_TIME']; +$_SESSION['exterminate_start_time'] = $start_time; + +?> + +

This features removes all posts that contain anywhere in the body or headline the exact phrase that you specify.

+ +
+
+ +
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+ + \ No newline at end of file diff --git a/failed_postings.php b/failed_postings.php new file mode 100644 index 0000000..588b9cc --- /dev/null +++ b/failed_postings.php @@ -0,0 +1,76 @@ +prepare('SELECT time, uid, reason, headline, body FROM failed_postings ORDER BY time DESC LIMIT ?'); +$stmt->bind_param('i', $items_per_page); +$stmt->execute(); +$stmt->bind_result($fail_time, $fail_uid, $fail_reason, $fail_headline, $fail_body); + +$table = new table(); + +$columns = array +( + 'Error message', + 'Poster', + 'Age ▼' +); +if( ! $moderator && ! $administrator) +{ + array_splice($columns, 1, 1); +} + +$table->define_columns($columns, 'Error message'); + +while($stmt->fetch()) +{ + if(strlen($fail_body) > 600) + { + $fail_body = substr($fail_body, 0, 600) . ' …'; + } + + $tooltip = ''; + if(empty($fail_headline)) + { + $tooltip = $fail_body; + } + else if( ! empty($fail_body)) + { + $tooltip = 'Headline: ' . $fail_headline . ' Body: ' . $fail_body; + } + + $fail_reasons = unserialize($fail_reason); + $error_message = ''; + + $values = array + ( + $error_message, + '' . $fail_uid . '', + '' . calculate_age($fail_time) . '' + ); + if( ! $moderator && ! $administrator) + { + array_splice($values, 1, 1); + } + + $table->row($values); +} +$stmt->close(); +echo $table->output('failed postings'); + +require('includes/footer.php'); + +?> diff --git a/favicon.png b/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..73999067df8e811c2d441927abd2dc9af851ff5d GIT binary patch literal 123 zcwXxa@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|PM$7~Ar_~T6Bfw)ac4TR>R5`f zfwZ>xd~XBBKtJ^`=8gM~FP55bzM)NYUw~4Oz;YALLiZe>$qO1=I)3|`D5&c)GuU!5 V@}1NA7Xvhn!PC{xWt~$(698yZBCP-b literal 0 HcwPel00001 diff --git a/history.php b/history.php new file mode 100644 index 0000000..50b3319 --- /dev/null +++ b/history.php @@ -0,0 +1,92 @@ +prepare('SELECT id, time, replies, visits, headline FROM topics WHERE author = ? ORDER BY id DESC LIMIT ?, ?'); +$stmt->bind_param('sii', $_SESSION['UID'], $start_listing_at, $items_per_page); +$stmt->execute(); +$stmt->bind_result($topic_id, $topic_time, $topic_replies, $topic_visits, $topic_headline); + +$topics = new table(); +$columns = array +( + 'Headline', + 'Replies', + 'Visits', + 'Age ▼' +); +$topics->define_columns($columns, 'Headline'); +$topics->add_td_class('Headline', 'topic_headline'); + +while($stmt->fetch()) +{ + $values = array + ( + '' . htmlspecialchars($topic_headline) . '', + replies($topic_id, $topic_replies), + format_number($topic_visits), + '' . calculate_age($topic_time) . '' + ); + + $topics->row($values); +} +$stmt->close(); +$num_topics_fetched = $topics->num_rows_fetched; +echo $topics->output('topics'); + +/* REPLIES */ + +$stmt = $link->prepare('SELECT replies.id, replies.parent_id, replies.time, replies.body, topics.headline, topics.time FROM replies INNER JOIN topics ON replies.parent_id = topics.id WHERE replies.author = ? ORDER BY id DESC LIMIT ?, ?'); +$stmt->bind_param('sii', $_SESSION['UID'], $start_listing_at, $items_per_page); +$stmt->execute(); +$stmt->bind_result($reply_id, $parent_id, $reply_time, $reply_body, $topic_headline, $topic_time); + +$replies = new table(); +$columns = array +( + 'Reply snippet', + 'Topic', + 'Age ▼' +); +$replies->define_columns($columns, 'Topic'); +$replies->add_td_class('Topic', 'topic_headline'); +$replies->add_td_class('Reply snippet', 'reply_body_snippet'); + +while($stmt->fetch()) +{ + $values = array + ( + '' . snippet($reply_body) . '', + '' . htmlspecialchars($topic_headline) . ' (' . calculate_age($topic_time) . ' old)', + '' . calculate_age($reply_time) . '' + ); + + $replies->row($values); +} +$stmt->close(); +$num_replies_fetched = $replies->num_rows_fetched; +echo $replies->output('replies'); + +page_navigation('history', $current_page, $num_replies_fetched); + +require('includes/footer.php'); + +?> \ No newline at end of file diff --git a/includes/.htaccess b/includes/.htaccess new file mode 100644 index 0000000..281d5c3 --- /dev/null +++ b/includes/.htaccess @@ -0,0 +1,2 @@ +order allow,deny +deny from all diff --git a/includes/config.php b/includes/config.php new file mode 100644 index 0000000..deec303 --- /dev/null +++ b/includes/config.php @@ -0,0 +1,47 @@ + 'localhost', // Usually "localhost". If you don't know, consult your host's FAQs. + 'username' => 'change me', + 'password' => 'change me', + 'database' => 'change me' // The name you chose for the ATBBS database. +); + +/* IMPORTANT */ +define('SITE_TITLE', 'DefaultTalk'); // The title of your site, shown in the main header among other places. +define('DOMAIN', 'http://changeme.com/'); // Your site's domain, e.g., http://www.example.com/ -- INCLUDE TRAILING SLASH! +define('ADMIN_NAME', 'Admin'); // This display's instead of "Anonymous *" when you reply as an admin. +define('MAILER_ADDRESS', 'change@me.com'); // Your e-mail address. This will be used as the From: header for ID recovery e-mails. +define('SITE_FOUNDED', 1256083756); // CHANGE ME! The Unix timestamp of your site's founding (used by statistics.php) You can find the current Unix timestamp at http://www.unixtimestamp.com/ + +/* ETC */ +define('ALLOW_IMAGES', true); // allow image uploading? +define('MAX_IMAGE_SIZE', 1048576); // max image filesize in bytes +define('MAX_IMAGE_DIMENSIONS', 180); // maximum thumbnail height/width +define('SALT', 'random shitdfsfds'); // just type random shit, it's not important +define('BAN_PERIOD', 604800); // The period in seconds of all ID bans +define('ITEMS_PER_PAGE', 50); // the number of topics shown on the index, the number of replies on replies.php, etc. +define('MAX_LENGTH_HEADLINE', 100); // max length of headlines +define('MIN_LENGTH_HEADLINE', 3); // min length of headlines +define('MAX_LENGTH_BODY', 30000); // max length of post bodies +define('MIN_LENGTH_BODY', 3); // min length of post bodies +define('MAX_LINES', 450); // The maximum number of lines in a post body. +define('REQUIRED_LURK_TIME_REPLY', 15); // How long should new IDs have to wait until they can reply? +define('REQUIRED_LURK_TIME_TOPIC', 120); // How long should new IDs have to wait until they can post a topic? +define('FLOOD_CONTROL_REPLY', 4); // seconds an IP address must wait before posting another reply +define('FLOOD_CONTROL_TOPIC', 120); // seconds an IP address must wait before posting another topic +define('ALLOW_MODS_EXTERMINATE', false); // should mods (i.e., not just admins) be allowed to use the dangerous exterminator tool? +define('ALLOW_EDIT', true); // should normal users be able to edit their posts? +define('TIME_TO_EDIT', 600); // how long in seconds should normal users have to edit their new posts? + + +$moderators = array +( + 'MOD ID HERE' +); +$administrators = array +( + 'ADMIN ID HERE' +); +?> \ No newline at end of file diff --git a/includes/footer.php b/includes/footer.php new file mode 100644 index 0000000..b4da5de --- /dev/null +++ b/includes/footer.php @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/includes/functions.php b/includes/functions.php new file mode 100644 index 0000000..9eaea11 --- /dev/null +++ b/includes/functions.php @@ -0,0 +1,751 @@ +prepare('INSERT INTO users (uid, password, ip_address, first_seen) VALUES (?, ?, ?, UNIX_TIMESTAMP())'); + $stmt->bind_param('sss', $user_id, $password, $_SERVER['REMOTE_ADDR']); + $stmt->execute(); + + $_SESSION['first_seen'] = $_SERVER['REQUEST_TIME']; + $_SESSION['notice'] = 'Welcome to ' . SITE_TITLE . '. An account has automatically been created and assigned to you. You don\'t have to register or log in to use the board. Please don\'t clear your cookies unless you have set a memorable name and password.'; + + setcookie('UID', $user_id, $_SERVER['REQUEST_TIME'] + 315569260, '/'); + setcookie('password', $password, $_SERVER['REQUEST_TIME'] + 315569260, '/'); + $_SESSION['UID'] = $user_id; +} + +function generate_password() +{ + $characters = str_split('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'); + $password = ''; + + for($i = 0; $i < 32; ++$i) + { + $password .= $characters[array_rand($characters)]; + } + return $password; +} + +function activate_id() +{ + global $link; + + $stmt = $link->prepare('SELECT password, first_seen FROM users WHERE uid = ?'); + $stmt->bind_param('s', $_COOKIE['UID']); + $stmt->execute(); + $stmt->bind_result($db_password, $first_seen); + $stmt->fetch(); + $stmt->close(); + + if( ! empty($db_password) && $_COOKIE['password'] === $db_password) + { + // The password is correct! + $_SESSION['UID'] = $_COOKIE['UID']; + // Our ID wasn't just created. + $_SESSION['ID_activated'] = true; + // For post.php + $_SESSION['first_seen'] = $first_seen; + + return true; + } + + // If the password was wrong, create a new ID. + create_id(); +} + +function force_id() +{ + if( ! isset($_SESSION['ID_activated'])) + { + add_error('The page that you tried to access requires that you have a valid internal ID. This is supposed to be automatically created the first time you load a page here. Maybe you were linked directly to this page? Upon loading this page, assuming that you have cookies supported and enabled in your Web browser, you have been assigned a new ID. If you keep seeing this page, something is wrong with your setup; stop refusing/modifying/deleting cookies!', true); + } +} + +function update_activity($action_name, $action_id = '') +{ + global $link; + + if( ! isset($_SESSION['UID'])) + { + return false; + } + + $update_activity = $link->prepare('INSERT INTO activity (time, uid, action_name, action_id) VALUES (UNIX_TIMESTAMP(), ?, ?, ?) ON DUPLICATE KEY UPDATE time = UNIX_TIMESTAMP(), action_name = ?, action_id = ?;'); + $update_activity->bind_param('ssisi', $_SESSION['UID'], $action_name, $action_id, $action_name, $action_id); + $update_activity->execute(); + $update_activity->close(); +} + +function id_exists($id) +{ + global $link; + + $uid_exists = $link->prepare('SELECT 1 FROM users WHERE uid = ?'); + $uid_exists->bind_param('s', $_GET['id']); + $uid_exists->execute(); + $uid_exists->store_result(); + + if($uid_exists->num_rows < 1) + { + $uid_exists->close(); + return false; + } + + $uid_exists->close(); + return true; +} + +function remove_id_ban($id) +{ + global $link; + + $remove_ban = $link->prepare('DELETE FROM uid_bans WHERE uid = ?'); + $remove_ban->bind_param('s', $id); + $remove_ban->execute(); + $remove_ban->close(); +} + +function remove_ip_ban($ip) +{ + global $link; + + $remove_ban = $link->prepare('DELETE FROM ip_bans WHERE ip_address = ?'); + $remove_ban->bind_param('s', $ip); + $remove_ban->execute(); + $remove_ban->close(); +} + +function fetch_ignore_list() // For ostrich mode. +{ + global $link; + + if($_COOKIE['ostrich_mode'] == 1) + { + $fetch_ignore_list = $link->prepare('SELECT ignored_phrases FROM ignore_lists WHERE uid = ?'); + $fetch_ignore_list->bind_param('s', $_COOKIE['UID']); + $fetch_ignore_list->execute(); + $fetch_ignore_list->bind_result($ignored_phrases); + $fetch_ignore_list->fetch(); + $fetch_ignore_list->close(); + + // To make this work with Windows input, we need to strip out the return carriage. + $ignored_phrases = explode("\n", str_replace("\r", '', $ignored_phrases)); + + return $ignored_phrases; + } +} + +function show_trash($uid, $silence = false) // For profile and trash can. +{ + global $link; + + $output = ''; + + $fetch_trash = $link->prepare('SELECT headline, body, time FROM trash WHERE uid = ? ORDER BY time DESC'); + $fetch_trash->bind_param('s', $uid); + $fetch_trash->execute(); + $fetch_trash->bind_result($trash_headline, $trash_body, $trash_time); + + $table = new table(); + $columns = array + ( + 'Headline', + 'Body', + 'Time since deletion ▼' + ); + $table->define_columns($columns, 'Body'); + + while($fetch_trash->fetch()) + { + if(empty($trash_headline)) + { + $trash_headline = '(Reply.)'; + } + else + { + $trash_headline = htmlspecialchars($trash_headline); + } + + $values = array + ( + $trash_headline, + nl2br(htmlspecialchars($trash_body)), + '' . calculate_age($trash_time) . '' + ); + + $table->row($values); + } + $fetch_trash->close(); + + if($table->num_rows_fetched === 0) + { + return false; + } + return $table->output(); +} + +/* ============================================== + OUTPUT + ===============================================*/ + +// Prettify dynamic mark-up +function indent($num_tabs = 1) +{ + return "\n" . str_repeat("\t", $num_tabs); +} + +// Print a
Headline Body Time since deletion ▼
. 100 rows takes ~0.0035 seconds on my computer. +class table +{ + public $num_rows_fetched = 0; + + private $output = ''; + + private $primary_key; + private $columns = array(); + private $td_classes = array(); + + private $marker_printed = false; + private $last_seen = false; + private $order_time = false; + + public function define_columns($all_columns, $primary_column) + { + $this->columns = $all_columns; + + $this->output .= '
' . indent() . '' . indent(2) . ''; + + foreach($all_columns as $key => $column) + { + $this->output .= indent(3) . ' output .= ' class="minimal"'; + } + else + { + $this->primary_key = $key; + } + $this->output .= '>' . $column . ''; + } + + $this->output .= indent(2) . '' . indent() . '' . indent() . ''; + } + + public function add_td_class($column_name, $class) + { + $this->td_classes[$column_name] = $class; + } + + public function last_seen_marker($last_seen, $order_time) + { + $this->last_seen = $last_seen; + $this->order_time = $order_time; + } + + public function row($values) + { + // Print + $this->output .= indent(2) . 'num_rows_fetched & 1) + { + $this->output .= ' class="odd"'; + } + // Print the last seen marker. + if($this->last_seen && ! $this->marker_printed && $this->order_time <= $this->last_seen) + { + $this->marker_printed = true; + if($this->num_rows_fetched != 0) + { + $this->output .= ' id="last_seen_marker"'; + } + } + $this->output .= '>'; + + // Print each '; + } + + $this->output .= indent(2) . ''; + + $this->num_rows_fetched++; + } + + public function output($items = 'items', $silence = false) + { + $this->output .= indent() . '' . "\n" . '
+ foreach($values as $key => $value) + { + $classes = array(); + + $this->output .= indent(3) . 'primary_key) + { + $classes[] = 'minimal'; + } + // Check if a class has been added via add_td_class. + if( isset( $this->td_classes[ $this->columns[$key] ] ) ) + { + $classes[] = $this->td_classes[$this->columns[$key]]; + } + // Print any classes added by the above two conditionals. + if( ! empty($classes)) + { + $this->output .= ' class="' . implode(' ', $classes) . '"'; + } + + $this->output .= '>' . $value . '
' . "\n"; + + if($this->num_rows_fetched > 0) + { + return $this->output; + } + else if( ! $silence) + { + return '

(No ' . $items . ' to show.)

'; + } + + // Silence. + return ''; + } +} + + +function add_error($message, $critical = false) +{ + global $errors, $erred; + + $errors[] = $message; + $erred = true; + + if($critical) + { + print_errors(true); + } +} + +function print_errors($critical = false) +{ + global $errors; + + $number_errors = count($errors); + + if($number_errors > 0) + { + echo '

'; + if($number_errors > 1) + { + echo $number_errors . ' errors'; + } + else + { + echo 'Error'; + } + echo '

'; + + if($critical) + { + if( ! isset($page_title)) + { + $page_title = 'Fatal error'; + } + require('footer.php'); + exit; + } + } +} + +function page_navigation($section_name, $current_page, $num_items_fetched) +{ + $output = ''; + if($current_page != 1) + { + $output .= indent() . '
  • Latest
  • '; + } + if($current_page != 1 && $current_page != 2) + { + $newer = $current_page - 1; + $output .= indent() . '
  • Newer
  • '; + } + if($num_items_fetched == ITEMS_PER_PAGE) + { + $older = $current_page + 1; + $output .= indent() . '
  • Older
  • '; + } + + if( ! empty($output)) + { + echo "\n" . '' . "\n"; + } +} + +function edited_message($original_time, $edit_time, $edit_mod) +{ + if($edit_time) + { + echo '

    (Edited ' . calculate_age($original_time, $edit_time) . ' later'; + if($edit_mod) + { + echo ' by a moderator'; + } + echo '.)

    '; + } +} + +function dummy_form() +{ + echo "\n" . '
    ' . indent() . '
    ' . "\n" . '
    ' . "\n"; +} + +// To redirect to index, use redirect($notice, ''). To redirect back to referrer, +// use redirect($notice). To redirect to /topic/1, use redirect($notice, 'topic/1') +function redirect($notice = '', $location = NULL) +{ + if( ! empty($notice)) + { + $_SESSION['notice'] = $notice; + } + + if( ! is_null($location) || empty($_SERVER['HTTP_REFERER'])) + { + $location = DOMAIN . $location; + } + else + { + $location = $_SERVER['HTTP_REFERER']; + } + + header('Location: ' . $location); + exit; +} + +// Unused +function regenerate_config() +{ + global $link; + + $output = 'query('SELECT `option`, `value` FROM configuration'); + while( $row = $result->fetch_assoc() ) + { + if( ! ctype_digit($row['value'])) + { + $row['value'] = "'" . $row['value'] . "'"; + } + $output .= "define('" . strtoupper($row['option']) . "', " . $row['value'] . ");\n"; + } + $result->close(); + + $output .= "\n" . '?>'; + + file_put_contents('cache/config.php', $output, LOCK_EX); +} + +/* ============================================== + CHECKING + ===============================================*/ + +function check_length($text, $name, $min_length, $max_length) +{ + $text_length = strlen($text); + + if($min_length > 0 && empty($text)) + { + add_error('The ' . $name . ' cannot be blank.'); + } + else if($text_length > $max_length) + { + add_error('The ' . $name . ' was ' . number_format($text_length - $max_length) . ' characters over the limit (' . number_format($max_length) . ').'); + } + else if($text_length < $min_length) + { + add_error('The ' . $name . ' was too short.'); + } +} + +function check_tor($ip_address) //query TorDNSEL +{ + // Reverse the octets of our IP address. + $ip_address = implode('.', array_reverse( explode('.', $ip_address) )); + + // Returns true if Tor, false if not. 80.208.77.188.166 is of no significance. + return checkdnsrr($ip_address . '.80.208.77.188.166.ip-port.exitlist.torproject.org', 'A'); +} + +// Prevent cross-site redirection forgeries. +function csrf_token() +{ + if( ! isset($_SESSION['token'])) + { + $_SESSION['token'] = md5(SALT . mt_rand()); + } + echo '
    ' . "\n"; +} + +function check_token() +{ + if($_POST['CSRF_token'] !== $_SESSION['token']) + { + add_error('Session error. Try again.'); + return false; + } + return true; +} + +/* ============================================== + FORMATTING + ===============================================*/ + +function parse($text) +{ + $text = htmlspecialchars($text); + $text = str_replace("\r", '', $text); + + $markup = array + ( + // Strong emphasis. + "/'''(.+?)'''/", + // Emphasis. + "/''(.+?)''/", + // Linkify URLs. + '@\b(?$1', + '$1', + '$1://$2$3$4$5', + '$3', + '> $1', + '

    $1

    ' + ); + + $text = preg_replace($markup, $html, $text); + return nl2br($text); +} + +function snippet($text, $snippet_length = 80) +{ + $patterns = array + ( + "/'''?(.*?)'''?/", // strip formatting + '/^(@|>)(.*)/m' //replace quotes and citations + ); + + $replacements = array + ( + '$1', + ' ~ ' + ); + + $text = preg_replace($patterns, $replacements, $text); + $text = str_replace( array("\r", "\n"), ' ', $text ); // strip line breaks + $text = htmlspecialchars($text); + + if(ctype_digit($_COOKIE['snippet_length'])) + { + $snippet_length = $_COOKIE['snippet_length']; + } + if(strlen($text) > $snippet_length) + { + $text = substr($text, 0, $snippet_length) . '…'; + } + return $text; +} + +function super_trim($text) +{ + // Strip return carriage and non-printing characters. + $nonprinting_characters = array + ( + "\r", + '­', //soft hyphen ( U+00AD) + '', // zero width no-break space ( U+FEFF) + '​', // zero width space (U+200B) + '‍', // zero width joiner (U+200D) + '‌' // zero width non-joiner (U+200C) + ); + $text = str_replace($nonprinting_characters, '', $text); + //Trim and kill excessive newlines (maximum of 3) + return preg_replace( '/(\r?\n[ \t]*){3,}/', "\n\n\n", trim($text) ); +} + +function sanitize_for_textarea($text) +{ + $text = str_ireplace('/textarea', '/textarea', $text); + $text = str_replace('