3 require('includes/header.php');
9 $onload_javascript = 'focusId(\'body\'); init();';
11 if( ! ctype_digit($_GET['reply']))
13 add_error('Invalid topic ID.', true);
16 $stmt = $link->prepare('SELECT headline, author, replies FROM topics WHERE id = ?');
17 $stmt->bind_param('i', $_GET['reply']);
19 $stmt->store_result();
20 if($stmt->num_rows
< 1)
22 $page_title = 'Non-existent topic';
23 add_error('There is no such topic. It may have been deleted.', true);
25 $stmt->bind_result($replying_to, $topic_author, $topic_replies);
29 update_activity('replying', $_GET['reply']);
30 $page_title = 'New reply in topic: <a href="/topic/' . $_GET['reply'] . '">' . htmlspecialchars($replying_to) . '</a>';
32 $check_watchlist = $link->prepare('SELECT 1 FROM watchlists WHERE uid = ? AND topic_id = ?');
33 $check_watchlist->bind_param('si', $_SESSION['UID'], $_GET['reply']);
34 $check_watchlist->execute();
35 $check_watchlist->store_result();
36 if($check_watchlist->num_rows
> 0)
38 $watching_topic = true;
40 $check_watchlist->close();
42 else // this is a topic
45 $onload_javascript = 'focusId(\'headline\'); init();';
46 update_activity('new_topic');
48 $page_title = 'New topic';
50 if( ! empty($_POST['headline']))
52 $page_title .= ': ' . htmlspecialchars($_POST['headline']);
56 // If we're trying to edit and it's not disabled in the configuration ...
57 if(ALLOW_EDIT
&& ctype_digit($_GET['edit']))
63 $fetch_edit = $link->prepare('SELECT author, time, body, edit_mod FROM replies WHERE id = ?');
67 $fetch_edit = $link->prepare('SELECT author, time, body, edit_mod, headline FROM topics WHERE id = ?');
70 $fetch_edit->bind_param('i', $_GET['edit']);
71 $fetch_edit->execute();
72 $fetch_edit->store_result();
73 if($fetch_edit->num_rows
< 1)
75 add_error('There is no such post. It may have been deleted.', true);
80 $fetch_edit->bind_result($edit_data['author'], $edit_data['time'], $edit_data['body'], $edit_data['mod']);
81 $page_title = 'Editing <a href="/topic/' . $_GET['reply'] . '#reply_' . $_GET['edit'] . '">reply</a> to topic: <a href="/topic/' . $_GET['reply'] . '">' . htmlspecialchars($replying_to) . '</a>';
85 $fetch_edit->bind_result($edit_data['author'], $edit_data['time'], $edit_data['body'], $edit_data['mod'], $edit_data['headline']);
86 $page_title = 'Editing topic';
92 if($edit_data['author'] === $_SESSION['UID'])
96 if( ! $administrator && ! $moderator)
98 if(TIME_TO_EDIT
!= 0 && ( $_SERVER['REQUEST_TIME'] - $edit_data['time'] > TIME_TO_EDIT
))
100 add_error('You can no longer edit your post.', true);
102 if($edit_data['mod'])
104 add_error('You cannot edit a post that has been edited by a moderator.');
108 else if($administrator ||
$moderator)
114 add_error('You are not allowed to edit that post.', true);
117 if( ! $_POST['form_sent'])
119 $body = $edit_data['body'];
123 $page_title .= ': <a href="/topic/' . $_GET['edit'] . '">' . htmlspecialchars($edit_data['headline']) . '</a>';
124 $headline = $edit_data['headline'];
127 else if( ! empty($_POST['headline']))
129 $page_title .= ': <a href="/topic/' . $_GET['edit'] . '">' . htmlspecialchars($_POST['headline']) . '</a>';
133 if($_POST['form_sent'])
136 $headline = super_trim($_POST['headline']);
137 $body = super_trim($_POST['body']);
139 // Parse for mass quote tag ([quote]). I'm not sure about create_function, it seems kind of slow.
140 $body = preg_replace_callback(
141 '/\[quote\](.+?)\[\/quote\]/s',
144 'return preg_replace(\'/.*[^\s]$/m\', \'> $0\', $matches[1]);'
151 // Check for poorly made bots.
152 if( ! $editing && $_SERVER['REQUEST_TIME'] - $_POST['start_time'] < 3 )
154 add_error('Wait a few seconds between starting to compose a post and actually submitting it.');
156 if( ! empty($_POST['e-mail']))
158 add_error('Bot detected.');
160 if( ! is_array($_SESSION['random_posting_hashes']) )
162 add_error('Session error (no hash values stored). Try again.');
164 else foreach($_SESSION['random_posting_hashes'] as $name => $value)
166 if( ! isset($_POST[$name]) ||
$_POST[$name] != $value)
168 add_error('Session error (wrong hash value sent). Try again.');
173 check_length($body, 'body', MIN_LENGTH_BODY
, MAX_LENGTH_BODY
);
175 if(count( explode("\n", $body) ) > MAX_LINES
)
177 add_error('Your post has too many lines.');
180 // Check for UID ban.
181 $check_uid_ban = $link->prepare('SELECT filed FROM uid_bans WHERE uid = ?');
182 $check_uid_ban->bind_param('s', $_SESSION['UID']);
183 $check_uid_ban->execute();
184 $check_uid_ban->store_result();
185 if($check_uid_ban->num_rows
> 0)
187 $check_uid_ban->bind_result($ban_filed);
188 $check_uid_ban->fetch();
190 $time_since_ban = $_SERVER['REQUEST_TIME'] - $ban_filed;
191 if($time_since_ban < BAN_PERIOD
)
193 add_error('You are banned. Your ban will expire in ' . calculate_age( $_SERVER['REQUEST_TIME'], $ban_filed + BAN_PERIOD
) . '.');
197 remove_id_ban($_SESSION['UID']);
200 $check_uid_ban->close();
203 // Check for IP address ban.
204 $check_ip_ban = $link->prepare('SELECT expiry FROM ip_bans WHERE ip_address = ?');
205 $check_ip_ban->bind_param('s', $_SERVER['REMOTE_ADDR']);
206 $check_ip_ban->execute();
207 $check_ip_ban->store_result();
208 if($check_ip_ban->num_rows
> 0)
210 $check_ip_ban->bind_result($ban_expiry);
211 $check_ip_ban->fetch();
213 if($ban_expiry == 0 |
$ban_expiry > $_SERVER['REQUEST_TIME'])
215 $error_message = 'Your IP address is banned. ';
218 $error_message .= 'This ban will expire in ' . calculate_age($ban_expiry) . '.';
222 $error_message .= 'This ban is not set to expire.';
224 add_error($error_message);
228 remove_ip_ban($_SERVER['REMOTE_ADDR']);
231 $check_ip_ban->close();
233 if(ALLOW_IMAGES
&& ! empty($_FILES['image']['name']) && ! $editing)
235 $image_data = array();
237 switch($_FILES['image']['error'])
243 case UPLOAD_ERR_PARTIAL
:
244 add_error('The image was only partially uploaded.');
247 case UPLOAD_ERR_INI_SIZE
:
248 add_error('The uploaded file exceeds the upload_max_filesize directive in php.ini.');
251 case UPLOAD_ERR_NO_FILE
:
252 add_error('No file was uploaded.');
255 case UPLOAD_ERR_NO_TMP_DIR
:
256 add_error('Missing a temporary directory.');
259 case UPLOAD_ERR_CANT_WRITE
:
260 add_error('Failed to write image to disk.');
264 add_error('Unable to upload image.');
269 $uploading = false; // until we make our next checks
277 $valid_name = preg_match('/(.+)\.([a-z0-9]+)$/i', $_FILES['image']['name'], $match);
278 $image_data['type'] = strtolower($match[2]);
279 $image_data['md5'] = md5_file($_FILES['image']['tmp_name']);
280 $image_data['name'] = str_replace( array('.', '/', '<', '>', '"', "'", '%') , '', $match[1]);
281 $image_data['name'] = substr( trim($image_data['name']) , 0, 35);
283 if($image_data['type'] == 'jpeg')
285 $image_data['type'] = 'jpg';
288 if(file_exists('img/' . $image_data['name'] . '.' . $image_data['type']))
290 $image_data['name'] = $_SERVER['REQUEST_TIME'] . mt_rand(0, 99);
293 if($valid_name === 0 ||
empty($image_data['name']))
295 add_error('The image has an invalid file name.');
297 else if( ! in_array($image_data['type'], $valid_types))
299 add_error('Only <strong>GIF</strong>, <strong>JPEG</strong> and <strong>PNG</strong> files are allowed.');
301 else if($_FILES['image']['size'] > MAX_IMAGE_SIZE
)
303 add_error('Uploaded images can be no greater than ' . round(MAX_IMAGE_SIZE
/ 1048576, 2) . ' MB. ');
308 $image_data['name'] = $image_data['name'] . '.' . $image_data['type'];
313 // Set the author (internal use only)
314 $author = $_SESSION['UID'];
315 if(isset($_POST['admin']) && $administrator)
320 // If this is a reply...
326 if($_SERVER['REQUEST_TIME'] - $_SESSION['first_seen'] < REQUIRED_LURK_TIME_REPLY
)
328 add_error('Lurk for at least ' . REQUIRED_LURK_TIME_REPLY
. ' seconds before posting your first reply.');
332 $too_early = $_SERVER['REQUEST_TIME'] - FLOOD_CONTROL_REPLY
;
333 $stmt = $link->prepare('SELECT 1 FROM replies WHERE author_ip = ? AND time > ?');
334 $stmt->bind_param('si', $_SERVER['REMOTE_ADDR'], $too_early);
337 $stmt->store_result();
338 if($stmt->num_rows
> 0)
340 add_error('Wait at least ' . FLOOD_CONTROL_REPLY
. ' seconds between each reply. ');
344 // Get letter, if applicable.
345 if($_SESSION['UID'] == $topic_author)
349 else // we are not the topic author
351 $stmt = $link->prepare('SELECT poster_number FROM replies WHERE parent_id = ? AND author = ? LIMIT 1');
352 $stmt->bind_param('is', $_GET['reply'], $author);
354 $stmt->bind_result($poster_number);
358 // If the user has not already replied to this thread, get a new letter.
359 if(empty($poster_number))
361 // We need to lock the table to prevent others from selecting the same letter.
362 $unlock_table = true;
363 $link->real_query('LOCK TABLE replies WRITE');
365 $stmt = $link->prepare('SELECT poster_number FROM replies WHERE parent_id = ? ORDER BY poster_number DESC LIMIT 1');
366 $stmt->bind_param('i', $_GET['reply']);
368 $stmt->bind_result($last_number);
372 if(empty($last_number))
378 $poster_number = $last_number +
1;
383 $stmt = $link->prepare('INSERT INTO replies (author, author_ip, poster_number, parent_id, body, time) VALUES (?, ?, ?, ?, ?, UNIX_TIMESTAMP())');
384 $stmt->bind_param('ssiis', $author, $_SERVER['REMOTE_ADDR'], $poster_number, $_GET['reply'], $body);
385 $congratulation = 'Reply posted.';
389 $stmt = $link->prepare('UPDATE replies SET body = ?, edit_mod = ?, edit_time = UNIX_TIMESTAMP() WHERE id = ?');
390 $stmt->bind_param('sii', $body, $edit_mod, $_GET['edit']);
391 $congratulation = 'Reply edited.';
394 else { // or a topic...
395 check_length($headline, 'headline', MIN_LENGTH_HEADLINE
, MAX_LENGTH_HEADLINE
);
400 if($_SERVER['REQUEST_TIME'] - $_SESSION['first_seen'] < REQUIRED_LURK_TIME_TOPIC
)
402 add_error('Lurk for at least ' . REQUIRED_LURK_TIME_TOPIC
. ' seconds before posting your first topic.');
406 $too_early = $_SERVER['REQUEST_TIME'] - FLOOD_CONTROL_TOPIC
;
407 $stmt = $link->prepare('SELECT 1 FROM topics WHERE author_ip = ? AND time > ?');
408 $stmt->bind_param('si', $_SERVER['REMOTE_ADDR'], $too_early);
411 $stmt->store_result();
412 if($stmt->num_rows
> 0)
414 add_error('Wait at least ' . FLOOD_CONTROL_TOPIC
. ' seconds before creating another topic. ');
418 // Prepare our query...
419 $stmt = $link->prepare('INSERT INTO topics (author, author_ip, headline, body, last_post, time) VALUES (?, ?, ?, ?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP())');
420 $stmt->bind_param('ssss', $author, $_SERVER['REMOTE_ADDR'], $headline, $body);
421 $congratulation = 'Topic created.';
425 $stmt = $link->prepare('UPDATE topics SET headline = ?, body = ?, edit_mod = ?, edit_time = UNIX_TIMESTAMP() WHERE id = ?');
426 $stmt->bind_param('ssii', $headline, $body, $edit_mod, $_GET['edit']);
427 $congratulation = 'Topic edited.';
431 // If all is well, execute!
437 $link->real_query('UNLOCK TABLE');
440 if($stmt->affected_rows
> 0)
445 setcookie('last_bump', time(), $_SERVER['REQUEST_TIME'] +
315569260, '/');
449 $link->real_query("UPDATE last_actions SET time = UNIX_TIMESTAMP() WHERE feature = 'last_bump'");
451 $increment_replies = $link->prepare('UPDATE topics SET replies = replies + 1, last_post = UNIX_TIMESTAMP() WHERE id = ?');
452 $increment_replies->bind_param('i', $_GET['reply']);
453 $increment_replies->execute();
454 $increment_replies->close();
458 // Do not change the time() below to REQUEST_TIME. The script execution may have taken a second.
459 setcookie('last_topic', time(), $_SERVER['REQUEST_TIME'] +
315569260, '/');
460 //Update last topic and last bump, for people using the "date created" order option in the dashboard.
461 $link->real_query("UPDATE last_actions SET time = UNIX_TIMESTAMP() WHERE feature = 'last_topic' OR feature = 'last_bump'");
465 // Sort out what topic we're affecting and where to go next. Way too fucking long.
468 $inserted_id = $stmt->insert_id
;
472 $target_topic = $_GET['edit'];
473 $redir_loc = $_GET['reply'] . '#reply_' . $inserted_id;
477 $target_topic = $inserted_id;
478 $redir_loc = $inserted_id;
485 $target_topic = $_GET['reply'];
486 $redir_loc = $_GET['reply'] . '#reply_' . $_GET['edit'];
490 $target_topic = $_GET['edit'];
491 $redir_loc = $_GET['edit'];
495 // Take care of the upload.
498 // Check if this image is already on the server.
499 $duplicate_check = $link->prepare('SELECT file_name FROM images WHERE md5 = ?');
500 $duplicate_check->bind_param('s', $image_data['md5']);
501 $duplicate_check->execute();
502 $duplicate_check->bind_result($previous_image);
503 $duplicate_check->fetch();
504 $duplicate_check->close();
506 // If the file has been uploaded before this, just link the old version.
509 $image_data['name'] = $previous_image;
511 // Otherwise, keep the new image and make a thumbnail.
514 thumbnail($_FILES['image']['tmp_name'], $image_data['name'], $image_data['type']);
515 move_uploaded_file($_FILES['image']['tmp_name'], 'img/' . $image_data['name']);
520 $insert_image = $link->prepare('INSERT INTO images (file_name, md5, reply_id) VALUES (?, ?, ?)');
524 $insert_image = $link->prepare('INSERT INTO images (file_name, md5, topic_id) VALUES (?, ?, ?)');
526 $insert_image->bind_param('ssi', $image_data['name'], $image_data['md5'], $inserted_id);
527 $insert_image->execute();
528 $insert_image->close();
531 // Add topic to watchlist if desired.
532 if($_POST['watch_topic'] && ! $watching_topic)
534 $add_watchlist = $link->prepare('INSERT INTO watchlists (uid, topic_id) VALUES (?, ?)');
535 $add_watchlist->bind_param('si', $_SESSION['UID'], $target_topic);
536 $add_watchlist->execute();
537 $add_watchlist->close();
540 // The random shit is only good for one post to prevent spambots from reusing the same form data again and again.
541 unset($_SESSION['random_posting_hashes']);
542 // Set the congratulation notice and redirect to affected topic or reply.
543 redirect($congratulation, 'topic/' . $redir_loc);
545 else // Our query failed ;_;
547 add_error('Database error.');
552 // If we erred, insert this into failed postings.
557 $link->real_query('UNLOCK TABLE');
562 $add_fail = $link->prepare('INSERT INTO failed_postings (time, uid, reason, body) VALUES (UNIX_TIMESTAMP(), ?, ?, ?)');
563 $add_fail->bind_param('sss', $_SESSION['UID'], serialize($errors), substr($body, 0, MAX_LENGTH_BODY
));
567 $add_fail = $link->prepare('INSERT INTO failed_postings (time, uid, reason, body, headline) VALUES (UNIX_TIMESTAMP(), ?, ?, ?, ?)');
568 $add_fail->bind_param('ssss', $_SESSION['UID'], serialize($errors), substr($body, 0, MAX_LENGTH_BODY
), substr($headline, 0, MAX_LENGTH_HEADLINE
));
570 $add_fail->execute();
578 // For the bot check.
579 $start_time = $_SERVER['REQUEST_TIME'];
580 if( ctype_digit($_POST['start_time']) )
582 $start_time = $_POST['start_time'];
588 if($reply && ! $editing)
590 echo '<p>You <strong>are';
591 if($_SESSION['UID'] !== $topic_author)
595 echo '</strong> recognized as the original poster of this topic.</p>';
598 // Print deadline for edit submission.
599 if($editing && TIME_TO_EDIT
!= 0 && ! $moderator && ! $administrator)
601 echo '<p>You have <strong>' . calculate_age( $_SERVER['REQUEST_TIME'], $edit_data['time'] + TIME_TO_EDIT
) . '</strong> left to finish editing this post.</p>';
605 if($_POST['preview'] && ! empty($body))
607 $preview_body = parse($body);
608 $preview_body = preg_replace('/^@([0-9,]+|OP)/m', '<span class="unimportant"><a href="#">$0</a></span>', $preview_body);
609 echo '<h3 id="preview">Preview</h3><div class="body standalone">' . $preview_body . '</div>';
612 // Check if any new replies have been posted since we last viewed the topic.
613 if($reply && isset($visited_topics[ $_GET['reply'] ]) && $visited_topics[ $_GET['reply'] ] < $topic_replies)
615 $new_replies = $topic_replies - $visited_topics[$_GET['reply']];
616 echo '<p><a href="/topic/' . $_GET['reply'] . '#new"><strong>' . $new_replies . '</strong> new repl' . ($new_replies == 1 ?
'y</a> has' : 'ies</a> have') . ' been posted in this topic since you last checked!</p>';
619 // Print the main form.
623 <form action
="" method
="post"<?php
if(ALLOW_IMAGES
) echo ' enctype="multipart/form-data"' ?
>>
624 <div
class="noscreen">
625 <input name
="form_sent" type
="hidden" value
="1" />
626 <input name
="e-mail" type
="hidden" />
627 <input name
="start_time" type
="hidden" value
="<?php echo $start_time ?>" />
629 // For the bot check.
630 if( ! is_array($_SESSION['random_posting_hashes']) )
632 for($i = 0, $max = mt_rand(3, 12); $i < $max; ++
$i)
634 $_SESSION['random_posting_hashes'][ dechex(mt_rand()) ] = dechex(mt_rand());
638 foreach($_SESSION['random_posting_hashes'] as $name => $value)
642 'name="' . $name . '"',
643 'value="' . $value . '"',
646 // To make life harder for bots, print the elements in a random order.
647 shuffle($attributes);
648 echo '<input ' . implode(' ', $attributes) . ' />' . "\n\t\t\t";
654 <?php
if( ! $reply): ?
>
656 <label
for="headline">Headline
</label
> <script type
="text/javascript"> printCharactersRemaining('headline_remaining_characters', 100); </script
>
657 <input id
="headline" name
="headline" tabindex
="1" type
="text" size
="124" maxlength
="100" onkeydown
="updateCharactersRemaining('headline', 'headline_remaining_characters', 100);" onkeyup
="updateCharactersRemaining('headline', 'headline_remaining_characters', 100);" value
="<?php if($_POST['form_sent'] || $editing) echo htmlspecialchars($headline) ?>">
662 <label
for="body" class="noscreen">Post body
</label
>
663 <textarea name
="body" cols
="120" rows
="18" tabindex
="2" id
="body"><?php
664 // If we've had an error or are previewing, print the submitted text.
665 if($_POST['form_sent'] ||
$editing)
667 echo sanitize_for_textarea($body);
670 // Otherwise, fetch any text we may be quoting.
671 else if(isset($_GET['quote_topic']) ||
ctype_digit($_GET['quote_reply']))
673 // Fetch the topic...
674 if(isset($_GET['quote_topic']))
676 $stmt = $link->prepare('SELECT body FROM topics WHERE id = ?');
677 $stmt->bind_param('i', $_GET['reply']);
682 echo '@' . number_format($_GET['quote_reply']) . "\n\n";
684 $stmt = $link->prepare('SELECT body FROM replies WHERE id = ?');
685 $stmt->bind_param('i', $_GET['quote_reply']);
690 $stmt->bind_result($quoted_text);
694 // Snip citations from quote.
695 $quoted_text = trim( preg_replace('/^@([0-9,]+|OP)/m', '', $quoted_text) );
697 //Prefix newlines with >
698 $quoted_text = preg_replace('/^/m', '> ', $quoted_text);
700 echo sanitize_for_textarea($quoted_text) . "\n\n";
703 // If we're just citing, print the citation.
704 else if(ctype_digit($_GET['cite']))
706 echo '@' . number_format($_GET['cite']) . "\n\n";
711 if(ALLOW_IMAGES
&& ! $editing)
713 echo '<label for="image" class="noscreen">Image</label> <input type="file" name="image" id="image" />';
717 <p
><a href
="/markup_syntax">Markup syntax
</a
>: <kbd
>''</kbd
> on each side of a word
or part of text
= <em
>emphasis
</em
>. <kbd
>'''</kbd> = <strong>strong emphasis</strong>. <kbd>></kbd> on the beginning of a line = quote. To mass quote a long section of text, surround it with <kbd>[quote]</kbd> tags. <abbr>URL</abbr>s are automatically linkified.</p>
721 if( ! $watching_topic)
723 echo '<div
class="row"><label
for="watch_topic" class="inline">Watch topic
</label
> <input type
="checkbox" name
="watch_topic" id
="watch_topic" class="inline"';
724 if($_POST['watch_topic
'])
726 echo ' checked
="checked"';
730 if($administrator && ! $editing)
732 echo '<div
class="row"><label
for="admin" class="inline">Post
as admin
</label
> <input type
="checkbox" name
="admin" id
="admin" class="inline"></div
>';
738 <input type="submit" name="preview" tabindex="3" value="Preview" class="inline"<?php if(ALLOW_IMAGES) echo ' onclick
="document.getElementById(\'image\').value=\'\'"' ?> />
739 <input type="submit" name="post" tabindex="4" value="<?php echo ($editing) ? 'Update
' : 'Post
' ?>" class="inline">
746 // If citing, fetch and display the reply in question.
747 if(ctype_digit($_GET['cite
']))
749 $stmt = $link->prepare('SELECT body
, poster_number FROM replies WHERE id
= ?
');
750 $stmt->bind_param('i
', $_GET['cite
']);
752 $stmt->bind_result($cited_text, $poster_number);
756 if( ! empty($cited_text))
758 $cited_text = parse($cited_text);
760 // Linkify citations within the text.
761 preg_match_all('/^
@([0-9,]+
)/m
', $cited_text, $matches);
762 foreach($matches[0] as $formatted_id)
764 $pure_id = str_replace( array('@', ',') , '', $formatted_id);
766 $cited_text = str_replace($formatted_id, '<a href
="/topic/' . $_GET['reply'] . '#reply_' . $pure_id . '" class="unimportant">' . $formatted_id . '</a
>', $cited_text);
770 echo '<h3 id
="replying_to">Replying to Anonymous
' . number_to_letter($poster_number) . '&hellip
;</h3
> <div
class="body standalone">' . $cited_text . '</div
>';
773 // If we're not citing
or quoting
, display the original post
.
774 else if($reply && ! isset($_GET['quote_topic']) && ! isset($_GET['quote_reply']) && ! $editing)
776 $stmt = $link->prepare('SELECT body FROM topics WHERE id = ?');
777 $stmt->bind_param('i', $_GET['reply']);
779 $stmt->bind_result($cited_text);
783 echo '<h3 id="replying_to">Original post</h3> <div class="body standalone">' . parse($cited_text) . '</div>';
786 require('includes/footer.php');