Browse Source

merged mod branch

pull/40/head
Savetheinternet 13 years ago
parent
commit
afbd0c6473
  1. 34
      LICENSE
  2. 47
      README.md
  3. 0
      default.css
  4. BIN
      img/fade-yotsuba.png
  5. 369
      inc/config.php
  6. 49
      inc/database.php
  7. 193
      inc/display.php
  8. 724
      inc/functions.php
  9. 44
      inc/instance-config.php
  10. 187
      inc/mod.php
  11. 2
      inc/template.php
  12. 101
      inc/user.php
  13. 244
      install.php
  14. 63
      install.sql
  15. 69
      main.js
  16. 661
      mod.php
  17. 357
      post.php
  18. 3
      res/index.php
  19. 3
      src/index.php
  20. BIN
      static/deleted.png
  21. 0
      static/error.png
  22. BIN
      static/locked.gif
  23. 0
      static/ok.png
  24. BIN
      static/sticky.gif
  25. 0
      static/warning.png
  26. 0
      static/zip.png
  27. 103
      style.css
  28. 47
      templates/index.html
  29. 28
      templates/login.html
  30. 8
      templates/page.html
  31. 25
      templates/posts.sql
  32. 41
      templates/thread.html
  33. 112
      test.php
  34. 3
      thumb/index.php
  35. 40
      yotsuba.css

34
LICENSE

@ -1,13 +1,21 @@
Copyright (c) 2010 by Omega Software Development Group
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above copyright
notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
Copyright (c) 2010-2011, Omega Software Development Group <http://omegasdg.com/>
Permission to use, copy, modify, and/or distribute this software is granted,
provided that the following terms are obeyed.
1. The above copyright notice, this permission notice, and these terms must
appear in all copies and modifications of this software.
2. This software must not be utilized in any manner that is primarily intended
for or directed toward commercial advantage or private monetary compensation.
3. Any changes to this software must be made easily publicly available in the
form of source code.
THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.

47
README.md

@ -5,24 +5,26 @@ Tinyboard is an imageboard software package written in PHP. It aims to maintain
Tinyboard is not currently at a stable state.
[o]: http://omegadev.org/
[o]: http://omegasdg.com/
## Requirements
1. PHP >= 5.2.0
2. php-gd
3. php-pdo with appropriate driver for your database
## Installation
1. Tinyboard requires a MySQL database and a user to work. Create one.
1. Tinyboard requires an SQL database and a user to work. Create one.
2. Import 'install.sql' into the database. There are several ways to do this.
- using phpMyAdmin
- `mysql -uUSERNAME -pPASSWORD DATABASE < install.sql`
3. Edit 'instance-config.php' to suit your installation. You should copy some values from '[inc/config.php][c]' to redefine them in the instance-config.
4. Make sure that the directories used by Tinyboard are writable. Depending on your setup, you may need to `chmod` the directories to 777.
The default directories are:
- ./res
- ./src
- ./thumb
- . (document root)
4. Make sure that the Tinyboard directory is writable. Depending on your setup, you may need to `chmod` "." to 777, with `chmod 777 .`
5. Ensure everything is okay by running [test.php][t] in a browser. The script will try and help you correct your errors.
6. Run the [post.php][p] script. It should create an index.html and redirect you to it if everything is okay.
7. Optional (highly recommended): Either delete or chmod as unreadable the following files: [test.php][t], [install.sql][i], and this [README][r].
An automated installation script will be completed soon.
[t]: http://github.com/savetheinternet/Tinyboard/blob/master/test.php
[p]: http://github.com/savetheinternet/Tinyboard/blob/master/post.php
[c]: http://github.com/savetheinternet/Tinyboard/blob/master/inc/config.php
@ -30,17 +32,24 @@ Tinyboard is not currently at a stable state.
[r]: http://github.com/savetheinternet/Tinyboard/blob/master/README.md
## License
Copyright (c) 2010 by Omega Software Development Group
Copyright (c) 2010-2011, Omega Software Development Group <http://omegasdg.com/>
Permission to use, copy, modify, and/or distribute this software is granted,
provided that the following terms are obeyed.
1. The above copyright notice, this permission notice, and these terms must
appear in all copies and modifications of this software.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above copyright
notice and this permission notice appear in all copies.
2. This software must not be utilized in any manner that is primarily intended
for or directed toward commercial advantage or private monetary compensation.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
3. Any changes to this software must be made easily publicly available in the
form of source code.
THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.

0
default.css

BIN
img/fade-yotsuba.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

369
inc/config.php

@ -9,176 +9,325 @@
*
*/
$config = Array(
'db' => Array(),
'cookies' => Array(),
'error' => Array(),
'dir' => Array(),
'mod' => Array()
);
// Database stuff
define('MY_SERVER', 'localhost', true);
define('MY_USER', '', true);
define('MY_PASSWORD', '', true);
define('MY_DATABASE', '', true);
// The name of the session cookie (PHP's $_SESSION)
define('SESS_COOKIE', 'imgboard', true);
// Used to safely determine when the user was first seen, to prevent floods.
// time()
define('TIME_COOKIE', 'arrived', true);
// HASH_COOKIE contains an MD5 hash of TIME_COOKIE+SALT for verification.
define('HASH_COOKIE', 'hash', true);
// Used for moderation login
define('MOD_COOKIE', 'mod', true);
// Where to set the 'path' parameter to ROOT when creating cookies. Recommended.
define('JAIL_COOKIES', true, true);
// SQL driver ("mysql", "pgsql", "sqlite", "dblib", etc)
// http://www.php.net/manual/en/pdo.drivers.php
$config['db']['type'] = 'mysql';
// Hostname or IP address
$config['db']['server'] = 'localhost';
// Login
$config['db']['user'] = '';
$config['db']['password'] = '';
// Tinyboard database
$config['db']['database'] = '';
// Anything more to add to the DSN string (eg. port=xxx;foo=bar)
$config['db']['dsn'] = '';
// Whether or not to lock moderator sessions to the IP address that was logged in with.
define('MOD_LOCK_IP', true, true);
// Mod
// The name of the session cookie (PHP's $_SESSION)
$config['cookies']['session']= 'imgboard';
// Used to safely determine when the user was first seen, to prevent floods. Contains a UNIX timestamp.
$config['cookies']['time'] = 'arrived';
// Contains an MD5 hash of $config['cookies']['time'] for verification.
$config['cookies']['hash'] = 'hash';
// Used for moderation login
$config['cookies']['mod'] = 'mod';
// Where to set the 'path' parameter to $config['root'] when creating cookies. Recommended.
$config['cookies']['jail'] = true;
// How long should the cookies last (in seconds)
define('COOKIE_EXPIRE', 15778463, true); //6 months
define('SALT', 'wefaw98YHEWUFuo', true);
$config['cookies']['expire']= 15778463; //6 months
// Make this something long and random for security
$config['cookies']['salt'] = 'wefaw98YHEWUFuo';
// How long should moderators should remain logged in (0=browser session) (in seconds)
$config['mod']['expire'] = 15778463; //6 months
// Used to salt secure tripcodes (##trip)
$config['secure_trip_salt'] = '@#$&^@#)$(*&@!_$(&329-8347';
// How many seconds before you can post, after the first visit
define('LURKTIME', 30, true);
$config['lurktime'] = 30;
// How many seconds between each post
$config['flood_time'] = 10;
// How many seconds between each post with exactly the same content and same IP
$config['flood_time_ip'] = 120;
// Same as above but different IP address
$config['flood_time_same'] = 30;
// Do you need a body for your non-OP posts?
$config['force_body'] = true;
// Max body length
define('MAX_BODY', 1800, true);
$config['max_body'] = 1800;
define('THREADS_PER_PAGE', 10, true);
define('MAX_PAGES', 5, true);
define('THREADS_PREVIEW', 5, true);
$config['threads_per_page'] = 10;
$config['max_pages'] = 10;
$config['threads_preview'] = 5;
// For development purposes. Turns 'display_errors' on. Not recommended for production.
define('VERBOSE_ERRORS', true, true);
$config['verbose_errors'] = true;
// Error messages
define('ERROR_LURK', 'Lurk some more before posting.', true);
define('ERROR_BOT', 'You look like a bot.', true);
define('ERROR_TOOLONG', 'The %s field was too long.', true);
define('ERROR_TOOLONGBODY', 'The body was too long.', true);
define('ERROR_TOOSHORTBODY', 'The body was too short or empty.', true);
define('ERROR_NOIMAGE', 'You must upload an image.', true);
define('ERROR_NOMOVE', 'The server failed to handle your upload.', true);
define('ERROR_FILEEXT', 'Unsupported image format.', true);
define('ERROR_NOBOARD', 'Invalid board!', true);
define('ERROR_NONEXISTANT', 'Thread specified does not exist.', true);
define('ERROR_NOPOST', 'You didn\'t make a post.', true);
define('ERR_INVALIDIMG','Invalid image.', true);
define('ERR_FILESIZE', 'Maximum file size: %maxsz% bytes<br>Your file\'s size: %filesz% bytes', true);
define('ERR_MAXSIZE', 'The file was too big.', true);
define('ERR_INVALIDZIP','Invalid archive!', true);
$config['error']['lurk'] = 'Lurk some more before posting.';
$config['error']['bot'] = 'You look like a bot.';
$config['error']['toolong'] = 'The %s field was too long.';
$config['error']['toolong_body'] = 'The body was too long.';
$config['error']['tooshort_body'] = 'The body was too short or empty.';
$config['error']['noimage'] = 'You must upload an image.';
$config['error']['nomove'] = 'The server failed to handle your upload.';
$config['error']['fileext'] = 'Unsupported image format.';
$config['error']['noboard'] = 'Invalid board!';
$config['error']['nonexistant'] = 'Thread specified does not exist.';
$config['error']['locked'] = 'Thread locked. You may not reply at this time.';
$config['error']['nopost'] = 'You didn\'t make a post.';
$config['error']['flood'] = 'Flood detected; Post discared.';
$config['error']['unoriginal'] = 'Unoriginal content!';
$config['error']['muted'] = 'Unoriginal content! You have been muted for %d seconds.';
$config['error']['youaremuted'] = 'You are muted! Expires in %d seconds.';
$config['error']['tor'] = 'Hmm… That looks like a Tor exit node.';
$config['error']['toomanylinks'] = 'Too many links; flood detected.';
$config['error']['nodelete'] = 'You didn\'t select anything to delete.';
$config['error']['invalidpassword'] = 'Wrong password…';
$config['error']['invalidimg'] = 'Invalid image.';
$config['error']['filesize'] = 'Maximum file size: %maxsz% bytes<br>Your file\'s size: %filesz% bytes';
$config['error']['maxsize'] = 'The file was too big.';
$config['error']['invalidzip'] = 'Invalid archive!';
// Moderator errors
define('ERROR_INVALID', 'Invalid username and/or password.', true);
define('ERROR_INVALIDAFTER', 'Invalid username and/or password. Your user may have been deleted or changed.');
define('ERROR_MALFORMED','Invalid/malformed cookies.', true);
$config['error']['invalid'] = 'Invalid username and/or password.';
$config['error']['notamod'] = 'You are not a mod…';
$config['error']['invalidafter'] = 'Invalid username and/or password. Your user may have been deleted or changed.';
$config['error']['malformed'] = 'Invalid/malformed cookies.';
$config['error']['missedafield'] = 'Your browser didn\'t submit an input when it should have.';
$config['error']['required'] = 'The %s field is required.';
$config['error']['invalidfield'] = 'The %s field was invalid.';
$config['error']['boardexists'] = 'There is already a %s board.';
$config['error']['noaccess'] = 'You don\'t have permission to do that.';
$config['error']['invalidpost'] = 'That post doesn\'t exist…';
$config['error']['404'] = 'Page not found.';
// Reply limit (deletes thread when this is reached)
$config['reply_limit'] = 250;
// For resizing, max values
define('THUMB_WIDTH', 200, true);
define('THUMB_HEIGHT', 200, true);
$config['thumb_width'] = 255;
$config['thumb_height'] = 255;
// Store image hash in the database for r9k-like boards implementation soon
// Function name for hashing
// sha1_file, md5_file, etc.
$config['file_hash'] = 'sha1_file';
$config['block_tor'] = true;
// Typically spambots try to post a lot of links. Refuse a post with X standalone links?
$config['max_links'] = 20;
// Maximum image upload size in bytes
define('MAX_FILESIZE', 10*1024*1024, true); // 10MB
$config['max_filesize'] = 10*1024*1024; // 10MB
// Maximum image dimensions
define('MAX_WIDTH', 10000, true);
define('MAX_HEIGHT', MAX_WIDTH, true);
/* When you upload a ZIP as a file, all the images inside the archive
* get dumped into the thread as replies.
* Extremely beta and not recommended yet.
*/
define('ALLOW_ZIP', false, true);
define('ZIP_IMAGE', 'src/zip.png', true);
$config['max_width'] = 1000;
$config['max_height'] = $config['max_width']; // 1:1
/**
Redraw the image using GD functions to strip any excess data (commonly ZIP archives)
WARNING: Very beta. Currently strips animated GIFs too :(
WARNING: Currently strips animated GIFs too :(
**/
define('REDRAW_IMAGE', false, true);
$config['redraw_image'] = false;
// Redrawing configuration
define('JPEG_QUALITY', 100, true);
define('REDRAW_GIF', false, true);
$config['jpeg_quality'] = 100;
// Temporary fix for the animation-stripping bug
$config['redraw_gifs'] = false;
// Display the aspect ratio in a post's file info
define('SHOW_RATIO', true, true);
define('DIR_IMG', 'src/', true);
define('DIR_THUMB', 'thumb/', true);
define('DIR_RES', 'res/', true);
// Where to store the .html templates. This folder and templates must exist or fatal errors will be thrown.
define('DIR_TEMPLATE', getcwd() . '/templates', true);
$config['show_ratio'] = true;
// The root directory, including the trailing slash, for Tinyboard.
// examples: '/', 'http://boards.chan.org/', '/chan/'
define('ROOT', '/', true);
$config['root'] = '/';
$config['dir']['img'] = 'src/';
$config['dir']['thumb'] = 'thumb/';
$config['dir']['res'] = 'res/';
// For load balancing, having a seperate server (and domain/subdomain) for serving static content is possible.
// This can either be a directory or a URL (eg. http://static.example.org/)
$config['dir']['static'] = $config['root'] . 'static/';
// Where to store the .html templates. This folder and templates must exist or fatal errors will be thrown.
$config['dir']['template'] = getcwd() . '/templates';
// Static images
// These can be URLs OR base64 (data URI scheme)
$config['image_sticky'] = $config['dir']['static'] . 'sticky.gif';
$config['image_locked'] = $config['dir']['static'] . 'locked.gif';
$config['image_deleted'] = $config['dir']['static'] . 'deleted.png';
$config['image_zip'] = $config['dir']['static'] . 'zip.png';
// If for some reason the folders and static HTML index files aren't in the current working direcotry,
// enter the directory path here. Otherwise, keep it false.
define('ROOT_FILE', false, true);
$config['root_file'] = false;
define('POST_URL', ROOT . 'post.php', true);
define('FILE_INDEX', 'index.html', true);
define('FILE_PAGE', '%d.html', true);
$config['file_index'] = 'index.html';
$config['file_page'] = '%d.html';
$config['file_mod'] = 'mod.php';
// Multi-board (%s is board abbreviation)
define('BOARD_PATH', '%s/', true);
$config['board_path'] = '%s/';
// The HTTP status code to use when redirecting.
// Should be 3xx (redirection). http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
// "302" is recommended.
$config['redirect_http'] = 302;
// TODO: Put this in per-board instance-config instead
// Robot stuff
// Strip repeating characters when making hashes
$config['robot_enable'] = false;
$config['robot_strip_repeating'] = true;
// Enable mutes
// Tinyboard uses ROBOT9000's original 2^x implementation
$config['robot_mute'] = true;
// How many mutes x hours ago to include in the algorithm
$config['robot_mute_hour'] = 50;
// If you want to alter the algorithm a bit. Default value is 2. n^x
$config['robot_mute_multiplier'] = 2;
$config['robot_mute_descritpion'] = 'You have been muted for unoriginal content.';
/*
Mod stuff
*/
// Whether or not to lock moderator sessions to the IP address that was logged in with.
$config['mod']['lock_ip'] = true;
// The page that is first shown when a moderator logs in. Defaults to the dashboard.
$config['mod']['default'] = '/';
// Don't even display MySQL password to administrators (in the configuration page).
$config['mod']['never_reveal_password'] = true;
// Do a DNS lookup on IP addresses to get their hostname on the IP summary page
$config['mod']['dns_lookup'] = true;
// Show ban form on the IP summary page
$config['mod']['ip_banform'] = true;
// How many recent posts, per board, to show in the IP summary page
$config['mod']['ip_recentposts'] = 5;
// Probably best not to change these:
define('JANITOR', 0, true);
define('MOD', 1, true);
define('ADMIN', 2, true);
// Permissions
// What level of administration you need to:
/* Post Controls */
// View IP addresses
$config['mod']['show_ip'] = MOD;
// Delete a post
$config['mod']['delete'] = JANITOR;
// Ban a user for a post
$config['mod']['ban'] = MOD;
// Ban and delete (one click; instant)
$config['mod']['bandelete'] = MOD;
// Delete file (and keep post)
$config['mod']['deletefile'] = JANITOR;
// Delete all posts by IP
$config['mod']['deletebyip'] = MOD;
// Sticky a thread
$config['mod']['sticky'] = MOD;
// Lock a thread
$config['mod']['lock'] = MOD;
// Post in a locked thread
$config['mod']['postinlocked'] = MOD;
// Post bypass unoriginal content check
$config['mod']['postunoriginal'] = MOD;
// Raw HTML posting
$config['mod']['rawhtml'] = MOD;
/* Administration */
// Display the contents of instance-config.php
$config['mod']['show_config'] = ADMIN;
// View list of bans
$config['mod']['view_banlist'] = MOD;
// View the username of the mod who made a ban
$config['mod']['view_banstaff'] = MOD;
// If the moderator doesn't fit the $config['mod']['view_banstaff''] (previous) permission,
// show him just a "?" instead. Otherwise, it will be "Mod" or "Admin"
$config['mod']['view_banquestionmark'] = false;
// Show expired bans in the ban list (they are kept in cache until the culprit returns)
$config['mod']['view_banexpired'] = true;
// Create a new board
$config['mod']['newboard'] = ADMIN;
// Mod links (full HTML)
// Correspond to above permission directives
$config['mod']['link_delete'] = '[D]';
$config['mod']['link_ban'] = '[B]';
$config['mod']['link_bandelete'] = '[B&amp;D]';
$config['mod']['link_deletefile'] = '[F]';
$config['mod']['link_deletebyip'] = '[D+]';
$config['mod']['link_sticky'] = '[Sticky]';
$config['mod']['link_desticky'] = '[-Sticky]';
$config['mod']['link_lock'] = '[Lock]';
$config['mod']['link_unlock'] = '[-Lock]';
// A small file in the main directory indicating that the script has been ran and the board(s) have been generated.
// This keeps the script from querying the database and causing strain when not needed.
define('HAS_INSTALLED', '.installed', true);
$config['has_installed'] = '.installed';
// Name of the boards. Typically '/%s/' (/b/, /mu/, etc)
// BOARD_ABBREVIATION - BOARD_TITLE
define('BOARD_ABBREVIATION', '/%s/', true);
// Name of the boards. Usually '/%s/' (/b/, /mu/, etc)
// $config['board_abbreviation'] - BOARD_TITLE
$config['board_abbreviation'] = '/%s/';
// Automatically convert things like "..." to Unicode characters ("…")
define('AUTO_UNICODE', true, true);
$config['auto_unicode'] = true;
// Use some Wiki-like syntax (''em'', '''strong''', ==Heading==, etc)
define('WIKI_MARKUP', true, true);
$config['wiki_markup'] = true;
// Whether to turn URLs into functional links
define('MARKUP_URLS', true, true);
$config['markup_urls'] = true;
// Complex regular expression to catch URLs
define('URL_REGEX', '/' . '(https?|ftp):\/\/' . '([\w\-]+\.)+[a-zA-Z]{2,6}' . '(\/([\w\-~\.#\/?=&;:+%]+))?' . '/', true);
$config['url_regex'] = '/' . '(https?|ftp):\/\/' . '(([\w\-]+\.)+[a-zA-Z]{2,6}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' . '(\/([\w\-~\.#\/?=&;:+%]+)?)?' . '/';
// Allowed file extensions
$allowed_ext = Array('jpg', 'jpeg', 'bmp', 'gif', 'png', true);
$config['allowed_ext'] = Array('jpg', 'jpeg', 'bmp', 'gif', 'png');
define('BUTTON_NEWTOPIC', 'New Topic', true);
define('BUTTON_REPLY', 'New Reply', true);
// The names on the post buttons. (On most imageboards, these are both "Post".)
$config['button_newtopic'] = 'New Topic';
$config['button_reply'] = 'New Reply';
define('ALWAYS_NOKO', false, true);
// The string passed to date() for post times
// http://php.net/manual/en/function.date.php
$config['post_date'] = 'm/d/y (D) H:i:s';
define('URL_MATCH', '/^' .
(@$_SERVER['HTTPS']?'https':'http').':\/\/'.$_SERVER['HTTP_HOST'] .
// Always act as if they had typed "noko" in the email field no mattter what
$config['always_noko'] = false;
$config['url_match'] = '/^' .
(preg_match($config['url_regex'], $config['root']) ? '' :
(@$_SERVER['HTTPS']?'https':'http') .
':\/\/'.$_SERVER['HTTP_HOST']) .
preg_quote($config['root'], '/') .
'(' .
preg_quote(ROOT, '/') .
str_replace('%s', '\w{1,8}', preg_quote(BOARD_PATH, '/')) .
str_replace('%s', '\w{1,8}', preg_quote($config['board_path'], '/')) .
'|' .
str_replace('%s', '\w{1,8}', preg_quote($config['board_path'], '/')) .
preg_quote($config['file_index'], '/') .
'|' .
preg_quote(ROOT, '/') .
str_replace('%s', '\w{1,8}', preg_quote(BOARD_PATH, '/')) .
preg_quote(FILE_INDEX, '/') .
str_replace('%s', '\w{1,8}', preg_quote($config['board_path'], '/')) .
str_replace('%d', '\d+', preg_quote($config['file_page'], '/')) .
'|' .
preg_quote(ROOT, '/') .
str_replace('%s', '\w{1,8}', preg_quote(BOARD_PATH, '/')) .
str_replace('%d', '\d+', preg_quote(FILE_PAGE, '/')) .
')$/', true);
preg_quote($config['file_mod'], '/') .
'\?\/.+' .
')$/i';
if(ROOT_FILE) {
chdir(ROOT_FILE);
if($config['root_file']) {
chdir($config['root_file']);
}
if(VERBOSE_ERRORS) {
if($config['verbose_errors']) {
error_reporting(E_ALL);
ini_set('display_errors', 1);
}
/*
Multi-board support removes any use for this.
if(!defined('IS_INSTALLATION')) {
if(!file_exists(DIR_IMG)) @mkdir(DIR_IMG, 0777) or error("Couldn't create " . DIR_IMG . ". Install manually.", true);
if(!file_exists(DIR_THUMB)) @mkdir(DIR_THUMB, 0777) or error("Couldn't create " . DIR_IMG . ". Install manually.", true);
if(!file_exists(DIR_RES)) @mkdir(DIR_RES, 0777) or error("Couldn't create " . DIR_IMG . ". Install manually.", true);
}
*/
?>

49
inc/database.php

@ -0,0 +1,49 @@
<?php
function sql_open() {
global $pdo, $config;
if($pdo) return true;
$dsn = $config['db']['type'] . ':host=' . $config['db']['server'] . ';dbname=' . $config['db']['database'];
if(!empty($config['db']['dsn']))
$dsn .= ';' . $config['db']['dsn'];
try {
return $pdo = new PDO($dsn, $config['db']['user'], $config['db']['password']);
} catch(PDOException $e) {
$message = $e->getMessage();
// Remove any sensitive information
$message = str_replace($config['db']['user'], '<em>hidden</em>', $message);
$message = str_replace($config['db']['password'], '<em>hidden</em>', $message);
// Print error
error('Database error: ' . $message);
}
}
function sql_close() {
global $pdo;
$pdo = NULL;
}
function prepare($query) {
global $pdo;
return $pdo->prepare($query);
}
function query($query) {
global $pdo;
return $pdo->query($query);
}
function db_error($PDOStatement=null) {
global $pdo;
if(isset($PDOStatement)) {
$err = $PDOStatement->errorInfo();
return $err[2];
} else {
$err = $pdo->errorInfo();
return $err[2];
}
}
?>

193
inc/display.php

@ -20,27 +20,32 @@
}
function error($message) {
global $board;
global $board, $mod, $config;
if(function_exists('sql_close')) sql_close();
die(Element('page.html', Array(
'index'=>ROOT,
'index'=>$config['root'],
'title'=>'Error',
'subtitle'=>'An error has occured.',
'body'=>"<center>" .
"<h2>$message</h2>" .
(isset($board) ? "<p><a href=\"" . ROOT . $board['dir'] . FILE_INDEX . "\">Go back</a>.</p>" : '').
(isset($board) ?
"<p><a href=\"" . $config['root'] .
($mod ? $config['file_mod'] . '?/' : '') .
$board['dir'] . $config['file_index'] . "\">Go back</a>.</p>" : '').
"</center>"
)));
}
function loginForm($error=false, $username=false) {
global $config;
if(function_exists('sql_close')) sql_close();
die(Element('page.html', Array(
'index'=>ROOT,
'index'=>$config['root'],
'title'=>'Login',
'body'=>Element('login.html', Array(
'index'=>ROOT,
'index'=>$config['root'],
'error'=>$error,
'username'=>$username
)
@ -49,7 +54,10 @@
}
class Post {
public function __construct($id, $thread, $subject, $email, $name, $trip, $body, $time, $thumb, $thumbx, $thumby, $file, $filex, $filey, $filesize, $filename) {
public function __construct($id, $thread, $subject, $email, $name, $trip, $body, $time, $thumb, $thumbx, $thumby, $file, $filex, $filey, $filesize, $filename, $ip, $root=null, $mod=false) {
global $config;
if(!isset($root)) $root = $config['root'];
$this->id = $id;
$this->thread = $thread;
$this->subject = utf8tohtml($subject);
@ -66,12 +74,59 @@
$this->filey = $filey;
$this->filesize = $filesize;
$this->filename = $filename;
$this->ip = $ip;
$this->root = $root;
$this->mod = $mod;
if($this->mod)
// Fix internal links
// Very complicated regex
$this->body = preg_replace(
'/<a(([a-zA-Z]+="[^"]+")|[a-zA-Z]+=[a-zA-Z]+|\s)*href="' . preg_quote($config['root'], '/') . '(' . sprintf(preg_quote($config['board_path'], '/'), '\w+') . ')/',
'<a href="?/$3',
$this->body
);
}
public function postControls() {
global $board, $config;
$built = '';
if($this->mod) {
// Mod controls (on posts)
$built .= '<span class="controls">';
// Delete
if($this->mod['type'] >= $config['mod']['delete'])
$built .= ' <a title="Delete" href="?/' . $board['uri'] . '/delete/' . $this->id . '">' . $config['mod']['link_delete'] . '</a>';
// Delete all posts by IP
if($this->mod['type'] >= $config['mod']['deletebyip'])
$built .= ' <a title="Delete all posts by IP" href="?/' . $board['uri'] . '/deletebyip/' . $this->id . '">' . $config['mod']['link_deletebyip'] . '</a>';
// Ban
if($this->mod['type'] >= $config['mod']['ban'])
$built .= ' <a title="Ban" href="?/' . $board['uri'] . '/ban/' . $this->id . '">' . $config['mod']['link_ban'] . '</a>';
// Ban & Delete
if($this->mod['type'] >= $config['mod']['bandelete'])
$built .= ' <a title="Ban & Delete" href="?/' . $board['uri'] . '/ban&amp;delete/' . $this->id . '">' . $config['mod']['link_bandelete'] . '</a>';
// Delete file (keep post)
if(!empty($this->file) && $this->mod['type'] >= $config['mod']['deletefile'])
$built .= ' <a title="Remove file" href="?/' . $board['uri'] . '/deletefile/' . $this->id . '">' . $config['mod']['link_deletefile'] . '</a>';
$built .= '</span>';
}
return $built;
}
public function build($index=false) {
global $board;
global $board, $config;
$built = '<div class="post reply"' . (!$index?' id="reply_' . $this->id . '"':'') . '>' .
'<p class="intro"' . (!$index?' id="' . $this->id . '"':'') . '>';
'<p class="intro"' . (!$index?' id="' . $this->id . '"':'') . '>' .
// Delete
'<input type="checkbox" class="delete" name="delete_' . $this->id . '" id="delete_' . $this->id . '" /><label for="delete_' . $this->id . '">';
// Subject
if(!empty($this->subject))
@ -84,39 +139,52 @@
// Trip
. (!empty($this->trip) ? ' <span class="trip">'.$this->trip.'</span>':'');
// IP Address
if($this->mod && $this->mod['type'] >= $config['mod']['show_ip']) {
$built .= ' [<a style="margin:0;" href="?/IP/' . $this->ip . '">' . $this->ip . '</a>]';
}
// End email
if(!empty($this->email))
$built .= '</a>';
// Date/time
$built .= ' ' . date('m/d/y (D) H:i:s', $this->time);
$built .= ' ' . date($config['post_date'], $this->time);
// End delete
$built .= '</label>';
$built .= ' <a class="post_no"' .
// JavaScript highlight
($index?'':' onclick="highlightReply(' . $this->id . ');"') .
' href="' . ROOT . $board['dir'] . DIR_RES . $this->thread . '.html' . '#' . $this->id . '">No.</a>' .
' href="' . $this->root . $board['dir'] . $config['dir']['res'] . $this->thread . '.html' . '#' . $this->id . '">No.</a>' .
// JavaScript cite
'<a class="post_no"' . ($index?'':'onclick="citeReply(' . $this->id . ');"') . 'href="' . ($index?ROOT . $board['dir'] . DIR_RES . $this->thread . '.html' . '#q' . $this->id:'javascript:void(0);') . '">'.$this->id.'</a>' .
'<a class="post_no"' . ($index?'':' onclick="citeReply(' . $this->id . ');"') . ' href="' . ($index?$this->root . $board['dir'] . $config['dir']['res'] . $this->thread . '.html' . '#q' . $this->id:'javascript:void(0);') . '">'.$this->id.'</a>' .
'</p>';
// File info
if(!empty($this->file)) {
$built .= '<p class="fileinfo">File: <a href="' . ROOT . $board['dir'] . DIR_IMG . $this->file .'">' . $this->file . '</a> <span class="unimportant">(' .
if(!empty($this->file) && $this->file != 'deleted') {
$built .= '<p class="fileinfo">File: <a href="' . $config['root'] . $board['dir'] . $config['dir']['img'] . $this->file .'">' . $this->file . '</a> <span class="unimportant">(' .
// Filesize
format_bytes($this->filesize) . ', ' .
// File dimensions
$this->filex . 'x' . $this->filey;
// Aspect Ratio
if(SHOW_RATIO) {
if($config['show_ratio']) {
$fraction = fraction($this->filex, $this->filey, ':');
$built .= ', ' . $fraction;
}
// Filename
$built .= ', ' . $this->filename . ')</span></p>' .
// Thumbnail
'<a href="' . ROOT . $board['dir'] . DIR_IMG . $this->file.'"><img src="' . ROOT . $board['dir'] . DIR_THUMB . $this->thumb.'" style="width:'.$this->thumbx.'px;height:'.$this->thumby.'px;" /></a>';
'<a href="' . $config['root'] . $board['dir'] . $config['dir']['img'] . $this->file.'"><img src="' . $config['root'] . $board['dir'] . $config['dir']['thumb'] . $this->thumb.'" style="width:'.$this->thumbx.'px;height:'.$this->thumby.'px;" /></a>';
} elseif($this->file == 'deleted') {
$built .= '<img src="' . $config['image_deleted'] . '" />';
}
$built .= $this->postControls();
// Body
$built .= '<p class="body">' . $this->body . '</p></div><br class="clear"/>';
@ -126,7 +194,10 @@
class Thread {
public $omitted = 0;
public function __construct($id, $subject, $email, $name, $trip, $body, $time, $thumb, $thumbx, $thumby, $file, $filex, $filey, $filesize, $filename) {
public function __construct($id, $subject, $email, $name, $trip, $body, $time, $thumb, $thumbx, $thumby, $file, $filex, $filey, $filesize, $filename, $ip, $sticky, $locked, $root=null, $mod=false) {
global $config;
if(!isset($root)) $root = $config['root'];
$this->id = $id;
$this->subject = utf8tohtml($subject);
$this->email = $email;
@ -144,32 +215,91 @@
$this->filename = $filename;
$this->omitted = 0;
$this->posts = Array();
$this->ip = $ip;
$this->sticky = $sticky;
$this->locked = $locked;
$this->root = $root;
$this->mod = $mod;
if($this->mod)
// Fix internal links
// Very complicated regex
$this->body = preg_replace(
'/<a(([a-zA-Z]+="[^"]+")|[a-zA-Z]+=[a-zA-Z]+|\s)*href="' . preg_quote($config['root'], '/') . '(' . sprintf(preg_quote($config['board_path'], '/'), '\w+') . ')/',
'<a href="?/$3',
$this->body
);
}
public function add(Post $post) {
$this->posts[] = $post;
}
public function postControls() {
global $board, $config;
$built = '';
if($this->mod) {
// Mod controls (on posts)
$built .= '<span class="controls op">';
// Delete
if($this->mod['type'] >= $config['mod']['delete'])
$built .= ' <a title="Delete" href="?/' . $board['uri'] . '/delete/' . $this->id . '">' . $config['mod']['link_delete'] . '</a>';
// Delete all posts by IP
if($this->mod['type'] >= $config['mod']['deletebyip'])
$built .= ' <a title="Delete all posts by IP" href="?/' . $board['uri'] . '/deletebyip/' . $this->id . '">' . $config['mod']['link_deletebyip'] . '</a>';
// Ban
if($this->mod['type'] >= $config['mod']['ban'])
$built .= ' <a title="Ban" href="?/' . $board['uri'] . '/ban/' . $this->id . '">' . $config['mod']['link_ban'] . '</a>';
// Ban & Delete
if($this->mod['type'] >= $config['mod']['bandelete'])
$built .= ' <a title="Ban & Delete" href="?/' . $board['uri'] . '/ban&amp;delete/' . $this->id . '">' . $config['mod']['link_bandelete'] . '</a>';
// Stickies
if($this->mod['type'] >= $config['mod']['sticky'])
if($this->sticky)
$built .= ' <a title="Make thread not sticky" href="?/' . $board['uri'] . '/unsticky/' . $this->id . '">' . $config['mod']['link_desticky'] . '</a>';
else
$built .= ' <a title="Make thread sticky" href="?/' . $board['uri'] . '/sticky/' . $this->id . '">' . $config['mod']['link_sticky'] . '</a>';
// Lock
if($this->mod['type'] >= $config['mod']['lock'])
if($this->locked)
$built .= ' <a title="Lock thread" href="?/' . $board['uri'] . '/unlock/' . $this->id . '">' . $config['mod']['link_unlock'] . '</a>';
else
$built .= ' <a title="Unlock thread" href="?/' . $board['uri'] . '/lock/' . $this->id . '">' . $config['mod']['link_lock'] . '</a>';
$built .= '</span>';
}
return $built;
}
public function build($index=false) {
global $board;
global $board, $config;
$built = '<p class="fileinfo">File: <a href="' . ROOT . $board['dir'] . DIR_IMG . $this->file .'">' . $this->file . '</a> <span class="unimportant">(' .
$built = '<p class="fileinfo">File: <a href="' . $config['root'] . $board['dir'] . $config['dir']['img'] . $this->file .'">' . $this->file . '</a> <span class="unimportant">(' .
// Filesize
format_bytes($this->filesize) . ', ' .
// File dimensions
$this->filex . 'x' . $this->filey;
// Aspect Ratio
if(SHOW_RATIO) {
if($config['show_ratio']) {
$fraction = fraction($this->filex, $this->filey, ':');
$built .= ', ' . $fraction;
}
// Filename
$built .= ', ' . $this->filename . ')</span></p>' .
// Thumbnail
'<a href="' . ROOT . $board['dir'] . DIR_IMG . $this->file.'"><img src="' . ROOT . $board['dir'] . DIR_THUMB . $this->thumb.'" style="width:'.$this->thumbx.'px;height:'.$this->thumby.'px;" /></a>';
'<a href="' . $config['root'] . $board['dir'] . $config['dir']['img'] . $this->file.'"><img src="' . $config['root'] . $board['dir'] . $config['dir']['thumb'] . $this->thumb.'" style="width:'.$this->thumbx.'px;height:'.$this->thumby.'px;" /></a>';
$built .= '<div class="post op"><p class="intro"' . (!$index?' id="' . $this->id . '"':'') . '>';
// Delete
$built .= '<input type="checkbox" class="delete" name="delete_' . $this->id . '" id="delete_' . $this->id . '" /><label for="delete_' . $this->id . '">';
// Subject
if(!empty($this->subject))
$built .= '<span class="subject">' . $this->subject . '</span> ';
@ -181,21 +311,36 @@
// Trip
. (!empty($this->trip) ? ' <span class="trip">'.$this->trip.'</span>':'');
// IP Address
if($this->mod && $this->mod['type'] >= $config['mod']['show_ip']) {
$built .= ' [<a style="margin:0;" href="?/IP/' . $this->ip . '">' . $this->ip . '</a>]';
}
// End email
if(!empty($this->email))
$built .= '</a>';
// Date/time
$built .= ' ' . date('m/d/y (D) H:i:s', $this->time);
$built .= ' ' . date($config['post_date'], $this->time);
// End delete
$built .= '</label>';
$built .= ' <a class="post_no"' .
// JavaScript highlight
($index?'':' onclick="highlightReply(' . $this->id . ');"') .
' href="' . ROOT . $board['dir'] . DIR_RES . $this->id . '.html' . '#' . $this->id . '">No.</a>' .
' href="' . $this->root . $board['dir'] . $config['dir']['res'] . $this->id . '.html' . '#' . $this->id . '">No.</a>' .
// JavaScript cite
'<a class="post_no"' . ($index?'':'onclick="citeReply(' . $this->id . ');"') . 'href="' . ($index?ROOT . $board['dir'] . DIR_RES . $this->id . '.html' . '#q' . $this->id:'javascript:void(0);') . '">'.$this->id.'</a>' .
'<a class="post_no"' . ($index?'':' onclick="citeReply(' . $this->id . ');"') . ' href="' . ($index?$this->root . $board['dir'] . $config['dir']['res'] . $this->id . '.html' . '#q' . $this->id:'javascript:void(0);') . '">'.$this->id.'</a>' .
// Sticky
($this->sticky ? '<img class="icon" title="Sticky" src="' . $config['image_sticky'] . '" />' : '') .
// Locked
($this->locked ? '<img class="icon" title="Locked" src="' . $config['image_locked'] . '" />' : '') .
// [Reply]
($index ? '<a href="' . ROOT . $board['dir'] . DIR_RES . $this->id . '.html">[Reply]</a>' : '') .
($index ? '<a href="' . $this->root . $board['dir'] . $config['dir']['res'] . $this->id . '.html">[Reply]</a>' : '') .
// Mod controls
$this->postControls() .
'</p>';
// Body

724
inc/functions.php

@ -8,25 +8,8 @@
array_values($replaces), $str);
}
function sql_open() {
global $sql;
$sql = @mysql_connect(MY_SERVER, MY_USER, MY_PASSWORD) or error('Database error.');
@mysql_select_db(MY_DATABASE, $sql) or error('Database error.');
}
function sql_close() {
global $sql;
@mysql_close($sql);
}
function mysql_safe_array(&$array) {
foreach($array as &$item) {
$item = mysql_real_escape_string($item);
}
}
function setupBoard($array) {
global $board;
global $board, $config;
$board = Array(
'id' => $array['id'],
@ -34,166 +17,514 @@
'name' => $array['title'],
'title' => $array['subtitle']);
$board['dir'] = sprintf(BOARD_PATH, $board['uri']);
$board['url'] = sprintf(BOARD_ABBREVIATION, $board['uri']);
$board['dir'] = sprintf($config['board_path'], $board['uri']);
$board['url'] = sprintf($config['board_abbreviation'], $board['uri']);
if(!file_exists($board['dir'])) mkdir($board['dir'], 0777);
if(!file_exists($board['dir'] . DIR_IMG)) @mkdir($board['dir'] . DIR_IMG, 0777) or error("Couldn't create " . DIR_IMG . ". Check permissions.", true);
if(!file_exists($board['dir'] . DIR_THUMB)) @mkdir($board['dir'] . DIR_THUMB, 0777) or error("Couldn't create " . DIR_THUMB . ". Check permissions.", true);
if(!file_exists($board['dir'] . DIR_RES)) @mkdir($board['dir'] . DIR_RES, 0777) or error("Couldn't create " . DIR_RES . ". Check permissions.", true);
if(!file_exists($board['dir'] . $config['dir']['img'])) @mkdir($board['dir'] . $config['dir']['img'], 0777) or error("Couldn't create " . $config['dir']['img'] . ". Check permissions.", true);
if(!file_exists($board['dir'] . $config['dir']['thumb'])) @mkdir($board['dir'] . $config['dir']['thumb'], 0777) or error("Couldn't create " . $config['dir']['thumb'] . ". Check permissions.", true);
if(!file_exists($board['dir'] . $config['dir']['res'])) @mkdir($board['dir'] . $config['dir']['res'], 0777) or error("Couldn't create " . $config['dir']['res'] . ". Check permissions.", true);
}
function openBoard($uri) {
global $sql;
$boards_res = mysql_query(sprintf(
"SELECT * FROM `boards` WHERE `uri` = '%s' LIMIT 1",
mysql_real_escape_string($uri)
), $sql) or error(mysql_error($sql));
if($_board = mysql_fetch_array($boards_res)) {
setupBoard($_board);
sql_open();
$query = prepare("SELECT * FROM `boards` WHERE `uri` = :uri LIMIT 1");
$query->bindValue(':uri', $uri);
$query->execute() or error(db_error($query));
if($board = $query->fetch()) {
setupBoard($board);
return true;
} else return false;
}
function listBoards() {
$query = query("SELECT * FROM `boards` ORDER BY `uri`") or error(db_error());
$boards = $query->fetchAll();
return $boards;
}
function checkFlood($post) {
global $board, $config;
$query = prepare(sprintf("SELECT * FROM `posts_%s` WHERE (`ip` = :ip AND `time` >= :floodtime) OR (`ip` = :ip AND `body` = :body AND `time` >= :floodsameiptime) OR (`body` = :body AND `time` >= :floodsametime) LIMIT 1", $board['uri']));
$query->bindValue(':ip', $_SERVER['REMOTE_ADDR']);
$query->bindValue(':body', $post['body'], PDO::PARAM_INT);
$query->bindValue(':floodtime', time()-$config['flood_time'], PDO::PARAM_INT);
$query->bindValue(':floodsameiptime', time()-$config['flood_time_ip'], PDO::PARAM_INT);
$query->bindValue(':floodsametime', time()-$config['flood_time_same'], PDO::PARAM_INT);
$query->execute() or error(db_error($query));
return (bool)$query->fetch();
}
function until($timestamp) {
$difference = $timestamp - time();
if($difference < 60) {
return $difference . ' second' . ($difference != 1 ? 's' : '');
} elseif($difference < 60*60) {
return ($num = round($difference/(60))) . ' minute' . ($num != 1 ? 's' : '');
} elseif($difference < 60*60*24) {
return ($num = round($difference/(60*60))) . ' hour' . ($num != 1 ? 's' : '');
} elseif($difference < 60*60*24*7) {
return ($num = round($difference/(60*60*24))) . ' day' . ($num != 1 ? 's' : '');
} elseif($difference < 60*60*24*365) {
return ($num = round($difference/(60*60*24*7))) . ' week' . ($num != 1 ? 's' : '');
} else {
return ($num = round($difference/(60*60*24*365))) . ' year' . ($num != 1 ? 's' : '');
}
}
function formatDate($timestamp) {
return date('jS F, Y', $timestamp);
}
function checkBan() {
global $config;
if(!isset($_SERVER['REMOTE_ADDR'])) {
// Server misconfiguration
return;
}
$query = prepare("SELECT * FROM `bans` WHERE `ip` = :ip LIMIT 1");
$query->bindValue(':ip', $_SERVER['REMOTE_ADDR']);
$query->execute() or error(db_error($query));
if($ban = $query->fetch()) {
if($ban['expires'] && $ban['expires'] < time()) {
// Ban expired
$query = prepare("DELETE FROM `bans` WHERE `ip` = :ip AND `expires` = :expires LIMIT 1");
$query->bindValue(':ip', $_SERVER['REMOTE_ADDR']);
$query->bindValue(':expires', $ban['expires'], PDO::PARAM_INT);
$query->execute() or error(db_error($query));
return;
}
$body = '<div class="ban">
<h2>You are banned! ;_;</h2>
<p>You have been banned ' .
($ban['reason'] ? 'for the following reason:' : 'for an unspecified reason.') .
'</p>' .
($ban['reason'] ?
'<p class="reason">' .
$ban['reason'] .
'</p>'
: '') .
'<p>Your ban was filed on <strong>' .
formatDate($ban['set']) .
'</strong>, and <span id="expires">' .
($ban['expires'] ?
'expires <span id="countdown">' . until($ban['expires']) . '</span> from now, which is on <strong>' .
formatDate($ban['expires']) .
'</strong>
<script>
// return date("jS F, Y", $timestamp);
var secondsLeft = ' . ($ban['expires'] - time()) . '
var end = new Date().getTime() + secondsLeft*1000;
function updateExpiresTime() {
countdown.firstChild.nodeValue = until(end);
}
function until(end) {
var now = new Date().getTime();
var diff = Math.round((end - now) / 1000); // in seconds
if (diff < 0) {
document.getElementById("expires").innerHTML = "has since expired. Refresh the page to continue.";
//location.reload(true);
clearInterval(int);
return "";
} else if (diff < 60) {
return diff + " second" + (diff == 1 ? "" : "s");
} else if (diff < 60*60) {
return (num = Math.round(diff/(60))) + " minute" + (num == 1 ? "" : "s");
} else if (diff < 60*60*24) {
return (num = Math.round(diff/(60*60))) + " hour" + (num == 1 ? "" : "s");
} else if (diff < 60*60*24*7) {
return (num = Math.round(diff/(60*60*24))) + " day" + (num == 1 ? "" : "s");
} else if (diff < 60*60*24*365) {
return (num = Math.round(diff/(60*60*24*7))) + " week" + (num == 1 ? "" : "s");
} else {
return (num = Math.round(diff/(60*60*365))) + " year" + (num == 1 ? "" : "s");
}
}
var countdown = document.getElementById("countdown");
updateExpiresTime();
var int = setInterval(updateExpiresTime, 1000);
</script>'
: '<em>will not expire</em>.' ) .
'</span></p>
<p>Your IP address is <strong>' . $_SERVER['REMOTE_ADDR'] . '</strong>.</p>
</div>';
// Show banned page and exit
die(Element('page.html', Array(
'index' => $config['root'],
'title' => 'Banned',
'subtitle' => 'You are banned!',
'body' => $body
)
));
}
}
function threadLocked($id) {
global $board;
$query = prepare(sprintf("SELECT `locked` FROM `posts_%s` WHERE `id` = :id AND `thread` IS NULL LIMIT 1", $board['uri']));
$query->bindValue(':id', $id, PDO::PARAM_INT);
$query->execute() or error(db_error());
if(!$post = $query->fetch()) {
// Non-existant, so it can't be locked...
return false;
}
return (bool) $post['locked'];
}
function threadExists($id) {
global $sql;
$thread_res = mysql_query(sprintf(
"SELECT 1 FROM `posts` WHERE `id` = '%d' AND `thread` IS NULL LIMIT 1",
$id
), $sql) or error(mysql_error($sql));
global $board;
$query = prepare(sprintf("SELECT 1 FROM `posts_%s` WHERE `id` = :id AND `thread` IS NULL LIMIT 1", $board['uri']));
$query->bindValue(':id', $id, PDO::PARAM_INT);
$query->execute() or error(db_error());
if(mysql_num_rows($thread_res) > 0) {
if($query->rowCount()) {
return true;
} else return false;
}
function post($post, $OP) {
global $sql, $board;
global $pdo, $board;
$query = prepare(sprintf("INSERT INTO `posts_%s` VALUES ( NULL, :thread, :subject, :email, :name, :trip, :body, :time, :time, :thumb, :thumbwidth, :thumbheight, :file, :width, :height, :filesize, :filename, :filehash, :password, :ip, :sticky, :locked)", $board['uri']));
// Basic stuff
$query->bindValue(':subject', $post['subject']);
$query->bindValue(':email', $post['email']);
$query->bindValue(':name', $post['name']);
$query->bindValue(':trip', $post['trip']);
$query->bindValue(':body', $post['body']);
$query->bindValue(':time', time(), PDO::PARAM_INT);
$query->bindValue(':password', $post['password']);
$query->bindValue(':ip', $_SERVER['REMOTE_ADDR']);
if($post['mod'] && $post['sticky']) {
$query->bindValue(':sticky', 1, PDO::PARAM_INT);
} else {
$query->bindValue(':sticky', 0, PDO::PARAM_INT);
}
if($post['mod'] && $post['locked']) {
$query->bindValue(':locked', 1, PDO::PARAM_INT);
} else {
$query->bindValue(':locked', 0, PDO::PARAM_INT);
}
if($OP) {
mysql_query(
sprintf("INSERT INTO `posts` VALUES ( NULL, '%d', NULL, '%s', '%s', '%s', '%s', '%s', '%d', '%d', '%s', '%d', '%d', '%s', '%d', '%d', '%d', '%s', '%s', '%s', '%s' )",
$board['id'],
$post['subject'],
$post['email'],
$post['name'],
$post['trip'],
$post['body'],
time(),
time(),
$post['thumb'],
$post['thumbwidth'],
$post['thumbheight'],
$post['file'],
$post['width'],
$post['height'],
$post['filesize'],
$post['filename'],
$post['filehash'],
$post['password'],
mysql_real_escape_string($_SERVER['REMOTE_ADDR'])
), $sql) or error(mysql_error($sql));
return mysql_insert_id($sql);
// No parent thread, image
$query->bindValue(':thread', null, PDO::PARAM_NULL);
} else {
mysql_query(
sprintf("INSERT INTO `posts` VALUES ( NULL, '%d', '%d', '%s', '%s', '%s', '%s', '%s', '%d', '%d', '%s', '%d', '%d', '%s', '%d', '%d', '%d', '%s', '%s', '%s', '%s' )",
$board['id'],
$post['thread'],
$post['subject'],
$post['email'],
$post['name'],
$post['trip'],
$post['body'],
time(),
time(),
$post['has_file']?$post['thumb']:null,
$post['has_file']?$post['thumbwidth']:null,
$post['has_file']?$post['thumbheight']:null,
$post['has_file']?$post['file']:null,
$post['has_file']?$post['width']:null,
$post['has_file']?$post['height']:null,
$post['has_file']?$post['filesize']:null,
$post['has_file']?$post['filename']:null,
$post['has_file']?$post['filehash']:null,
$post['password'],
mysql_real_escape_string($_SERVER['REMOTE_ADDR'])
), $sql) or error(mysql_error($sql));
return mysql_insert_id($sql);
}
}
function index($page) {
global $sql, $board;
$query->bindValue(':thread', $post['thread'], PDO::PARAM_INT);
}
if($post['has_file']) {
$query->bindValue(':thumb', $post['thumb']);
$query->bindValue(':thumbwidth', $post['thumbwidth'], PDO::PARAM_INT);
$query->bindValue(':thumbheight', $post['thumbheight'], PDO::PARAM_INT);
$query->bindValue(':file', $post['file']);
$query->bindValue(':width', $post['width'], PDO::PARAM_INT);
$query->bindValue(':height', $post['height'], PDO::PARAM_INT);
$query->bindValue(':filesize', $post['filesize'], PDO::PARAM_INT);
$query->bindValue(':filename', $post['filename']);
$query->bindValue(':filehash', $post['filehash']);
} else {
$query->bindValue(':thumb', null, PDO::PARAM_NULL);
$query->bindValue(':thumbwidth', null, PDO::PARAM_NULL);
$query->bindValue(':thumbheight', null, PDO::PARAM_NULL);
$query->bindValue(':file', null, PDO::PARAM_NULL);
$query->bindValue(':width', null, PDO::PARAM_NULL);
$query->bindValue(':height', null, PDO::PARAM_NULL);
$query->bindValue(':filesize', null, PDO::PARAM_NULL);
$query->bindValue(':filename', null, PDO::PARAM_NULL);
$query->bindValue(':filehash', null, PDO::PARAM_NULL);
}
$query->execute() or error(db_error($query));
return $pdo->lastInsertId();
}
function bumpThread($id) {
global $board;
$query = prepare(sprintf("UPDATE `posts_%s` SET `bump` = :time WHERE `id` = :id AND `thread` IS NULL", $board['uri']));
$query->bindValue(':time', time(), PDO::PARAM_INT);
$query->bindValue(':id', $id, PDO::PARAM_INT);
$query->execute() or error(db_error($query));
}
// Remove file from post
function deleteFile($id, $remove_entirely_if_already=true) {
global $board, $config;
$query = prepare(sprintf("SELECT `thread`,`thumb`,`file` FROM `posts_%s` WHERE `id` = :id AND `thread` IS NOT NULL LIMIT 1", $board['uri']));
$query->bindValue(':id', $id, PDO::PARAM_INT);
$query->execute() or error(db_error($query));
if($query->rowCount() < 1) {
error($config['error']['invalidpost']);
}
$post = $query->fetch();
$query = prepare(sprintf("UPDATE `posts_%s` SET `thumb` = NULL, `thumbwidth` = NULL, `thumbheight` = NULL, `filewidth` = NULL, `fileheight` = NULL, `filesize` = NULL, `filename` = NULL, `filehash` = NULL, `file` = :file WHERE `id` = :id OR `thread` = :id", $board['uri']));
if($post['file'] == 'deleted' && $remove_entirely_if_already) {
// Already deleted; remove file fully
$query->bindValue(':file', null, PDO::PARAM_NULL);
} else {
// Delete thumbnail
@unlink($board['dir'] . $config['dir']['thumb'] . $post['thumb']);
// Delete file
@unlink($board['dir'] . $config['dir']['img'] . $post['file']);
// Set file to 'deleted'
$query->bindValue(':file', 'deleted', PDO::PARAM_INT);
}
// Update database
$query->bindValue(':id', $id, PDO::PARAM_INT);
$query->execute() or error(db_error($query));
buildThread($post['thread']);
}
// Delete a post (reply or thread)
function deletePost($id, $error_if_doesnt_exist=true) {
global $board, $config;
// Select post and replies (if thread) in one query
$query = prepare(sprintf("SELECT `id`,`thread`,`thumb`,`file` FROM `posts_%s` WHERE `id` = :id OR `thread` = :id", $board['uri']));
$query->bindValue(':id', $id, PDO::PARAM_INT);
$query->execute() or error(db_error($query));
if($query->rowCount() < 1) {
if($error_if_doesnt_exist)
error($config['error']['invalidpost']);
else return false;
}
// Delete posts and maybe replies
while($post = $query->fetch()) {
if(!$post['thread']) {
// Delete thread HTML page
@unlink($board['dir'] . $config['dir']['res'] . sprintf($config['file_page'], $post['id']));
} elseif($query->rowCount() == 1) {
// Rebuild thread
$rebuild = $post['thread'];
}
if($post['thumb']) {
// Delete thumbnail
@unlink($board['dir'] . $config['dir']['thumb'] . $post['thumb']);
}
if($post['file']) {
// Delete file
@unlink($board['dir'] . $config['dir']['img'] . $post['file']);
}
}
$query = prepare(sprintf("DELETE FROM `posts_%s` WHERE `id` = :id OR `thread` = :id", $board['uri']));
$query->bindValue(':id', $id, PDO::PARAM_INT);
$query->execute() or error(db_error($query));
if(isset($rebuild)) {
buildThread($rebuild);
}
return true;
}
function clean() {
global $board, $config;
$offset = round($config['max_pages']*$config['threads_per_page']);
// I too wish there was an easier way of doing this...
$query = prepare(sprintf("SELECT `id` FROM `posts_%s` WHERE `thread` IS NULL ORDER BY `sticky` DESC, `bump` DESC LIMIT :offset, 9001", $board['uri']));
$query->bindValue(':offset', $offset, PDO::PARAM_INT);
$query->execute() or error(db_error($query));
while($post = $query->fetch()) {
deletePost($post['id']);
}
}
function index($page, $mod=false) {
global $board, $config;
$body = '';
$offset = round($page*THREADS_PER_PAGE-THREADS_PER_PAGE);
$offset = round($page*$config['threads_per_page']-$config['threads_per_page']);
sql_open();
$query = mysql_query(sprintf(
"SELECT * FROM `posts` WHERE `thread` IS NULL AND `board` = '%d' ORDER BY `bump` DESC LIMIT %d,%d",
$board['id'],
$offset,
THREADS_PER_PAGE
), $sql) or error(mysql_error($sql));
if(mysql_num_rows($query) < 1 && $page > 1) return false;
while($th = mysql_fetch_array($query)) {
$thread = new Thread($th['id'], $th['subject'], $th['email'], $th['name'], $th['trip'], $th['body'], $th['time'], $th['thumb'], $th['thumbwidth'], $th['thumbheight'], $th['file'], $th['filewidth'], $th['fileheight'], $th['filesize'], $th['filename']);
$newposts = mysql_query(sprintf(
"SELECT `id`, `subject`, `email`, `name`, `trip`, `body`, `time`, `thumb`, `thumbwidth`, `thumbheight`, `file`, `filewidth`, `fileheight`, `filesize`, `filename` FROM `posts` WHERE `board` = '%d' AND `thread` = '%s' ORDER BY `time` DESC LIMIT %d",
$board['id'],
$th['id'],
THREADS_PREVIEW
), $sql) or error(mysql_error($sql));
if(mysql_num_rows($newposts) == THREADS_PREVIEW) {
$count_query = mysql_query(sprintf(
"SELECT COUNT(`id`) as `num` FROM `posts` WHERE `board` = '%d' AND `thread` = '%s'",
$board['id'],
$th['id']
), $sql) or error(mysql_error($sql));
$count = mysql_fetch_array($count_query);
$omitted = $count['num'] - THREADS_PREVIEW;
$query = prepare(sprintf("SELECT * FROM `posts_%s` WHERE `thread` IS NULL ORDER BY `sticky` DESC, `bump` DESC LIMIT ?,?", $board['uri']));
$query->bindValue(1, $offset, PDO::PARAM_INT);
$query->bindValue(2, $config['threads_per_page'], PDO::PARAM_INT);
$query->execute() or error(db_error($query));
if($query->rowcount() < 1 && $page > 1) return false;
while($th = $query->fetch()) {
$thread = new Thread($th['id'], $th['subject'], $th['email'], $th['name'], $th['trip'], $th['body'], $th['time'], $th['thumb'], $th['thumbwidth'], $th['thumbheight'], $th['file'], $th['filewidth'], $th['fileheight'], $th['filesize'], $th['filename'], $th['ip'], $th['sticky'], $th['locked'], $mod ? '?/' : $config['root'], $mod);
$posts = prepare(sprintf("SELECT `id`, `subject`, `email`, `name`, `trip`, `body`, `time`, `thumb`, `thumbwidth`, `thumbheight`, `file`, `filewidth`, `fileheight`, `filesize`, `filename`,`ip` FROM `posts_%s` WHERE `thread` = ? ORDER BY `id` DESC LIMIT ?", $board['uri']));
$posts->bindValue(1, $th['id']);
$posts->bindValue(2, $config['threads_preview'], PDO::PARAM_INT);
$posts->execute() or error(db_error($posts));
if($posts->rowCount() == $config['threads_preview']) {
$count = prepare(sprintf("SELECT COUNT(`id`) as `num` FROM `posts_%s` WHERE `thread` = ?", $board['uri']));
$count->bindValue(1, $th['id']);
$count->execute() or error(db_error($count));
$count = $count->fetch();
$omitted = $count['num'] - $config['threads_preview'];
$thread->omitted = $omitted;
mysql_free_result($count_query);
unset($count);
unset($omitted);
}
while($po = mysql_fetch_array($newposts)) {
$thread->add(new Post($po['id'], $th['id'], $po['subject'], $po['email'], $po['name'], $po['trip'], $po['body'], $po['time'], $po['thumb'], $po['thumbwidth'], $po['thumbheight'], $po['file'], $po['filewidth'], $po['fileheight'], $po['filesize'], $po['filename']));
while($po = $posts->fetch()) {
$thread->add(new Post($po['id'], $th['id'], $po['subject'], $po['email'], $po['name'], $po['trip'], $po['body'], $po['time'], $po['thumb'], $po['thumbwidth'], $po['thumbheight'], $po['file'], $po['filewidth'], $po['fileheight'], $po['filesize'], $po['filename'], $po['ip'], $mod ? '?/' : $config['root'], $mod));
}
mysql_free_result($newposts);
$thread->posts = array_reverse($thread->posts);
$body .= $thread->build(true);
}
mysql_free_result($query);
return Array('button'=>BUTTON_NEWTOPIC, 'board'=>$board, 'body'=>$body, 'post_url' => POST_URL, 'index' => ROOT);
return Array('button'=>$config['button_newtopic'], 'board'=>$board, 'body'=>$body, 'post_url' => $config['post_url'], 'index' => $config['root']);
}
function buildIndex() {
global $sql, $board;
sql_open();
function getPages($mod=false) {
global $board, $config;
// Count threads
$query = query(sprintf("SELECT COUNT(`id`) as `num` FROM `posts_%s` WHERE `thread` IS NULL", $board['uri'])) or error(db_error());
$res = mysql_query(sprintf(
"SELECT COUNT(`id`) as `num` FROM `posts` WHERE `board` = '%d' AND `thread` IS NULL",
$board['id']
), $sql) or error(mysql_error($sql));
$arr = mysql_fetch_array($res);
$count = floor((THREADS_PER_PAGE + $arr['num'] - 1) / THREADS_PER_PAGE);
$count = current($query->fetch());
$count = floor(($config['threads_per_page'] + $count - 1) / $config['threads_per_page']);
$pages = Array();
for($x=0;$x<$count && $x<MAX_PAGES;$x++) {
$pages[] = Array('num' => $x+1, 'link' => $x==0 ? ROOT . $board['dir'] . FILE_INDEX : ROOT . $board['dir'] . sprintf(FILE_PAGE, $x+1));
for($x=0;$x<$count && $x<$config['max_pages'];$x++) {
$pages[] = Array('num' => $x+1, 'link' => $x==0 ? ($mod ? '?/' : $config['root']) . $board['dir'] . $config['file_index'] : ($mod ? '?/' : $config['root']) . $board['dir'] . sprintf($config['file_page'], $x+1));
}
mysql_free_result($res);
unset($arr);
unset($count);
return $pages;
}
function makerobot($body) {
global $config;
$body = strtolower($body);
// Leave only letters
$body = preg_replace('/[^a-z]/i', '', $body);
// Remove repeating characters
if($config['robot_strip_repeating'])
$body = preg_replace('/(.)\\1+/', '$1', $body);
return sha1($body);
}
function checkRobot($body) {
/* CREATE TABLE `robot` (
`hash` VARCHAR( 40 ) NOT NULL COMMENT 'SHA1'
) ENGINE = INNODB; */
/* CREATE TABLE `mutes` (
`ip` VARCHAR( 15 ) NOT NULL ,
`time` INT NOT NULL
) ENGINE = MYISAM ; */
$body = makerobot($body);
$query = prepare("SELECT 1 FROM `robot` WHERE `hash` = :hash LIMIT 1");
$query->bindValue(':hash', $body);
$query->execute() or error(db_error($query));
if($query->fetch()) {
return true;
} else {
// Insert new hash
$query = prepare("INSERT INTO `robot` VALUES (:hash)");
$query->bindValue(':hash', $body);
$query->execute() or error(db_error($query));
return false;
}
}
function numPosts($id) {
global $board;
$query = prepare(sprintf("SELECT COUNT(*) as `count` FROM `posts_%s` WHERE `thread` = :thread", $board['uri']));
$query->bindValue(':thread', $id, PDO::PARAM_INT);
$query->execute() or error(db_error($query));
$result = $query->fetch();
return $result['count'];
}
function muteTime() {
global $config;
// Find number of mutes in the past X hours
$query = prepare("SELECT COUNT(*) as `count` FROM `mutes` WHERE `time` >= :time AND `ip` = :ip");
$query->bindValue(':time', time()-(ROBOT_MUTE_HOUR*3600), PDO::PARAM_INT);
$query->bindValue(':ip', $_SERVER['REMOTE_ADDR']);
$query->execute() or error(db_error($query));
$result = $query->fetch();
if($result['count'] == 0) return 0;
return pow($config['robot_mute_multiplier'], $result['count']);
}
function mute() {
// Insert mute
$query = prepare("INSERT INTO `mutes` VALUES (:ip, :time)");
$query->bindValue(':time', time(), PDO::PARAM_INT);
$query->bindValue(':ip', $_SERVER['REMOTE_ADDR']);
$query->execute() or error(db_error($query));
return muteTime();
}
function checkMute() {
$mutetime = muteTime();
if($mutetime > 0) {
// Find last mute time
$query = prepare("SELECT `time` FROM `mutes` WHERE `ip` = :ip ORDER BY `time` DESC LIMIT 1");
$query->bindValue(':ip', $_SERVER['REMOTE_ADDR']);
$query->execute() or error(db_error($query));
if(!$mute = $query->fetch()) {
// What!? He's muted but he's not muted...
return;
}
if($mute['time'] + $mutetime > time()) {
// Not expired yet
error(sprintf($config['error']['youaremuted'], $mute['time'] + $mutetime - time()));
} else {
// Already expired
return;
}
}
}
function buildIndex() {
global $board, $config;
sql_open();
$pages = getPages();
$page = 1;
while($page <= MAX_PAGES && $content = index($page)) {
$filename = $board['dir'] . ($page==1 ? FILE_INDEX : sprintf(FILE_PAGE, $page));
while($page <= $config['max_pages'] && $content = index($page)) {
$filename = $board['dir'] . ($page==1 ? $config['file_index'] : sprintf($config['file_page'], $page));
if(file_exists($filename)) $md5 = md5_file($filename);
$content['pages'] = $pages;
@ -204,18 +535,52 @@
}
$page++;
}
if($page < MAX_PAGES) {
for(;$page<=MAX_PAGES;$page++) {
$filename = $page==1 ? FILE_INDEX : sprintf(FILE_PAGE, $page);
if($page < $config['max_pages']) {
for(;$page<=$config['max_pages'];$page++) {
$filename = $page==1 ? $config['file_index'] : sprintf($config['file_page'], $page);
@unlink($filename);
}
}
}
function isDNSBL() {
$dns_black_lists = file('./dnsbl.txt', FILE_IGNORE_NEW_LINES);
// Reverse the IP
$rev_ip = implode(array_reverse(explode('.', $_SERVER['REMOTE_ADDR'])), '.');
$response = array();
foreach ($dns_black_lists as $dns_black_list) {
$response = (gethostbynamel($rev_ip . '.' . $dns_black_list));
if(!empty($response))
return true;
}
return false;
}
function isTor() {
return gethostbyname(
ReverseIPOctets($_SERVER['REMOTE_ADDR']) . '.' . $_SERVER['SERVER_PORT'] . '.' . ReverseIPOctets($_SERVER['SERVER_ADDR']) . '.ip-port.exitlist.torproject.org'
) == '127.0.0.2';
}
function ReverseIPOctets($inputip) {
$ipoc = explode('.', $inputip);
return $ipoc[3] . '.' . $ipoc[2] . '.' . $ipoc[1] . '.' . $ipoc[0];
}
function markup(&$body) {
global $sql, $board;
global $board, $config;
if(AUTO_UNICODE) {
$body = utf8tohtml($body, true);
if($config['markup_urls']) {
$body = preg_replace($config['url_regex'], "<a href=\"$0\">$0</a>", $body, -1, $num_links);
if($num_links > $config['max_links'])
error($config['error']['toomanylinks']);
}
if($config['auto_unicode']) {
$body = str_replace('...', '…', $body);
$body = str_replace('<--', '', $body);
@ -226,8 +591,6 @@
$body = str_replace('--', '–', $body); // en dash
}
$body = utf8tohtml($body, true);
// Cites
if(preg_match_all('/(^|\s)&gt;&gt;([0-9]+?)(\s|$)/', $body, $cites)) {
$previousPosition = 0;
@ -239,27 +602,28 @@
strlen($cites[1][$index]),
strlen($cites[3][$index]),
);
$query = prepare(sprintf("SELECT `thread`,`id` FROM `posts_%s` WHERE `id` = :id LIMIT 1", $board['uri']));
$query->bindValue(':id', $cite);
$query->execute() or error(db_error($query));
$result = mysql_query(sprintf(
"SELECT `thread`,`id` FROM `posts` WHERE `board` = '%d' AND `id` = '%d' LIMIT 1",
$board['id'],
$cite
), $sql) or error(mysql_error($sql));
if($post = mysql_fetch_array($result)) {
$replacement = '<a onclick="highlightReply(\''.$cite.'\');" href="' . ROOT . $board['dir'] . DIR_RES . ($post['thread']?$post['thread']:$post['id']) . '.html#' . $cite . '">&gt;&gt;' . $cite . '</a>';
if($post = $query->fetch()) {
$replacement = '<a onclick="highlightReply(\''.$cite.'\');" href="' . $config['root'] . $board['dir'] . $config['dir']['res'] . ($post['thread']?$post['thread']:$post['id']) . '.html#' . $cite . '">&gt;&gt;' . $cite . '</a>';
} else {
$replacement = "&gt;&gt;{$cite}";
}
mysql_free_result($result);
// Find the position of the cite
$position = strpos($body, $cites[0][$index]);
// Replace the found string with "xxxx[...]". (allows duplicate tags). Keeps whitespace.
$body = substr_replace($body, str_repeat('x', strlen($cites[0][$index]) - $whitespace[0] - $whitespace[1]), $position + $whitespace[0], strlen($cites[0][$index]) - $whitespace[0] - $whitespace[1]);
$temp .= substr($body, $previousPosition, $position-$previousPosition) . $cites[1][$index] . $replacement . $cites[3][$index];
$previousPosition = $position+strlen($cites[0][$index]);
}
// The rest
$temp .= substr($body, $previousPosition);
@ -268,14 +632,13 @@
$body = str_replace("\r", '', $body);
if(MARKUP_URLS)
$body = preg_replace(URL_REGEX, "<a href=\"$0\">$0</a>", $body);
$body = preg_replace("/(^|\n)([\s]+)?(&gt;)([^\n]+)?($|\n)/m", '$1$2<span class="quote">$3$4</span>$5', $body);
if(WIKI_MARKUP) {
if($config['wiki_markup']) {
$body = preg_replace("/(^|\n)==(.+?)==\n?/m", "<h2>$2</h2>", $body);
$body = preg_replace("/'''(.+?)'''/m", "<strong>$1</strong>", $body);
$body = preg_replace("/''(.+?)''/m", "<em>$1</em>", $body);
$body = preg_replace("/\*\*(.+?)\*\*/m", "<span class=\"spoiler\">$1</span>", $body);
}
$body = preg_replace("/\n/", '<br/>', $body);
}
@ -319,41 +682,44 @@
return $result;
}
function buildThread($id, $return=false) {
global $sql, $board;
function buildThread($id, $return=false, $mod=false) {
global $board, $config;
$id = round($id);
$query = mysql_query(sprintf(
"SELECT `id`,`thread`,`subject`,`name`,`email`,`trip`,`body`,`time`,`thumb`,`thumbwidth`,`thumbheight`,`file`,`filewidth`,`fileheight`,`filesize`,`filename` FROM `posts` WHERE `board` = '%d' AND ((`thread` IS NULL AND `id` = '%s') OR `thread` = '%s') ORDER BY `thread`,`time`",
$board['id'],
$id,
$id
), $sql) or error(mysql_error($sql));
$query = prepare(sprintf("SELECT `id`,`thread`,`subject`,`name`,`email`,`trip`,`body`,`time`,`thumb`,`thumbwidth`,`thumbheight`,`file`,`filewidth`,`fileheight`,`filesize`,`filename`,`ip`,`sticky`,`locked` FROM `posts_%s` WHERE (`thread` IS NULL AND `id` = :id) OR `thread` = :id ORDER BY `thread`,`time`", $board['uri']));
$query->bindValue(':id', $id, PDO::PARAM_INT);
$query->execute() or error(db_error($query));
while($post = mysql_fetch_array($query)) {
while($post = $query->fetch()) {
if(!isset($thread)) {
$thread = new Thread($post['id'], $post['subject'], $post['email'], $post['name'], $post['trip'], $post['body'], $post['time'], $post['thumb'], $post['thumbwidth'], $post['thumbheight'], $post['file'], $post['filewidth'], $post['fileheight'], $post['filesize'], $post['filename'], false);
$thread = new Thread($post['id'], $post['subject'], $post['email'], $post['name'], $post['trip'], $post['body'], $post['time'], $post['thumb'], $post['thumbwidth'], $post['thumbheight'], $post['file'], $post['filewidth'], $post['fileheight'], $post['filesize'], $post['filename'], $post['ip'], $post['sticky'], $post['locked'], $mod ? '?/' : $config['root'], $mod);
} else {
$thread->add(new Post($post['id'], $thread->id, $post['subject'], $post['email'], $post['name'], $post['trip'], $post['body'], $post['time'], $post['thumb'], $post['thumbwidth'], $post['thumbheight'], $post['file'], $post['filewidth'], $post['fileheight'], $post['filesize'], $post['filename']));
$thread->add(new Post($post['id'], $thread->id, $post['subject'], $post['email'], $post['name'], $post['trip'], $post['body'], $post['time'], $post['thumb'], $post['thumbwidth'], $post['thumbheight'], $post['file'], $post['filewidth'], $post['fileheight'], $post['filesize'], $post['filename'], $post['ip'], $mod ? '?/' : $config['root'], $mod));
}
}
// Check if any posts were found
if(!isset($thread)) error($config['error']['nonexistant']);
$body = Element('thread.html', Array(
'button'=>BUTTON_REPLY,
'button'=>$config['button_reply'],
'board'=>$board,
'body'=>$thread->build(),
'post_url' => POST_URL,
'index' => ROOT,
'id' => $id
'post_url' => $config['post_url'],
'index' => $config['root'],
'id' => $id,
'mod' => $mod,
'return' => ($mod ? '?' . $board['url'] . $config['file_index'] : $config['root'] . $board['uri'] . '/' . $config['file_index'])
));
if($return)
return $body;
else
@file_put_contents($board['dir'] . DIR_RES . $id . '.html', $body) or error("Couldn't write to file.");
}
mysql_free_result($query);
@file_put_contents($board['dir'] . $config['dir']['res'] . sprintf($config['file_page'], $id), $body) or error("Couldn't write to file.");
}
function generate_tripcode ( $name, $length = 10 ) {
global $config;
$name = stripslashes ( $name );
$t = explode('#', $name);
$nameo = $t[0];
@ -369,7 +735,7 @@
$salt = strtr ( $salt, ':;<=>?@[\]^_`', 'ABCDEFGabcdef' );
if ( isset ( $t[2] ) ) {
// secure
$trip = '!!' . substr ( crypt ( $trip, '@#$%^&*()' ), ( -1 * $length ) );
$trip = '!!' . substr ( crypt ( $trip, $config['secure_trip_salt'] ), ( -1 * $length ) );
} else {
// insecure
$trip = '!' . substr ( crypt ( $trip, $salt ), ( -1 * $length ) );
@ -504,25 +870,25 @@
case 'jpeg':
if(!$image = @imagecreatefromjpeg($source_pic)) {
unlink($source_pic);
error(ERR_INVALIDIMG);
error($config['error']['invalidimg']);
}
break;
case 'png':
if(!$image = @imagecreatefrompng($source_pic)) {
unlink($source_pic);
error(ERR_INVALIDIMG);
error($config['error']['invalidimg']);
}
break;
case 'gif':
if(!$image = @imagecreatefromgif($source_pic)) {
unlink($source_pic);
error(ERR_INVALIDIMG);
error($config['error']['invalidimg']);
}
break;
case 'bmp':
if(!$image = @imagecreatefrombmp($source_pic)) {
unlink($source_pic);
error(ERR_INVALIDIMG);
error($config['error']['invalidimg']);
}
break;
default:

44
inc/instance-config.php

@ -9,15 +9,43 @@
*/
/*
// Database stuff
define('MY_SERVER', '127.0.0.1');
define('MY_USER', '');
define('MY_PASSWORD', '');
define('MY_DATABASE', '');
$config['db']['type'] = 'mysql';
$config['db']['server'] = 'localhost';
$config['db']['user'] = '';
$config['db']['password'] = '';
$config['db']['database'] = '';
define('ROOT', '/');
$config['root'] = '/';
// define('FOO', 'bar');
*/
// The following looks ugly. I will find a better place to put this code soon.
$config['post_url'] = $config['root'] . 'post.php';
$config['url_match'] = '/^' .
(preg_match($config['url_regex'], $config['root']) ? '' :
(@$_SERVER['HTTPS']?'https':'http') .
':\/\/'.$_SERVER['HTTP_HOST']) .
preg_quote($config['root'], '/') .
'(' .
str_replace('%s', '\w{1,8}', preg_quote($config['board_path'], '/')) .
'|' .
str_replace('%s', '\w{1,8}', preg_quote($config['board_path'], '/')) .
preg_quote($config['file_index'], '/') .
'|' .
str_replace('%s', '\w{1,8}', preg_quote($config['board_path'], '/')) .
str_replace('%d', '\d+', preg_quote($config['file_page'], '/')) .
'|' .
preg_quote($config['file_mod'], '/') .
'\?\/.+' .
')$/i';
$config['dir']['static'] = $config['root'] . 'static/';
$config['image_sticky'] = $config['dir']['static'] . 'sticky.gif';
$config['image_locked'] = $config['dir']['static'] . 'locked.gif';
$config['image_deleted'] = $config['dir']['static'] . 'deleted.png';
$config['image_zip'] = $config['dir']['static'] . 'zip.png';
?>

187
inc/mod.php

@ -0,0 +1,187 @@
<?php
// Creates a small random string for validating moderators' cookies
function mkhash($length=12) {
// The method here isn't really important,
// but I think this generates a relatively
// unique string that looks cool.
// If you choose to change this, make sure it cannot include a ':' character.
return substr(base64_encode(sha1(rand() . time(), true)), 0, $length);
}
function login($username, $password, $makehash=true) {
global $mod;
// SHA1 password
if($makehash) {
$password = sha1($password);
}
$query = prepare("SELECT `id`,`type` FROM `mods` WHERE `username` = :username AND `password` = :password LIMIT 1");
$query->bindValue(':username', $username);
$query->bindValue(':password', $password);
$query->execute() or error(db_error($query));
if($user = $query->fetch()) {
return $mod = Array(
'id' => $user['id'],
'type' => $user['type'],
'username' => $username,
'password' => $password,
'hash' => isset($_SESSION['mod']['hash']) ? $_SESSION['mod']['hash'] : mkhash()
);
} else return false;
}
function setCookies() {
global $mod, $config;
if(!$mod) error('setCookies() was called for a non-moderator!');
// $config['cookies']['mod'] contains username:hash
setcookie($config['cookies']['mod'], $mod['username'] . ':' . $mod['hash'], time()+$config['cookies']['expire'], $config['cookies']['jail']?$config['root']:'/', null, false, true);
// Put $mod in the session
$_SESSION['mod'] = $mod;
// Lock sessions to IP addresses
if($mod['lock_ip'])
$_SESSION['mod']['ip'] = $_SERVER['REMOTE_ADDR'];
}
function destroyCookies() {
// Delete the cookies
setcookie($config['cookies']['mod'], 'deleted', time()-$config['cookies']['expire'], $config['cookies']['jail']?$config['root']:'/', null, false, true);
// Unset the session
unset($_SESSION['mod']);
}
function modLog($action) {
global $mod;
$query = prepare("INSERT INTO `modlogs` VALUES (:id, :ip, :time, :text)");
$query->bindValue(':id', $mod['id'], PDO::PARAM_INT);
$query->bindValue(':ip', $_SERVER['REMOTE_ADDR']);
$query->bindValue(':time', time(), PDO::PARAM_INT);
$query->bindValue(':text', $action);
$query->execute() or error(db_error($query));
}
if(isset($_COOKIE['mod']) && isset($_SESSION['mod']) && is_array($_SESSION['mod'])) {
// Should be username:session hash
$cookie = explode(':', $_COOKIE['mod']);
if(count($cookie) != 2) {
destroyCookies();
error($config['error']['malformed']);
}
// Validate session
if( $cookie[0] != $_SESSION['mod']['username'] ||
$cookie[1] != $_SESSION['mod']['hash']) {
// Malformed cookies
destroyCookies();
error($config['error']['malformed']);
}
// Open connection
sql_open();
// Check username/password
if(!login($_SESSION['mod']['username'], $_SESSION['mod']['password'], false)) {
destroyCookies();
error($config['error']['invalidafter']);
}
}
// Generates a <ul> element with a list of linked
// boards and their subtitles. (without the <ul> opening and ending tags)
function ulBoards() {
global $mod, $config;
$body = '';
// List of boards
$boards = listBoards();
foreach($boards as &$b) {
$body .= '<li>' .
'<a href="?/' .
sprintf($config['board_path'], $b['uri']) . $config['file_index'] .
'">' .
sprintf($config['board_abbreviation'], $b['uri']) .
'</a> - ' .
$b['title'] .
(isset($b['subtitle']) ? '<span class="unimportant"> — ' . $b['subtitle'] . '</span>' : '') .
'</li>';
}
if($mod['type'] >= $config['mod']['newboard']) {
$body .= '<li style="margin-top:15px;"><a href="?/new"><strong>Create new board</strong></a></li>';
}
return $body;
}
function form_newBan($ip=null, $reason='', $continue=false, $delete=false, $board=false) {
return '<fieldset><legend>New ban</legend>' .
'<form action="?/ban" method="post">' .
($continue ? '<input type="hidden" name="continue" value="' . htmlentities($continue) . '" />' : '') .
($delete ? '<input type="hidden" name="delete" value="' . htmlentities($delete) . '" />' : '') .
($board ? '<input type="hidden" name="board" value="' . htmlentities($board) . '" />' : '') .
'<table>' .
'<tr>' .
'<th><label for="ip">IP</label></th>' .
'<td><input type="text" name="ip" id="ip" size="15" maxlength="15" ' .
(isset($ip) ?
'value="' . htmlentities($ip) . '" ' : ''
) .
'/></td>' .
'</tr>' .
'<tr>' .
'<th><label for="reason">Reason</label></th>' .
'<td><textarea name="reason" id="reason" rows="5" cols="30">' .
htmlentities($reason) .
'</textarea></td>' .
'</tr>' .
'<tr>' .
'<th><label for="length">Length</label></th>' .
'<td><input type="text" name="length" id="length" size="20" maxlength="40" />' .
' <span class="unimportant">(eg. "2d1h30m" or "2 days")</span></td>' .
'</tr>' .
'<tr>' .
'<td></td>' .
'<td><input name="new_ban" type="submit" value="New Ban" /></td>' .
'</tr>' .
'</table>' .
'</form>' .
'</fieldset>';
}
function form_newBoard() {
return '<fieldset><legend>New board</legend>' .
'<form action="?/new" method="post">' .
'<table>' .
'<tr>' .
'<th><label for="board">URI</label></th>' .
'<td><input type="text" name="uri" id="board" size="3" maxlength="8" />' .
' <span class="unimportant">(eg. "b"; "mu")</span></td>' .
'</tr>' .
'<tr>' .
'<th><label for="title">Title</label></th>' .
'<td><input type="text" name="title" id="title" size="15" maxlength="20" />' .
' <span class="unimportant">(eg. "Random")</span></td>' .
'</tr>' .
'<tr>' .
'<th><label for="subtitle">Subtitle</label></th>' .
'<td><input type="text" name="subtitle" id="subtitle" size="20" maxlength="40" />' .
' <span class="unimportant">(optional)</span></td>' .
'</tr>' .
'<tr>' .
'<td></td>' .
'<td><input name="new_board" type="submit" value="New Board" /></td>' .
'</tr>' .
'</table>' .
'</form>' .
'</fieldset>';
}
?>

2
inc/template.php

@ -8,7 +8,7 @@
// Standard configuration
//
// Folder where the template files are kept
$templateDir = DIR_TEMPLATE;
$templateDir = $config['dir']['template'];
//
// Enable global things like %gentime, etc.
$templateGlobals = true;

101
inc/user.php

@ -1,10 +1,12 @@
<?php
// 'false' means that the user is not logged in as a moderator
$mod = false;
// Set the session name.
session_name(SESS_COOKIE);
session_name($config['cookies']['session']);
// Set session parameters
session_set_cookie_params(0, JAIL_COOKIES?ROOT:'/');
session_set_cookie_params(0, $config['cookies']['jail']?$config['root']:'/');
// Start the session
session_start();
@ -12,100 +14,13 @@
// Session creation time
if(!isset($_SESSION['created'])) $_SESSION['created'] = time();
if(!isset($_COOKIE[HASH_COOKIE]) || !isset($_COOKIE[TIME_COOKIE]) || $_COOKIE[HASH_COOKIE] != md5($_COOKIE[TIME_COOKIE].SALT)) {
if(!isset($_COOKIE[$config['cookies']['hash']]) || !isset($_COOKIE[$config['cookies']['time']]) || $_COOKIE[$config['cookies']['hash']] != md5($_COOKIE[$config['cookies']['time']] . $config['cookies']['salt'])) {
$time = time();
setcookie(TIME_COOKIE, $time, time()+COOKIE_EXPIRE, JAIL_COOKIES?ROOT:'/', null, false, true);
setcookie(HASH_COOKIE, md5($time.SALT), $time+COOKIE_EXPIRE, JAIL_COOKIES?ROOT:'/', null, false, true);
setcookie($config['cookies']['time'], $time, time()+$config['cookies']['expire'], $config['cookies']['jail']?$config['root']:'/', null, false, true);
setcookie($config['cookies']['hash'], md5($time . $config['cookies']['salt']), $time+$config['cookies']['expire'], $config['cookies']['jail']?$config['root']:'/', null, false, true);
$user = Array('valid' => false, 'appeared' => $time);
} else {
$user = Array('valid' => true, 'appeared' => $_COOKIE[TIME_COOKIE]);
}
// 'false' means that the user is not logged in as a moderator
$mod = false;
// Creates a small random string for validating moderators' cookies
function mkhash($length=12) {
// The method here isn't really important,
// but I think this generates a relatively
// unique string that looks cool.
// If you choose to change this, make sure it cannot include a ':' character.
return substr(base64_encode(sha1(rand() . time(), true)), 0, $length);
$user = Array('valid' => true, 'appeared' => $_COOKIE[$config['cookies']['time']]);
}
function login($username, $password, $makehash=true) {
global $sql, $mod;
// SHA1 password
if($makehash) {
$password = sha1($password);
}
$res = mysql_query(sprintf(
"SELECT `id`,`type` FROM `mods` WHERE `username` = '%s' AND `password` = '%s' LIMIT 1",
mysql_real_escape_string($username),
$password
), $sql) or error(mysql_error($sql));
if($user = mysql_fetch_array($res)) {
return $mod = Array(
'id' => $user['id'],
'type' => $user['type'],
'username' => $username,
'password' => $password,
'hash' => mkhash()
);
} else return false;
}
function setCookies() {
global $mod;
if(!$mod) error('setCookies() was called for a non-moderator!');
// MOD_COOKIE contains username:hash
setcookie(MOD_COOKIE, $mod['username'] . ':' . $mod['hash'], time()+COOKIE_EXPIRE, JAIL_COOKIES?ROOT:'/', null, false, true);
// Put $mod in the session
$_SESSION['mod'] = $mod;
// Lock sessions to IP addresses
if(MOD_LOCK_IP)
$_SESSION['mod']['ip'] = $_SERVER['REMOTE_ADDR'];
}
function destroyCookies() {
// Delete the cookies
setcookie(MOD_COOKIE, 'deleted', time()-COOKIE_EXPIRE, JAIL_COOKIES?ROOT:'/', null, false, true);
// Unset the session
unset($_SESSION['mod']);
}
if(isset($_COOKIE['mod']) && isset($_SESSION['mod']) && is_array($_SESSION['mod'])) {
// Should be username:session hash
$cookie = explode(':', $_COOKIE['mod']);
if(count($cookie) != 2) {
destroyCookies();
error(ERROR_MALFORMED);
}
// Validate session
if( $cookie[0] != $_SESSION['mod']['username'] ||
$cookie[1] != $_SESSION['mod']['hash']) {
// Malformed cookies
destroyCookies();
error(ERROR_MALFORMED);
}
// Open connection
sql_open();
// Check username/password
if(!login($_SESSION['mod']['username'], $_SESSION['mod']['password'], false)) {
destroyCookies();
error(ERROR_INVALIDAFTER);
}
$mod = $_SESSION['mod'];
}
?>

244
install.php

@ -0,0 +1,244 @@
<?php
require 'inc/functions.php';
require 'inc/display.php';
require 'inc/config.php';
if (file_exists('inc/instance-config.php')) {
require 'inc/instance-config.php';
}
require 'inc/template.php';
require 'inc/database.php';
require 'inc/user.php';
$step = isset($_GET['step']) ? round($_GET['step']) : 0;
$page = Array(
'index' => $config['root'],
'title' => 'Install',
'body' => ''
);
if($step == 0) {
// Agreeement
$page['body'] = '
<textarea style="width:700px;height:370px;margin:auto;display:block;background:white;color:black" disabled>' . htmlentities(file_get_contents('LICENSE')) . '</textarea>
<p style="text-align:center">
<a href="?step=1">I have read and understood the agreement. Proceed to installation.</a>
</p>';
echo Element('page.html', $page);
} elseif($step == 1) {
$page['title'] = 'Pre-installation test';
$page['body'] = '<table class="test">';
function rheader($item) {
global $page, $config;
$page['body'] .= '<tr class="h"><th colspan="2">' . $item . '</th></tr>';
}
function row($item, $result) {
global $page, $config;
$page['body'] .= '<tr><th>' . $item . '</th><td><img style="width:16px;height:16px" src="' . $config['dir']['static'] . ($result ? 'ok.png' : 'error.png') . '" /></td></tr>';
}
// Required extensions
rheader('PHP extensions');
row('PDO', extension_loaded('pdo'));
row('GD', extension_loaded('gd'));
// GD tests
rheader('GD tests');
row('JPEG', function_exists('imagecreatefromjpeg'));
row('PNG', function_exists('imagecreatefrompng'));
row('GIF', function_exists('imagecreatefromgif'));
row('BMP', function_exists('imagecreatefrombmp'));
// Database drivers
$drivers = PDO::getAvailableDrivers();
rheader('PDO drivers <em>(currently installed drivers)</em>');
foreach($drivers as &$driver) {
row($driver, true);
}
// Permissions
rheader('File permissions');
row('<em>root directory</em> (' . getcwd() . ')', is_writable('.'));
$page['body'] .= '</table>
<p style="text-align:center">
<a href="?step=2">Continue.</a>
</p>';
echo Element('page.html', $page);
} elseif($step == 2) {
// Basic config
$page['title'] = 'Configuration';
function create_salt() {
return substr(base64_encode(sha1(rand())), 0, rand(25, 31));
}
$page['body'] = '
<form action="?step=3" method="post">
<fieldset>
<legend>Database</legend>
<label for="db_type">Type:</label>
<select id="db_type" name="db[type]">';
$drivers = PDO::getAvailableDrivers();
foreach($drivers as &$driver) {
$driver_txt = $driver;
switch($driver) {
case 'cubrid':
$driver_txt = 'Cubrid';
break;
case 'dblib':
$driver_txt = 'FreeTDS / Microsoft SQL Server / Sybase';
break;
case 'firebird':
$driver_txt = 'Firebird/Interbase 6';
break;
case 'ibm':
$driver_txt = 'IBM DB2';
break;
case 'informix':
$driver_txt = 'IBM Informix Dynamic Server';
break;
case 'mysql':
$driver_txt = 'MySQL';
break;
case 'oci':
$driver_txt = 'OCI';
break;
case 'odbc':
$driver_txt = 'ODBC v3 (IBM DB2, unixODBC)';
break;
case 'pgsql':
$driver_txt = 'PostgreSQL';
break;
case 'sqlite':
$driver_txt = 'SQLite 3';
break;
case 'sqlite2':
$driver_txt = 'SQLite 2';
break;
}
$page['body'] .= '<option name="' . $driver . '">' . $driver_txt . '</option>';
}
$page['body'] .= '
</select>
<label for="db_db">Database:</label>
<input type="text" id="db_db" name="db[database]" value="" />
<label for="db_user">Username:</label>
<input type="text" id="db_user" name="db[user]" value="" />
<label for="db_pass">Password:</label>
<input type="password" id="db_pass" name="db[password]" value="" />
</fieldset>
<fieldset>
<legend>Cookies</legend>
<label for="cookies_session">Name of session cookie:</label>
<input type="text" id="cookies_session" name="cookies[session]" value="' . session_name() . '" />
<label for="cookies_time">Cookie containing a timestamp of first arrival:</label>
<input type="text" id="cookies_time" name="cookies[time]" value="' . $config['cookies']['time'] . '" />
<label for="cookies_hash">Cookie containing a hash for verification purposes:</label>
<input type="text" id="cookies_hash" name="cookies[hash]" value="' . $config['cookies']['hash'] . '" />
<label for="cookies_mod">Moderator cookie:</label>
<input type="text" id="cookies_mod" name="cookies[mod]" value="' . $config['cookies']['mod'] . '" />
<label for="cookies_salt">Secure salt:</label>
<input type="text" id="cookies_salt" name="cookies[salt]" value="' . create_salt() . '" size="40" />
</fieldset>
<fieldset>
<legend>Flood control</legend>
<label for="flood_time">Seconds before each post:</label>
<input type="text" id="flood_time" name="flood_time" value="' . $config['flood_time'] . '" />
<label for="flood_time_ip">Seconds before you can repost something (post the exact same text):</label>
<input type="text" id="flood_time_ip" name="flood_time_ip" value="' . $config['flood_time_ip'] . '" />
<label for="flood_time_same">Same as above, but with a different IP address:</label>
<input type="text" id="flood_time_same" name="flood_time_same" value="' . $config['flood_time_same'] . '" />
<label for="max_body">Maximum post body length:</label>
<input type="text" id="max_body" name="max_body" value="' . $config['max_body'] . '" />
<label for="reply_limit">Replies in a thread before it can no longer be bumped:</label>
<input type="text" id="reply_limit" name="reply_limit" value="' . $config['reply_limit'] . '" />
<label for="max_links">Maximum number of links in a single post:</label>
<input type="text" id="max_links" name="max_links" value="' . $config['max_links'] . '" />
</fieldset>
<fieldset>
<legend>Images</legend>
<label for="max_filesize">Maximum image filesize:</label>
<input type="text" id="max_filesize" name="max_filesize" value="' . $config['max_filesize'] . '" />
<label for="thumb_width">Thumbnail width:</label>
<input type="text" id="thumb_width" name="thumb_width" value="' . $config['thumb_width'] . '" />
<label for="thumb_height">Thumbnail height:</label>
<input type="text" id="thumb_height" name="thumb_height" value="' . $config['thumb_height'] . '" />
<label for="max_width">Maximum image width:</label>
<input type="text" id="max_width" name="max_width" value="' . $config['max_width'] . '" />
<label for="max_height">Maximum image height:</label>
<input type="text" id="max_height" name="max_height" value="' . $config['max_height'] . '" />
</fieldset>
<fieldset>
<legend>Display</legend>
<label for="threads_per_page">Threads per page:</label>
<input type="text" id="threads_per_page" name="threads_per_page" value="' . $config['threads_per_page'] . '" />
<label for="max_pages">Page limit:</label>
<input type="text" id="max_pages" name="max_pages" value="' . $config['max_pages'] . '" />
<label for="threads_preview">Number of replies to show per thread on the index page:</label>
<input type="text" id="threads_preview" name="threads_preview" value="' . $config['threads_preview'] . '" />
</fieldset>
<fieldset>
<legend>Directories</legend>
<label for="root">Root URI (include trailing slash):</label>
<input type="text" id="root" name="root" value="' . $config['root'] . '" />
<label for="dir_img">Image directory:</label>
<input type="text" id="dir_img" name="dir[img]" value="' . $config['dir']['img'] . '" />
<label for="dir_thumb">Thumbnail directory:</label>
<input type="text" id="dir_thumb" name="dir[thumb]" value="' . $config['dir']['thumb'] . '" />
<label for="dir_res">Thread directory:</label>
<input type="text" id="dir_res" name="dir[res]" value="' . $config['dir']['res'] . '" />
</fieldset>
<fieldset>
<legend>Miscellaneous</legend>
<label for="secure_trip_salt">Secure trip (##) salt:</label>
<input type="text" id="secure_trip_salt" name="secure_trip_salt" value="' . create_salt() . '" size="40" />
</fieldset>
<p style="text-align:center">
<input type="submit" value="Complete installation" />
</p>
</form>
';
echo Element('page.html', $page);
}
?>

63
install.sql

@ -45,10 +45,10 @@ INSERT INTO `boards` (`id`, `uri`, `title`, `subtitle`) VALUES
-- --------------------------------------------------------
--
-- Table structure for table `posts`
-- Table structure for table `posts_b`
--
CREATE TABLE IF NOT EXISTS `posts` (
CREATE TABLE IF NOT EXISTS `posts_b` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`board` smallint(6) NOT NULL,
`thread` int(11) DEFAULT NULL,
@ -70,10 +70,67 @@ CREATE TABLE IF NOT EXISTS `posts` (
`filehash` varchar(32) DEFAULT NULL,
`password` varchar(20) DEFAULT NULL,
`ip` varchar(15) NOT NULL,
`sticky` int(1) NOT NULL,
`locked` int(1) NOT NULL,
UNIQUE KEY `id` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
--
-- Dumping data for table `posts`
-- Dumping data for table `posts_b`
--
-- --------------------------------------------------------
--
-- Table structure for table `mods`
--
CREATE TABLE IF NOT EXISTS `mods` (
`id` smallint(6) NOT NULL AUTO_INCREMENT,
`username` varchar(30) NOT NULL,
`password` char(40) NOT NULL COMMENT 'SHA1',
`type` smallint(1) NOT NULL COMMENT '0: janitor, 1: mod, 2: admin',
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`,`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=2 ;
--
-- Dumping data for table `mods`
--
INSERT INTO `mods` (`id`, `username`, `password`, `type`) VALUES
(1, 'admin', '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8', 2);
-- --------------------------------------------------------
--
-- Table structure for table `bans`
--
CREATE TABLE IF NOT EXISTS `bans` (
`ip` varchar( 15 ) NOT NULL ,
`mod` int NOT NULL COMMENT 'which mod made the ban',
`set` int NOT NULL,
`expires` int NULL,
`reason` text NULL
) ENGINE = InnoDB;
CREATE TABLE `robot` (
`hash` VARCHAR( 40 ) NOT NULL COMMENT 'SHA1'
) ENGINE = INNODB; */
CREATE TABLE `mutes` (
`ip` VARCHAR( 15 ) NOT NULL ,
`time` INT NOT NULL
) ENGINE = MYISAM ;
CREATE TABLE `modlogs` (
`mod` INT NOT NULL ,
`ip` VARCHAR( 15 ) NOT NULL ,
`time` INT NOT NULL ,
`text` TEXT NOT NULL
) ENGINE = INNODB;

69
main.js

@ -6,16 +6,31 @@ function highlightReply(id)
if (divs[i].className.indexOf('post') != -1)
divs[i].className = divs[i].className.replace(/highlighted/, '');
}
if (id)
document.getElementById('reply_'+id).className += ' highlighted';
if (id) {
post = document.getElementById('reply_'+id);
if(post)
post.className += ' highlighted';
}
}
function focusId(id)
{
document.getElementById(id).focus();
init();
}
function generatePassword() {
pass = '';
chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+';
for(i=0;i<8;i++) {
rnd = Math.floor(Math.random() * chars.length);
pass += chars.substring(rnd,rnd + 1);
}
return pass;
}
function dopost(form) {
localStorage.name = form.name.value;
localStorage.name = form.name.value.replace(/ ##.+$/, '');
if(form.email.value != 'sage')
localStorage.email = form.email.value;
return form.body.value != "" || (typeof form.thread != "undefined" && form.file.value != "");
@ -24,8 +39,56 @@ function citeReply(id) {
document.getElementById('body').value += '>>' + id + '\n';
}
var selectedstyle = 'Yotsuba B';
var styles = [
['Yotsuba B', '/default.css'],
['Yotsuba', '/yotsuba.css']
];
function changeStyle(x) {
localStorage.stylesheet = styles[x][1];
document.getElementById('stylesheet').href = styles[x][1];
selectedstyle = styles[x][0];
}
newLink = document.createElement('link');
newLink.rel = 'stylesheet';
newLink.type = 'text/css';
newLink.id = 'stylesheet';
document.getElementsByTagName('head')[0].insertBefore(newLink, document.getElementsByTagName('link')[0].lastChild)
if(localStorage.stylesheet) {
for(x=0;x<styles.length;x++) {
if(styles[x][1] == localStorage.stylesheet) {
changeStyle(x);
break;
}
}
}
function init()
{
newElement = document.createElement('div');
newElement.className = 'styles';
for(x=0;x<styles.length;x++) {
style = document.createElement('a');
style.innerHTML = '[' + styles[x][0] + ']';
style.href = 'javascript:changeStyle(' + x + ');';
if(selectedstyle == styles[x][0])
style.className = 'selected';
newElement.appendChild(style);
}
if(!localStorage.password)
localStorage.password = generatePassword();
elements = document.getElementsByName('password');
for(x=0;x<elements.length;x++) {
elements[x].value = localStorage.password;
}
document.getElementsByTagName('body')[0].insertBefore(newElement, document.getElementsByTagName('body')[0].lastChild)
if (window.location.hash.indexOf('q') == 1)
citeReply(window.location.hash.substring(2));
else if (window.location.hash.substring(1))

661
mod.php

@ -0,0 +1,661 @@
<?php
require 'inc/functions.php';
require 'inc/display.php';
require 'inc/config.php';
if (file_exists('inc/instance-config.php')) {
require 'inc/instance-config.php';
}
require 'inc/template.php';
require 'inc/database.php';
require 'inc/user.php';
sql_open();
// Check if banned
checkBan();
require 'inc/mod.php';
// Fix some encoding issues
header('Content-Type: text/html; charset=utf-8', true);
if (get_magic_quotes_gpc()) {
function strip_array($var) {
return is_array($var) ? array_map("strip_array", $var) : stripslashes($var);
}
$_SESSION = strip_array($_SESSION);
$_GET = strip_array($_GET);
$_POST = strip_array($_POST);
}
// If not logged in
if(!$mod) {
if(isset($_POST['login'])) {
// Check if inputs are set and not empty
if( !isset($_POST['username']) ||
!isset($_POST['password']) ||
empty($_POST['username']) ||
empty($_POST['password'])
) loginForm($config['error']['invalid'], $_POST['username']);
if(!login($_POST['username'], $_POST['password']))
loginForm($config['error']['invalid'], $_POST['username']);
modLog("Logged in.");
// Login successful
// Set cookies
setCookies();
// Redirect
header('Location: ?' . $config['mod']['default'], true, $config['redirect_http']);
// Close connection
sql_close();
} else {
loginForm();
}
} else {
$query = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '';
// A sort of "cache"
// Stops calling preg_quote and str_replace when not needed; only does it once
$regex = Array(
'board' => str_replace('%s', '(\w{1,8})', preg_quote($config['board_path'], '/')),
'page' => str_replace('%d', '(\d+)', preg_quote($config['file_page'], '/')),
'img' => preg_quote($config['dir']['img'], '/'),
'thumb' => preg_quote($config['dir']['thumb'], '/'),
'res' => preg_quote($config['dir']['res'], '/'),
'index' => preg_quote($config['file_index'], '/')
);
if(preg_match('/^\/?$/', $query)) {
// Dashboard
$fieldset = Array(
'Boards' => '',
'Administration' => ''
);
// Boards
$fieldset['Boards'] .= ulBoards();
if($mod['type'] >= $config['mod']['view_banlist']) {
$fieldset['Administration'] .= '<li><a href="?/bans">Ban list</a></li>';
}
if($mod['type'] >= $config['mod']['show_config']) {
$fieldset['Administration'] .= '<li><a href="?/config">Show configuration</a></li>';
}
// TODO: Statistics, etc, in the dashboard.
$body = '';
foreach($fieldset as $title => $data) {
if($data)
$body .= "<fieldset><legend>{$title}</legend><ul>{$data}</ul></fieldset>";
}
echo Element('page.html', Array(
'index'=>$config['root'],
'title'=>'Dashboard',
'body'=>$body
//,'mod'=>true /* All 'mod' does, at this point, is put the "Return to dashboard" link in. */
)
);
} elseif(preg_match('/^\/bans$/', $query)) {
if($mod['type'] < $config['mod']['view_banlist']) error($config['error']['noaccess']);
if($config['mod']['view_banexpired']) {
$query = prepare("SELECT * FROM `bans` INNER JOIN `mods` ON `mod` = `id` GROUP BY `ip` ORDER BY `expires` < :time, `set` DESC");
$query->bindValue(':time', time(), PDO::PARAM_INT);
$query->execute() or error(db_error($query));
} else {
// Filter out expired bans
$query = prepare("SELECT * FROM `bans` INNER JOIN `mods` ON `mod` = `id` GROUP BY `ip` WHERE `expires` = 0 OR `expires` > :time ORDER BY `set` DESC");
$query->bindValue(':time', time(), PDO::PARAM_INT);
$query->execute() or error(db_error($query));
}
if($query->rowCount() < 1) {
$body = '(There are no active bans.)';
} else {
$body = '<form action="" method="post">';
$body .= '<table><tr><th>IP address</th><th>Reason</th><th>Set</th><th>Expires</th><th>Staff</th><th>Actions</th></tr>';
while($ban = $query->fetch()) {
$body .=
'<tr' .
($config['mod']['view_banexpired'] && $ban['expires'] != 0 && $ban['expires'] < time() ?
' style="text-decoration:line-through"'
:'') .
'>' .
'<td style="white-space: nowrap">' .
// Checkbox
'<input type="checkbox" name="ban_' . $ban['ip'] . '" id="ban_' . $ban['ip'] . '" /> ' .
// IP address
'<a href="?/IP/' .
$ban['ip'] .
'">'. $ban['ip'] . '</a></td>' .
// Reason
'<td>' . $ban['reason'] . '</td>' .
// Set
'<td style="white-space: nowrap">' . date($config['post_date'], $ban['set']) . '</td>' .
// Expires
'<td style="white-space: nowrap">' .
($ban['expires'] == 0 ?
'<em>Never</em>'
:
date($config['post_date'], $ban['expires'])
) .
'</td>' .
// Staff
'<td>' .
($mod['type'] < $config['mod']['view_banstaff'] ?
($config['mod']['view_banquestionmark'] ?
'?'
:
($ban['type'] == JANITOR ? 'Janitor' :
($ban['type'] == MOD ? 'Mod' :
($ban['type'] == ADMIN ? 'Admin' :
'?')))
)
:
$ban['username']
) .
'</td>' .
'<td></td>' .
'</tr>';
}
$body .= '</table></form>';
}
echo Element('page.html', Array(
'index'=>$config['root'],
'title'=>'Ban list',
'body'=>$body,
'mod'=>true
)
);
} elseif(preg_match('/^\/config$/', $query)) {
if($mod['type'] < $config['mod']['show_config']) error($config['error']['noaccess']);
// Show instance-config.php
//$data = highlight_file('inc/instance-config.php', true);
//if(MOD_NEVER_REAL_PASSWORD) {
// // Rough and dirty removal of password
// $data = str_replace(MY_PASSWORD, '*******', $data);
//}
$constants = get_defined_constants(true);
$constants = $constants['user'];
$data = '';
foreach($constants as $name => $value) {
if(MOD_NEVER_REAL_PASSWORD && $name == 'DB_PASSWORD')
$value = '<em>hidden</em>';
else {
// For some reason PHP is only giving me the first defined value (the default), so use constant()
$value = constant($name);
if(gettype($value) == 'boolean') {
$value = $value ? '<span style="color:green;">On</span>' : '<span style="color:red;">Off</span>';
} elseif(gettype($value) == 'string') {
if(empty($value))
$value = '<em>empty</em>';
else
$value = '<span style="color:maroon;">' . utf8tohtml(substr($value, 0, 110) . (strlen($value) > 110 ? '…' : '')) . '</span>';
} elseif(gettype($value) == 'integer') {
// Show permissions in a cleaner way
if(preg_match('/^MOD_/', $name) && $name != 'MOD_JANITOR' && $name != 'MOD_MOD' && $name != 'MOD_ADMIN') {
if($value == MOD_JANITOR)
$value = 'Janitor';
elseif($value == MOD_MOD)
$value = 'Mod';
elseif($value == MOD_ADMIN)
$value = 'Admin';
}
$value = '<span style="color:black;">' . $value . '</span>';
}
}
$data .=
'<tr><th style="text-align:left;">' .
$name .
'</th><td>' .
$value .
'</td></tr>';
}
$body = '<fieldset><legend>Configuration</legend><table>' . $data . '</table></fieldset>';
echo Element('page.html', Array(
'index'=>$config['root'],
'title'=>'Configuration',
'body'=>$body,
'mod'=>true
)
);
} elseif(preg_match('/^\/new$/', $query)) {
if($mod['type'] < $config['mod']['newboard']) error($config['error']['noaccess']);
// New board
$body = '';
if(isset($_POST['new_board'])) {
// Create new board
if( !isset($_POST['uri']) ||
!isset($_POST['title']) ||
!isset($_POST['subtitle'])
) error($config['error']['missedafield']);
$b = Array(
'uri' => $_POST['uri'],
'title' => $_POST['title'],
'subtitle' => $_POST['subtitle']
);
// Check required fields
if(empty($b['uri']))
error(sprintf($config['error']['required'], 'URI'));
if(empty($b['title']))
error(sprintf($config['error']['required'], 'title'));
// Check string lengths
if(strlen($b['uri']) > 8)
error(sprintf($config['error']['toolong'], 'URI'));
if(strlen($b['title']) > 20)
error(sprintf($config['error']['toolong'], 'title'));
if(strlen($b['subtitle']) > 40)
error(sprintf($config['error']['toolong'], 'subtitle'));
if(!preg_match('/^\w+$/', $b['uri']))
error(sprintf($config['error']['invalidfield'], 'URI'));
if(openBoard($b['uri'])) {
unset($board);
error(sprintf($config['error']['boardexists'], sprintf($config['board_abbreviation'], $b['uri'])));
}
$query = prepare("INSERT INTO `boards` VALUES (NULL, :uri, :title, :subtitle)");
$query->bindValue(':uri', $b['uri']);
$query->bindValue(':title', $b['title']);
if(!empty($b['subtitle'])) {
$query->bindValue(':subtitle', $b['subtitle']);
} else {
$query->bindValue(':subtitle', null, PDO::PARAM_NULL);
}
$query->execute() or error(db_error($query));
// Record the action
modLog("Created a new board: {$b['title']}");
// Open the board
openBoard($b['uri']) or error("Couldn't open board after creation.");
// Create the posts table
query(Element('posts.sql', Array('board' => $board['uri']))) or error(db_error());
// Build the board
buildIndex();
}
$body .= form_newBoard();
// TODO: Statistics, etc, in the dashboard.
echo Element('page.html', Array(
'index'=>$config['root'],
'title'=>'New board',
'body'=>$body,
'mod'=>true
)
);
} elseif(preg_match('/^\/' . $regex['board'] . '(' . $regex['index'] . '|' . $regex['page'] . ')?$/', $query, $matches)) {
// Board index
$boardName = $matches[1];
// Open board
if(!openBoard($boardName))
error($config['error']['noboard']);
if(!$page = index(empty($matches[2]) || $matches[2] == $config['file_index'] ? 1 : $matches[2], $mod)) {
error($config['error']['404']);
}
$page['pages'] = getPages(true);
$page['mod'] = true;
echo Element('index.html', $page);
} elseif(preg_match('/^\/' . $regex['board'] . $regex['res'] . $regex['page'] . '$/', $query, $matches)) {
// View thread
$boardName = $matches[1];
$thread = $matches[2];
// Open board
if(!openBoard($boardName))
error($config['error']['noboard']);
$page = buildThread($thread, true, $mod);
echo $page;
} elseif(preg_match('/^\/' . $regex['board'] . 'deletefile\/(\d+)$/', $query, $matches)) {
if($mod['type'] < $config['mod']['deletefile']) error($config['error']['noaccess']);
// Delete file from post
$boardName = $matches[1];
$post = $matches[2];
// Open board
if(!openBoard($boardName))
error($config['error']['noboard']);
// Delete post
deleteFile($post);
// Record the action
modLog("Removed file from post #{$post}");
// Rebuild board
buildIndex();
// Redirect
if(isset($_SERVER['HTTP_REFERER']))
header('Location: ' . $_SERVER['HTTP_REFERER'], true, $config['redirect_http']);
else
header('Location: ?/' . sprintf($config['board_path'], $boardName) . $config['file_index'], true, $config['redirect_http']);
} elseif(preg_match('/^\/' . $regex['board'] . 'delete\/(\d+)$/', $query, $matches)) {
if($mod['type'] < $config['mod']['delete']) error($config['error']['noaccess']);
// Delete post
$boardName = $matches[1];
$post = $matches[2];
// Open board
if(!openBoard($boardName))
error($config['error']['noboard']);
// Delete post
deletePost($post);
// Record the action
modLog("Deleted post #{$post}");
// Rebuild board
buildIndex();
// Redirect
if(isset($_SERVER['HTTP_REFERER']))
header('Location: ' . $_SERVER['HTTP_REFERER'], true, $config['redirect_http']);
else
header('Location: ?/' . sprintf($config['board_path'], $boardName) . $config['file_index'], true, $config['redirect_http']);
} elseif(preg_match('/^\/' . $regex['board'] . '(un)?sticky\/(\d+)$/', $query, $matches)) {
if($mod['type'] < $config['mod']['sticky']) error($config['error']['noaccess']);
// Add/remove sticky
$boardName = $matches[1];
$post = $matches[3];
// Open board
if(!openBoard($boardName))
error($config['error']['noboard']);
$query = prepare(sprintf("UPDATE `posts_%s` SET `sticky` = :sticky WHERE `id` = :id AND `thread` IS NULL", $board['uri']));
$query->bindValue(':id', $post, PDO::PARAM_INT);
if($matches[2] == 'un') {
// Record the action
modLog("Unstickied post #{$post}");
$query->bindValue(':sticky', 0, PDO::PARAM_INT);
} else {
// Record the action
modLog("Stickied post #{$post}");
$query->bindValue(':sticky', 1, PDO::PARAM_INT);
}
$query->execute() or error(db_error($query));
buildIndex();
buildThread($post);
// Redirect
if(isset($_SERVER['HTTP_REFERER']))
header('Location: ' . $_SERVER['HTTP_REFERER'], true, $config['redirect_http']);
else
header('Location: ?/' . sprintf($config['board_path'], $boardName) . $config['file_index'], true, $config['redirect_http']);
} elseif(preg_match('/^\/' . $regex['board'] . '(un)?lock\/(\d+)$/', $query, $matches)) {
if($mod['type'] < $config['mod']['lock']) error($config['error']['noaccess']);
// Lock/Unlock
$boardName = $matches[1];
$post = $matches[3];
// Open board
if(!openBoard($boardName))
error($config['error']['noboard']);
$query = prepare(sprintf("UPDATE `posts_%s` SET `locked` = :locked WHERE `id` = :id AND `thread` IS NULL", $board['uri']));
$query->bindValue(':id', $post, PDO::PARAM_INT);
if($matches[2] == 'un') {
// Record the action
modLog("Unlocked post #{$post}");
$query->bindValue(':locked', 0, PDO::PARAM_INT);
} else {
// Record the action
modLog("Locked post #{$post}");
$query->bindValue(':locked', 1, PDO::PARAM_INT);
}
$query->execute() or error(db_error($query));
buildIndex();
buildThread($post);
// Redirect
if(isset($_SERVER['HTTP_REFERER']))
header('Location: ' . $_SERVER['HTTP_REFERER'], true, $config['redirect_http']);
else
header('Location: ?/' . sprintf($config['board_path'], $boardName) . $config['file_index'], true, $config['redirect_http']);
} elseif(preg_match('/^\/' . $regex['board'] . 'deletebyip\/(\d+)$/', $query, $matches)) {
// Delete all posts by an IP
$boardName = $matches[1];
$post = $matches[2];
// Open board
if(!openBoard($boardName))
error($config['error']['noboard']);
$query = prepare(sprintf("SELECT `ip` FROM `posts_%s` WHERE `id` = :id", $board['uri']));
$query->bindValue(':id', $post);
$query->execute() or error(db_error($query));
if(!$post = $query->fetch())
error($config['error']['invalidpost']);
$ip = $post['ip'];
// Record the action
modLog("Deleted all posts by IP address: #{$ip}");
$query = prepare(sprintf("SELECT `id` FROM `posts_%s` WHERE `ip` = :ip", $board['uri']));
$query->bindValue(':ip', $ip);
$query->execute() or error(db_error($query));
if($query->rowCount() < 1)
error($config['error']['invalidpost']);
while($post = $query->fetch()) {
deletePost($post['id'], false);
}
if(isset($_SERVER['HTTP_REFERER']))
header('Location: ' . $_SERVER['HTTP_REFERER'], true, $config['redirect_http']);
else
header('Location: ?/' . sprintf($config['board_path'], $boardName) . $config['file_index'], true, $config['redirect_http']);
} elseif(preg_match('/^\/ban$/', $query)) {
// Ban page
if(isset($_POST['new_ban'])) {
if( !isset($_POST['ip']) ||
!isset($_POST['reason']) ||
!isset($_POST['length'])
) error($config['error']['missedafield']);
// Check required fields
if(empty($_POST['ip']))
error(sprintf($config['error']['required'], 'IP address'));
$query = prepare("INSERT INTO `bans` VALUES (:ip, :mod, :set, :expires, :reason)");
// 1yr2hrs30mins
// 1y2h30m
$expire = 0;
if(preg_match('/^((\d+)\s?ye?a?r?s?)?\s?+((\d+)\s?we?e?k?s?)?\s?+((\d+)\s?da?y?s?)?((\d+)\s?ho?u?r?s?)?\s?+((\d+)\s?mi?n?u?t?e?s?)?\s?+((\d+)\s?se?c?o?n?d?s?)?$/', $_POST['length'], $m)) {
if(isset($m[2])) {
// Years
$expire += $m[2]*60*60*24*365;
}
if(isset($m[4])) {
// Weeks
$expire += $m[4]*60*60*24*7;
}
if(isset($m[6])) {
// Days
$expire += $m[6]*60*60*24;
}
if(isset($m[8])) {
// Hours
$expire += $m[8]*60*60;
}
if(isset($m[10])) {
// Minutes
$expire += $m[10]*60;
}
if(isset($m[12])) {
// Seconds
$expire += $m[12];
}
}
if($expire) {
$query->bindValue(':expires', time()+$expire, PDO::PARAM_INT);
} else {
// Never expire
$query->bindValue(':expires', null, PDO::PARAM_NULL);
}
$query->bindValue(':ip', $_POST['ip'], PDO::PARAM_STR);
$query->bindValue(':mod', $mod['id'], PDO::PARAM_INT);
$query->bindValue(':set', time(), PDO::PARAM_INT);
if(isset($_POST['reason'])) {
$query->bindValue(':reason', $_POST['reason'], PDO::PARAM_STR);
} else {
$query->bindValue(':reason', null, PDO::PARAM_NULL);
}
// Record the action
modLog("Created a ban for {$_POST['ip']} with reason {$_POST['reason']}");
$query->execute() or error(db_error($query));
// Delete too
if($mod['type'] >= $config['mod']['delete'] && isset($_POST['delete']) && isset($_POST['board'])) {
openBoard($_POST['board']);
deletePost(round($_POST['delete']));
}
// Redirect
if(isset($_POST['continue']))
header('Location: ' . $_POST['continue'], true, $config['redirect_http']);
else
header('Location: ?/' . sprintf($config['board_path'], $boardName) . $config['file_index'], true, $config['redirect_http']);
}
} elseif(preg_match('/^\/' . $regex['board'] . 'ban(&delete)?\/(\d+)$/', $query, $matches)) {
if($mod['type'] < $config['mod']['delete']) error($config['error']['noaccess']);
// Ban by post
$boardName = $matches[1];
$delete = isset($matches[2]) && $matches[2] == '&delete';
$post = $matches[3];
// Open board
if(!openBoard($boardName))
error($config['error']['noboard']);
$query = prepare(sprintf("SELECT `ip`,`id` FROM `posts_%s` WHERE `id` = :id LIMIT 1", $board['uri']));
$query->bindValue(':id', $post, PDO::PARAM_INT);
$query->execute() or error(db_error($query));
if($query->rowCount() < 1) {
error($config['error']['invalidpost']);
}
$post = $query->fetch();
$body = form_newBan($post['ip'], null, isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : false, $delete ? $post['id'] : false, $delete ? $boardName : false);
echo Element('page.html', Array(
'index'=>$config['root'],
'title'=>'New ban',
'body'=>$body,
'mod'=>true
)
);
} elseif(preg_match('/^\/IP\/(\d+\.\d+\.\d+\.\d+)$/', $query, $matches)) {
// View information on an IP address
$ip = $matches[1];
$host = $config['mod']['dns_lookup'] ? gethostbyaddr($ip) : false;
$body = '';
$boards = listBoards();
foreach($boards as &$_board) {
openBoard($_board['uri']);
$temp = '';
$query = prepare(sprintf("SELECT * FROM `posts_%s` WHERE `ip` = :ip ORDER BY `sticky` DESC, `time` DESC LIMIT :limit", $_board['uri']));
$query->bindValue(':ip', $ip);
$query->bindValue(':limit', $config['mod']['ip_recentposts'], PDO::PARAM_INT);
$query->execute() or error(db_error($query));
while($post = $query->fetch()) {
$po = new Post($post['id'], $post['thread'], $post['subject'], $post['email'], $post['name'], $post['trip'], $post['body'], $post['time'], $post['thumb'], $post['thumbwidth'], $post['thumbheight'], $post['file'], $post['filewidth'], $post['fileheight'], $post['filesize'], $post['filename'], $post['ip'], $mod ? '?/' : $config['root'], $mod);
$temp .= $po->build();
}
if(!empty($temp))
$body .= '<fieldset><legend>Last ' . $query->rowCount() . ' posts on <a href="?/' .
sprintf($config['board_path'], $_board['uri']) . $config['file_index'] .
'">' .
sprintf($config['board_abbreviation'], $_board['uri']) . ' - ' . $_board['title'] .
'</a></legend>' . $temp . '</fieldset>';
}
if($config['mod']['ip_banform'])
$body .= form_newBan($ip, null, isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : false);
echo Element('page.html', Array(
'index'=>$config['root'],
'title'=>'IP: ' . $ip,
'subtitle' => $host,
'body'=>$body,
'mod'=>true
)
);
} else {
error($config['error']['404']);
}
}
// Close the connection in-case it's still open
sql_close();
?>

357
post.php

@ -1,11 +1,12 @@
<?php
require 'inc/functions.php';
require 'inc/display.php';
require 'inc/config.php';
if (file_exists('inc/instance-config.php')) {
require 'inc/instance-config.php';
}
require 'inc/config.php';
require 'inc/template.php';
require 'inc/database.php';
require 'inc/user.php';
// Fix for magic quotes
@ -19,14 +20,77 @@
$_POST = strip_array($_POST);
}
if(isset($_POST['post'])) {
if(isset($_POST['delete'])) {
// Delete
if( !isset($_POST['board']) ||
!isset($_POST['password'])
)
error($config['error']['bot']);
$password = $_POST['password'];
if(empty($password))
error($config['error']['invalidpassword']);
$delete = Array();
foreach($_POST as $post => $value) {
if(preg_match('/^delete_(\d+)$/', $post, $m)) {
$delete[] = (int)$m[1];
}
}
sql_open();
// Check if banned
checkBan();
if($config['block_tor'] && isTor())
error($config['error']['tor']);
// Check if board exists
if(!openBoard($_POST['board']))
error($config['error']['noboard']);
if(empty($delete))
error($config['error']['nodelete']);
foreach($delete as &$id) {
$query = prepare(sprintf("SELECT `password` FROM `posts_%s` WHERE `id` = :id", $board['uri']));
$query->bindValue(':id', $id, PDO::PARAM_INT);
$query->execute() or error(db_error($query));
if($post = $query->fetch()) {
if(!empty($password) && $post['password'] != $password)
error($config['error']['invalidpassword']);
if(isset($_POST['file'])) {
// Delete just the file
deleteFile($id);
} else {
// Delete entire post
deletePost($id);
}
}
}
buildIndex();
sql_close();
$is_mod = isset($_POST['mod']) && $_POST['mod'];
$root = $is_mod ? $config['root'] . $config['file_mod'] . '?/' : $config['root'];
header('Location: ' . $root . $board['dir'] . $config['file_index'], true, $config['redirect_http']);
} elseif(isset($_POST['post'])) {
if( !isset($_POST['name']) ||
!isset($_POST['email']) ||
!isset($_POST['subject']) ||
!isset($_POST['body']) ||
!isset($_POST['board']) ||
!isset($_POST['password'])
) error(ERROR_BOT);
) error($config['error']['bot']);
$post = Array('board' => $_POST['board']);
@ -35,13 +99,13 @@
$post['thread'] = round($_POST['thread']);
} else $OP = true;
if(!(($OP && $_POST['post'] == BUTTON_NEWTOPIC) ||
(!$OP && $_POST['post'] == BUTTON_REPLY)))
error(ERROR_BOT);
if(!(($OP && $_POST['post'] == $config['button_newtopic']) ||
(!$OP && $_POST['post'] == $config['button_reply'])))
error($config['error']['bot']);
// Check the referrer
if($OP) {
if(!isset($_SERVER['HTTP_REFERER']) || !preg_match(URL_MATCH, $_SERVER['HTTP_REFERER'])) error(ERROR_BOT);
if(!isset($_SERVER['HTTP_REFERER']) || !preg_match($config['url_match'], $_SERVER['HTTP_REFERER'])) error($config['error']['bot']);
}
// TODO: Since we're now using static HTML files, we can't give them cookies on their first page view
@ -49,7 +113,7 @@
/*
// Check if he has a valid cookie.
if(!$user['valid']) error(ERROR_BOT);
if(!$user['valid']) error($config['error']['bot']);
// Check how long he has been here.
if(time()-$user['appeared']<LURKTIME) error(ERROR_LURK);
@ -58,18 +122,28 @@
// Open database connection
sql_open();
// Check if banned
checkBan();
if($config['block_tor'] && isTor())
error($config['error']['tor']);
// Check if board exists
if(!openBoard($post['board']))
error(ERROR_NOBOARD);
error($config['error']['noboard']);
//if(ROBOT_ENABLE && $board['uri'] == ROBOT_BOARD && ROBOT_MUTE) {
// checkMute();
//}
//Check if thread exists
if(!$OP && !threadExists($post['thread']))
error(ERROR_NONEXISTANT);
error($config['error']['nonexistant']);
// Check for a file
if($OP) {
if(!isset($_FILES['file']['tmp_name']) || empty($_FILES['file']['tmp_name']))
error(ERROR_NOIMAGE);
error($config['error']['noimage']);
}
$post['name'] = (!empty($_POST['name'])?$_POST['name']:'Anonymous');
@ -77,16 +151,52 @@
$post['email'] = utf8tohtml($_POST['email']);
$post['body'] = $_POST['body'];
$post['password'] = $_POST['password'];
$post['filename'] = $_FILES['file']['name'];
$post['filename'] = get_magic_quotes_gpc() ? stripslashes($_FILES['file']['name']) : $_FILES['file']['name'];
$post['has_file'] = $OP || !empty($_FILES['file']['tmp_name']);
$post['mod'] = isset($_POST['mod']) && $_POST['mod'];
if(empty($post['body']) && $config['force_body'])
error($config['error']['tooshortbody']);
if($post['mod']) {
require 'inc/mod.php';
if(!$mod) {
// Liar. You're not a mod.
error($config['error']['notamod']);
}
$post['sticky'] = $OP && isset($_POST['sticky']);
$post['locked'] = $OP && isset($_POST['lock']);
$post['raw'] = isset($_POST['raw']);
if($post['sticky'] && $mod['type'] < $config['mod']['sticky']) error($config['error']['noaccess']);
if($post['locked'] && $mod['type'] < $config['mod']['lock']) error($config['error']['noaccess']);
if($post['raw'] && $mod['type'] < $config['mod']['rawhtml']) error($config['error']['noaccess']);
}
// Check if thread is locked
// but allow mods to post
if(!$OP && (!$mod || $mod['type'] < $config['mod']['postinlocked'])) {
if(threadLocked($post['thread']))
error($config['error']['locked']);
}
if($post['has_file']) {
$size = $_FILES['file']['size'];
if($size > MAX_FILESIZE)
error(sprintf3(ERR_FILESIZE, array(
if($size > $config['max_filesize'])
error(sprintf3($config['error']['filesize'], array(
'sz'=>commaize($size),
'filesz'=>commaize($size),
'maxsz'=>commaize(MAX_FILESIZE))));
'maxsz'=>commaize($config['max_filesize']))));
}
if($mod && $mod['type'] >= MOD && preg_match('/^((.+) )?## (.+)$/', $post['name'], $match)) {
if(($mod['type'] == MOD && $match[3] == 'Mod') || $mod['type'] >= ADMIN) {
$post['mod_tag'] = $match[3];
$post['name'] = !empty($match[2])?$match[2]:'Anonymous';
}
} else {
$post['mod_tag'] = false;
}
$trip = generate_tripcode($post['name']);
@ -100,40 +210,37 @@
if($post['has_file']) {
$post['extension'] = strtolower(substr($post['filename'], strrpos($post['filename'], '.') + 1));
$post['file_id'] = rand(0, 1000000000);
$post['file'] = $board['dir'] . DIR_IMG . $post['file_id'] . '.' . $post['extension'];
$post['thumb'] = $board['dir'] . DIR_THUMB . $post['file_id'] . '.png';
$post['zip'] = $OP && $post['has_file'] && ALLOW_ZIP && $post['extension'] == 'zip' ? $post['file'] : false;
if(!($post['zip'] || in_array($post['extension'], $allowed_ext))) error(ERROR_FILEEXT);
$post['file_id'] = time() . rand(100, 999);
$post['file'] = $board['dir'] . $config['dir']['img'] . $post['file_id'] . '.' . $post['extension'];
$post['thumb'] = $board['dir'] . $config['dir']['thumb'] . $post['file_id'] . '.png';
}
// Check string lengths
if(strlen($post['name']) > 25) error(sprintf(ERROR_TOOLONG, 'name'));
if(strlen($post['email']) > 30) error(sprintf(ERROR_TOOLONG, 'email'));
if(strlen($post['subject']) > 40) error(sprintf(ERROR_TOOLONG, 'subject'));
if(strlen($post['body']) > MAX_BODY) error(ERROR_TOOLONGBODY);
if(!(!$OP && $post['has_file']) && strlen($post['body']) < 1) error(ERROR_TOOSHORTBODY);
if(strlen($post['password']) > 20) error(sprintf(ERROR_TOOLONG, 'password'));
if(strlen($post['name']) > 50) error(sprintf($config['error']['toolong'], 'name'));
if(strlen($post['email']) > 30) error(sprintf($config['error']['toolong'], 'email'));
if(strlen($post['subject']) > 40) error(sprintf($config['error']['toolong'], 'subject'));
if(!$mod && strlen($post['body']) > $config['max_body']) error($config['error']['toolongbody']);
if(!(!$OP && $post['has_file']) && strlen($post['body']) < 1) error($config['error']['tooshortbody']);
if(strlen($post['password']) > 20) error(sprintf($config['error']['toolong'], 'password'));
if($post['mod_tag'])
$post['trip'] .= ' <a class="nametag">## ' . $post['mod_tag'] . '</a>';
$post['body_nomarkup'] = $post['body'];
if(!($mod && $post['raw']))
markup($post['body']);
// Check for a flood
if(checkFlood($post)) {
error($config['error']['flood']);
}
if($post['has_file']) {
// Just trim the filename if it's too long
if(strlen($post['filename']) > 30) $post['filename'] = substr($post['filename'], 0, 27).'…';
// Move the uploaded file
if(!@move_uploaded_file($_FILES['file']['tmp_name'], $post['file'])) error(ERROR_NOMOVE);
if($post['zip']) {
// Validate ZIP file
if(is_resource($zip = zip_open($post['zip'])))
// TODO: Check if it's not empty and has at least one (valid) image
zip_close($zip);
else
error(ERR_INVALIDZIP);
$post['file'] = ZIP_IMAGE;
$post['extension'] = strtolower(substr($post['file'], strrpos($post['file'], '.') + 1));
}
if(!@move_uploaded_file($_FILES['file']['tmp_name'], $post['file'])) error($config['error']['nomove']);
$size = @getimagesize($post['file']);
$post['width'] = $size[0];
@ -142,189 +249,79 @@
// Check if the image is valid
if($post['width'] < 1 || $post['height'] < 1) {
unlink($post['file']);
error(ERR_INVALIDIMG);
error($config['error']['invalidimg']);
}
if($post['width'] > MAX_WIDTH || $post['height'] > MAX_HEIGHT) {
if($post['width'] > $config['max_width'] || $post['height'] > $config['max_height']) {
unlink($post['file']);
error(ERR_MAXSIZE);
error($config['error']['maxsize']);
}
$post['filehash'] = md5_file($post['file']);
$post['filehash'] = $config['file_hash']($post['file']);
$post['filesize'] = filesize($post['file']);
$image = createimage($post['extension'], $post['file']);
if(REDRAW_IMAGE && !$post['zip']) {
switch($post['extension']) {
case 'jpg':
case 'jpeg':
imagejpeg($image, $post['file'], JPEG_QUALITY);
break;
case 'png':
imagepng($image, $post['file'], 7);
break;
case 'gif':
if(REDRAW_GIF)
imagegif($image, $post['file']);
break;
case 'bmp':
imagebmp($image, $post['file']);
break;
default:
error('Unknwon file extension.');
}
}
// Create a thumbnail
$thumb = resize($image, $post['width'], $post['height'], $post['thumb'], THUMB_WIDTH, THUMB_HEIGHT);
$thumb = resize($image, $post['width'], $post['height'], $post['thumb'], $config['thumb_width'], $config['thumb_height']);
$post['thumbwidth'] = $thumb['width'];
$post['thumbheight'] = $thumb['height'];
}
// Remove DIR_* before inserting them into the database.
if($post['has_file']) {
$post['file'] = substr_replace($post['file'], '', 0, strlen($board['dir'] . DIR_IMG));
$post['thumb'] = substr_replace($post['thumb'], '', 0, strlen($board['dir'] . DIR_THUMB));
}
// Todo: Validate some more, remove messy code, allow more specific configuration
// MySQLify
mysql_safe_array($post);
$id = post($post, $OP);
if($post['has_file'] && $post['zip']) {
// Open ZIP
$zip = zip_open($post['zip']);
// Read files
while($entry = zip_read($zip)) {
$filename = basename(zip_entry_name($entry));
$extension = strtolower(substr($filename, strrpos($filename, '.') + 1));
if(in_array($extension, $allowed_ext)) {
if (zip_entry_open($zip, $entry, 'r')) {
// Fake post
$dump_post = Array(
'subject' => $post['subject'],
'email' => $post['email'],
'name' => $post['name'],
'trip' => $post['trip'],
'body' => '',
'thread' => $id,
'password' => '',
'has_file' => true,
'file_id' => rand(0, 1000000000),
'filename' => $filename
);
$dump_post['file'] = $board['dir'] . DIR_IMG . $dump_post['file_id'] . '.' . $extension;
$dump_post['thumb'] = $board['dir'] . DIR_THUMB . $dump_post['file_id'] . '.png';
// Extract the image from the ZIP
$fp = fopen($dump_post['file'], 'w+');
fwrite($fp, zip_entry_read($entry, zip_entry_filesize($entry)));
fclose($fp);
$size = @getimagesize($dump_post['file']);
$dump_post['width'] = $size[0];
$dump_post['height'] = $size[1];
// Check if the image is valid
if($dump_post['width'] < 1 || $dump_post['height'] < 1) {
unlink($dump_post['file']);
} else {
if($dump_post['width'] > MAX_WIDTH || $dump_post['height'] > MAX_HEIGHT) {
unlink($dump_post['file']);
error(ERR_MAXSIZE);
/*
if(!($mod && $mod['type'] >= $config['mod']['postunoriginal']) && ROBOT_ENABLE && $board['uri'] == ROBOT_BOARD && checkRobot($post['body_nomarkup'])) {
if(ROBOT_MUTE) {
error(sprintf($config['error']['muted'], mute()));
} else {
$dump_post['filehash'] = md5_file($dump_post['file']);
$dump_post['filesize'] = filesize($dump_post['file']);
$image = createimage($extension, $dump_post['file']);
$success = true;
if(REDRAW_IMAGE) {
switch($extension) {
case 'jpg':
case 'jpeg':
imagejpeg($image, $dump_post['file'], JPEG_QUALITY);
break;
case 'png':
imagepng($image, $dump_post['file'], 7);
break;
case 'gif':
if(REDRAW_GIF)
imagegif($image, $dump_post['file']);
break;
case 'bmp':
imagebmp($image, $dump_post['file']);
break;
default:
$success = false;
error($config['error']['unoriginal']);
}
}
// Create a thumbnail
$thumb = resize($image, $dump_post['width'], $dump_post['height'], $dump_post['thumb'], THUMB_WIDTH, THUMB_HEIGHT);
$dump_post['thumbwidth'] = $thumb['width'];
$dump_post['thumbheight'] = $thumb['height'];
*/
// Remove DIR_* before inserting them into the database.
$dump_post['file'] = substr_replace($dump_post['file'], '', 0, strlen($board['dir'] . DIR_IMG));
$dump_post['thumb'] = substr_replace($dump_post['thumb'], '', 0, strlen($board['dir'] . DIR_THUMB));
// Create the post
post($dump_post, false);
}
if($post['has_file']) {
$post['file'] = substr_replace($post['file'], '', 0, strlen($board['dir'] . $config['dir']['img']));
$post['thumb'] = substr_replace($post['thumb'], '', 0, strlen($board['dir'] . $config['dir']['thumb']));
}
// Close the ZIP
zip_entry_close($entry);
}
}
}
zip_close($zip);
unlink($post['zip']);
}
// Todo: Validate some more, remove messy code, allow more specific configuration
$id = post($post, $OP);
buildThread(($OP?$id:$post['thread']));
if(!$OP) {
mysql_query(
sprintf("UPDATE `posts` SET `bump` = '%d' WHERE `id` = '%s' AND `thread` IS NULL",
time(),
$post['thread']
), $sql) or error(mysql_error($sql));
if(!$OP && strtolower($post['email']) != 'sage' && ($config['reply_limit'] == 0 || numPosts($post['thread']) < $config['reply_limit'])) {
bumpThread($post['thread']);
}
if($OP)
clean();
buildIndex();
sql_close();
if(ALWAYS_NOKO || $noko) {
header('Location: ' . ROOT . $board['dir'] . DIR_RES . ($OP?$id:$post['thread']) . '.html' . (!$OP?'#'.$id:''), true, 302);
$root = $post['mod'] ? $config['root'] . $config['file_mod'] . '?/' : $config['root'];
if($config['always_noko'] || $noko) {
header('Location: ' . $root . $board['dir'] . $config['dir']['res'] . ($OP?$id:$post['thread']) . '.html' . (!$OP?'#'.$id:''), true, $config['redirect_http']);
} else {
header('Location: ' . ROOT . $board['dir'] . FILE_INDEX, true, 302);
header('Location: ' . $root . $board['dir'] . $config['file_index'], true, $config['redirect_http']);
}
exit;
} else {
if(!file_exists(HAS_INSTALLED)) {
if(!file_exists($config['has_installed'])) {
sql_open();
// Build all boards
$boards_res = mysql_query('SELECT * FROM `boards`', $sql) or error(mysql_error($sql));
while($_board = mysql_fetch_array($boards_res)) {
$boards = listBoards();
foreach($boards as &$_board) {
setupBoard($_board);
buildIndex();
}
sql_close();
touch(HAS_INSTALLED, 0777);
touch($config['has_installed'], 0777);
die(Element('page.html', Array(
'index'=>ROOT,
@ -336,7 +333,7 @@
} else {
// They opened post.php in their browser manually.
// Possible TODO: Redirect back to homepage.
error(ERROR_NOPOST);
error($config['error']['nopost']);
}
}
?>

3
res/index.php

@ -1,3 +0,0 @@
<?php
header('Location: ../', true, 302);
?>

3
src/index.php

@ -1,3 +0,0 @@
<?php
header('Location: ../', true, 302);
?>

BIN
static/deleted.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

0
src/error.png → static/error.png

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
static/locked.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 B

0
src/ok.png → static/ok.png

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
static/sticky.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 961 B

0
src/warning.png → static/warning.png

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

0
src/zip.png → static/zip.png

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

103
style.css

@ -80,6 +80,18 @@ form table tr th {
form table tr th {
background: #98E;
}
form table tr td div {
text-align: cetner;
float: left;
padding-left: 3px;
}
form table tr td div input {
display: block;
margin: 2px auto 0 auto;
}
form table tr td div label {
font-size: 10px;
}
.unimportant, .unimportant * {
font-size: 10px;
}
@ -101,6 +113,10 @@ div.banner a:hover {
color: #EEF2FF;
text-decoration: none;
}
img.banner {
float: none;
margin: 4px auto 0 auto;
}
img {
display: block;
float: left;
@ -111,6 +127,12 @@ div.post img {
padding: 5px;
margin: 5px 20px 0 0;
}
div.post img.icon {
display: inline;
float: none;
margin: 0 5px;
padding: 0;
}
div.post.op {
margin-right: 20px;
margin-bottom: 5px;
@ -120,6 +142,10 @@ p.intro {
padding: 0;
padding-bottom: 0.2em;
}
input.delete {
float: left;
margin: 1px 6px 0 0;
}
p.intro span.subject {
color: #0F0C5D;
font-weight: bold;
@ -128,9 +154,16 @@ p.intro span.name {
color: #117743;
font-weight: bold;
}
p.intro a.nametag {
color: #F00000;
margin-left: 0;
}
p.intro a {
margin-left: 8px;
}
div.delete {
float: right;
}
div.post.reply p {
margin: 0.3em 0 0 0;
}
@ -168,3 +201,73 @@ span.omitted {
br.clear {
clear: left;
}
span.controls {
float: right;
margin: 0;
padding: 0;
font-size: 80%;
}
span.controls.op {
float: none;
margin-left: 10px;
}
span.controls a {
margin: 0;
}
div#wrap {
width: 900px;
margin:0 auto;
}
div.ban {
background: white;
border: 1px solid #98E;
max-width: 700px;
margin: 30px auto;
}
div.ban p, div.ban h2 {
padding: 3px 7px;
}
div.ban h2 {
background: #98E;
color: black;
font-size: 12pt;
}
div.ban p {
font-size: 12px;
margin-bottom: 12px;
}
div.ban p.reason {
font-weight: bold;
}
span.spoiler {
background: black;
}
span.spoiler:hover {
color: white;
}
div.styles {
float: right;
padding-bottom: 20px;
}
div.styles a {
margin: 0 10px;
}
div.styles a.selected {
text-decoration: none;
}
table.test {
width: 100%;
}
table.test td, table.test th {
text-align: left;
padding: 5px;
}
table.test tr.h th {
background: #98E;
}
table.test td img {
margin: 0;
}
fieldset label {
display: block;
}

47
templates/index.html

@ -1,26 +1,27 @@
<!DOCTYPE html>
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" media="screen" href="{index}style.css"/>
<title>{board[url]} - {board[name]}</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf8" />
<meta http-equiv="Cache-Control" content="no-cache, must-revalidate" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<script type="text/javascript" src="{index}main.js"></script>
</head>
<body>
<h1>{board[url]} - {board[name]}</h1>
<div class="title">{board[title]?{board[title]}}</div>
<div class="title">{board[title]?{board[title]}}<p>{mod?<a href="?/">Return to dashboard</a>}</p></div>
<form onsubmit="return dopost(this);" enctype="multipart/form-data" action="{post_url}" method="post">
<input type="hidden" name="board" value="{board[uri]}" />
{mod?<input type="hidden" name="mod" value="1" />}
<table>
<tr>
<th>
Name
</th>
<td>
<input type="text" name="name" size="25" maxlength="25" autocomplete="off" />
<input type="text" name="name" size="25" maxlength="50" autocomplete="off" />
</td>
</tr>
<tr>
@ -37,7 +38,7 @@
</th>
<td>
<input style="float:left;" type="text" name="subject" size="25" maxlength="25" autocomplete="off" />
<input style="float:right;" type="submit" name="post" value="{button=New Topic}" />
<input accesskey="s" style="float:right;" type="submit" name="post" value="{button=New Topic}" />
</td>
</tr>
<tr>
@ -56,6 +57,27 @@
<input type="file" name="file"/>
</td>
</tr>
{mod?
<tr>
<th>
Flags
</th>
<td>
<div>
<label for="sticky">Sticky</label>
<input title="Sticky" type="checkbox" name="sticky" id="sticky"><br/>
</div>
<div>
<label for="lock">Lock</label><br/>
<input title="Lock" type="checkbox" name="lock" id="lock">
</div>
<div>
<label for="raw">Raw HTML</label><br/>
<input title="Raw HTML" type="checkbox" name="raw" id="raw">
</div>
</td>
</tr>
}
<tr>
<th>
Password
@ -68,10 +90,21 @@
</table>
</form>
<hr/>
<form action="{post_url}" method="post">
<input type="hidden" name="delete" value="1" />
<input type="hidden" name="board" value="{board[uri]}" />
{mod?<input type="hidden" name="mod" value="1" />}
{body}
<div class="delete">
Delete Post [<input title="Delete file only" type="checkbox" name="file" id="delete_file"/>
<label for="delete_file">File</label>] Password
<input type="password" name="password" size="12" maxlength="18" />
<input type="submit" value="Delete" />
</div>
</form>
Pages: {pages:
[<a href="{pages[link]}">{pages[num]}</a>]{!%last? }
}
<p class="unimportant" style="text-align:center;">Copyright &copy; 2010 <a href="http://omegadev.org/">OmegaSDG</a></p>
<p class="unimportant" style="text-align:center;">Tinyboard Software Copyright &copy; 2010-2011 <a href="http://omegasdg.com/">OmegaSDG</a></p>
</body>
</html>

28
templates/login.html

@ -0,0 +1,28 @@
<center>
{error?<h2>{error}</h2>}
<form action="" method="post">
<table style="margin-top:25px;">
<tr>
<th>
Username
</th>
<td>
<input type="text" name="username" size="20" maxlength="30" value="{username}">
</td>
</tr>
<tr>
<th>
Password
</th>
<td>
<input type="password" name="password" size="20" maxlength="30" value="">
</td>
</tr>
<tr>
<td></td>
<td>
<input type="submit" name="login" value="Continue" />
</td>
</table>
</form>
</center>

8
templates/page.html

@ -1,16 +1,16 @@
<!DOCTYPE html>
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" media="screen" href="{index}style.css"/>
<title>{title}</title>
<meta http-equiv="Cache-Control" content="no-cache, must-revalidate" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
</head>
<body>
<h1>{title}</h1>
<div class="title">{subtitle?{subtitle}}</div>
<div class="title">{subtitle?{subtitle}}<p>{mod?<a href="?/">Return to dashboard</a>}</p></div>
{body}
<hr/>
<p class="unimportant" style="text-align:center;">Copyright &copy; 2010 <a href="http://omegadev.org/">OmegaSDG</a></p>
<p class="unimportant" style="text-align:center;">Tinyboard Software Copyright &copy; 2010-2011 <a href="http://omegasdg.com/">OmegaSDG</a></p>
</body>
</html>

25
templates/posts.sql

@ -0,0 +1,25 @@
CREATE TABLE IF NOT EXISTS `posts_{board}` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`thread` int(11) DEFAULT NULL,
`subject` varchar(40) NOT NULL,
`email` varchar(30) NOT NULL,
`name` varchar(25) NOT NULL,
`trip` varchar(45) DEFAULT NULL,
`body` text NOT NULL,
`time` int(11) NOT NULL,
`bump` int(11) DEFAULT NULL,
`thumb` varchar(50) DEFAULT NULL,
`thumbwidth` int(11) DEFAULT NULL,
`thumbheight` int(11) DEFAULT NULL,
`file` varchar(50) DEFAULT NULL,
`filewidth` int(11) DEFAULT NULL,
`fileheight` int(11) DEFAULT NULL,
`filesize` int(11) DEFAULT NULL,
`filename` varchar(30) DEFAULT NULL,
`filehash` varchar(32) DEFAULT NULL,
`password` varchar(20) DEFAULT NULL,
`ip` varchar(15) NOT NULL,
`sticky` int(1) NOT NULL,
`locked` int(1) NOT NULL,
UNIQUE KEY `id` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;

41
templates/thread.html

@ -1,28 +1,28 @@
<!DOCTYPE html>
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" media="screen" href="{index}style.css"/>
<title>{board[url]} - {board[name]}</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf8" />
<meta http-equiv="Cache-Control" content="no-cache, must-revalidate" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<script type="text/javascript" src="{index}main.js"></script>
</head>
<body>
<h1>{board[url]} - {board[name]}</h1>
<div class="title">{board[title]?{board[title]}}</div>
<div class="title">{board[title]?{board[title]}}<p>{mod?<a href="?/">Return to dashboard</a>}</p></div>
<div class="banner">Posting mode: Reply <a class="unimportant" href="{index}{board[dir]}">[Return.]</a></div>
<div class="banner">Posting mode: Reply <a class="unimportant" href="{return}">[Return.]</a></div>
<form onsubmit="return dopost(this);" enctype="multipart/form-data" action="{post_url}" method="post">
<input type="hidden" name="thread" value="{id}" />
<input type="hidden" name="board" value="{board[uri]}" />
{mod?<input type="hidden" name="mod" value="1" />}
<table>
<tr>
<th>
Name
</th>
<td>
<input type="text" name="name" size="25" maxlength="25" autocomplete="off" />
<input type="text" name="name" size="25" maxlength="50" autocomplete="off" />
</td>
</tr>
<tr>
@ -39,7 +39,7 @@
</th>
<td>
<input style="float:left;" type="text" name="subject" size="25" maxlength="25" autocomplete="off" />
<input style="float:right;" type="submit" name="post" value="{button=New Topic}" />
<input accesskey="s" style="float:right;" type="submit" name="post" value="{button=New Topic}" />
</td>
</tr>
<tr>
@ -58,6 +58,19 @@
<input type="file" name="file"/>
</td>
</tr>
{mod?
<tr>
<th>
Flags
</th>
<td>
<div>
<label for="raw">Raw HTML</label><br/>
<input title="Raw HTML" type="checkbox" name="raw" id="raw">
</div>
</td>
</tr>
}
<tr>
<th>
Password
@ -70,7 +83,19 @@
</table>
</form>
<hr/>
<form action="{post_url}" method="post">
<input type="hidden" name="delete" value="1" />
<input type="hidden" name="board" value="{board[uri]}" />
{mod?<input type="hidden" name="mod" value="1" />}
{body}
<p class="unimportant" style="text-align:center;">Copyright &copy; 2010 <a href="http://omegadev.org/">OmegaSDG</a></p>
<div class="delete">
Delete Post [<input title="Delete file only" type="checkbox" name="file" id="delete_file"/>
<label for="delete_file">File</label>] Password
<input type="password" name="password" size="12" maxlength="18" />
<input type="submit" value="Delete" />
</div>
</form>
<a href="{return}">[Return.]</a>
<p class="unimportant" style="text-align:center;">Tinyboard Software Copyright &copy; 2010-2011 <a href="http://omegasdg.com/">OmegaSDG</a></p>
</body>
</html>

112
test.php

@ -1,112 +0,0 @@
<?php
define('IS_INSTALLATION', true);
require 'inc/functions.php';
require 'inc/display.php';
if (file_exists('inc/instance-config.php')) {
require 'inc/instance-config.php';
}
require 'inc/config.php';
require 'inc/template.php';
require 'inc/user.php';
function image($type) {
return "<img src=\"src/{$type}.png\" style=\"margin:0px;width:16px;height:16px;\" />";
}
function check($title, $test) {
global $body, $count;
$count[$test]++;
$body .= '<tr><td style="width:100%;border-bottom:1px solid #DDD;">' . $title . '</td><td style="width:1%;white-space:nowrap;border-bottom:1px solid #DDD;">' . image($test) . '</td></tr>';
}
function title($text) {
global $body;
$body .= '<tr><td colspan="2" style="padding-top:15px;font-weight:bold;width:100%;border-bottom:1px solid #DDD;">' . $text . '</td></tr>';
}
$count = Array('ok'=>0, 'warning'=>0, 'error'=>0);
$todo = Array();
$body = '<table style="width:600px;margin:auto;">';
$extensions = Array('mysql', 'gd');
// Extensions
title('Extensions');
foreach($extensions as &$ext) {
if(extension_loaded($ext)) {
$body .= check($ext, 'ok');
} else {
$body .= check($ext, 'error');
$todo[] = 'Install module "' . $ext . '"';
}
}
// Database
title('Database');
if(extension_loaded('mysql')) {
if($sql = @mysql_connect(MY_SERVER, MY_USER, MY_PASSWORD)) {
$body .= check('Connection to server.', 'ok');
if(@mysql_select_db(MY_DATABASE, $sql))
$body .= check('Select database.', 'ok');
else {
$body .= check('Select database.', 'error');
$todo[] = 'instance-config.php: Check database configuration.';
}
} else {
$body .= check('Connection to server.', 'error');
$todo[] = 'instance-config.php: Check database configuration.';
}
}
// Configuration
title('Configuration');
$root = dirname($_SERVER['REQUEST_URI']) . (dirname($_SERVER['REQUEST_URI']) == '/' ? '' : '/');
if(ROOT != $root) {
$body .= check('Correct document root.', 'error');
$todo[] = "instance-config.php: Change ROOT to '{$root}'";
} else
$body .= check('Correct document root.', 'ok');
// Permissions
title('Permissions');
$directories = Array(DIR_IMG, DIR_THUMB, DIR_RES, '.');
foreach($directories as $dir) {
if(file_exists($dir)) {
if(is_writable($dir) && is_readable($dir)) {
$body .= check($dir, 'ok');
} else {
$body .= check($dir, 'error');
$todo[] = 'CHMOD ' . $dir . ' to allow PHP to read and write.';
}
} else {
$body .= check($dir, 'error');
$todo[] = 'Create directory: ' . $dir;
}
}
// Other
title('Other');
if(get_magic_quotes_gpc()) {
$body .= check('magic_quotes_gpc', 'warning');
$todo[] = 'Recommended: Disable magic_quotes_gpc in your PHP configuration.';
} else
$body .= check('magic_quotes_gpc', 'ok');
$body .= '</table>';
if(!empty($todo)) {
$body .= '<pre style="width:600px;margin:20px auto;">';
foreach($todo as $do)
$body .= "{$do}\n";
$body .= '</pre>';
}
if(!$count['error']) {
$body .= '<p style="text-align:center;">Everything seems okay.</p>';
}
$body .= '<p style="text-align:center;font-weight:bold;">Disregard me. I haven\'t been patched to work with multiple boards yet.</p>';
die(Element('page.html', Array('index' => ROOT, 'title'=>'Tinyboard', 'subtitle'=>'Installation', 'body'=>$body)));
?>

3
thumb/index.php

@ -1,3 +0,0 @@
<?php
header('Location: ../', true, 302);
?>

40
yotsuba.css

@ -0,0 +1,40 @@
body {
background: #ffe url('img/fade-yotsuba.png') repeat-x 50% 0%;
color: #800000;
}
a:link, a:visited, p.intro a.email span.name {
color: #0000ff;
}
a:link:hover {
color: #d00;
}
a.post_no {
color: #800000;
}
div.post.reply {
background: #f0e0d6;
border-color: #d9bfb7;
}
div.post.reply.highlighted {
background: #f0c0b0;
border-color: #d9bfb7;
}
div.post.reply p.body a {
color: navy;
}
p.intro span.subject {
color: #d00;
}
form table tr th {
background: #EA8;
}
div.ban h2 {
background: #FCA;
color: inherit;
}
div.ban {
border-color: #800;
}
div.ban p {
color: black;
}
Loading…
Cancel
Save