Commits

thomas  committed 6bc952e

init

  • Participants

Comments (0)

Files changed (24)

+syntax: glob
+
+incl.config.php
+Options FollowSymLinks -MultiViews
+RewriteEngine on
+
+RewriteRule ^$ page.index.php [L]
+RewriteRule ^([0-9]+)$ page.index.php?page=$1 [L]
+
+RewriteRule ^nieuw/$ page.index.php?new [L]
+RewriteRule ^nieuw/([0-9]+)$ page.index.php?new&page=$1 [L]
+
+RewriteRule ^links/([0-9]+)$ page.links.php?link=$1 [L]
+
+RewriteRule ^reacties/$ page.comments.php [L]
+RewriteRule ^reacties/([0-9])$ page.comments.php?page=$1 [L]
+
+RewriteRule ^gebruikers/$ page.users.php [L]
+RewriteRule ^gebruikers/(.+)/reacties$ page.user.comments.php?user=$1 [L]
+RewriteRule ^gebruikers/(.+)/reacties/([0-9]+)$ page.user.comments.php?user=$1&page=$2 [L]
+RewriteRule ^gebruikers/(.+)/links$ page.user.links.php?user=$1 [L]
+RewriteRule ^gebruikers/(.+)/links/([0-9]+)$ page.user.links.php?user=$1&page=$2 [L]
+RewriteRule ^gebruikers/(.+)$ page.users.php?user=$1 [L]
+
+RewriteRule ^toevoegen/$ page.submit.php [L]
+
+RewriteRule ^aanmelden/$ page.login.php [L]
+
+RewriteRule ^afmelden/$ page.login.php?logout [L]

File incl.config.php.copy.me

+<?php
+/**
+ * To keep the config out of version control you have to copy this file
+ * (if the name == "incl.config.php.copy.me")
+ * and rename it to "incl.config.php".
+ * Make sure "incl.config.php" is ignored by version control
+ */
+
+// database settings
+$db_settings = array('host' => 'localhost',
+                        'name' => 'dbname',
+                        'user' => 'dbuser',
+                        'pass' => 'dbpass');
+
+// relative or absolute base URL
+define('BASE', '/');
+
+// max links and comments per page
+define('MAX_ENTRIES_PER_PAGE', 20);
+
+// bcrypt iterations
+define('PASSWORD_ITERARION', 8);
+
+// minimal karma before a user can downvote
+define('MIN_KARMA_TO_DOWNVOTE', 500);

File incl.functions.php

+<?php
+
+function checkVotePOST() {
+    global $D;
+
+    if (!isset($_SESSION['user'])) {
+       return;
+    }
+
+    foreach ($_POST as $key => $value) {
+        if (strpos($key, 'vote_link_up_') === 0) {
+            $linkId = (int)str_replace('vote_link_up_', '', $key);
+            $voteType = 'up';
+            break;
+        }
+
+        if (strpos($key, 'vote_comment_up_') === 0) {
+                $commentId = (int)str_replace('vote_comment_up_', '', $key);
+                $voteType = 'up';
+                break;
+        }
+
+        if ($_SESSION['user']['karma'] >= MIN_KARMA_TO_DOWNVOTE) {
+            if (strpos($key, 'vote_comment_down_') === 0) {
+                $commentId = (int)str_replace('vote_comment_down_', '', $key);
+                $voteType = 'down';
+                break;
+            }
+
+            if (strpos($key, 'vote_link_down_') === 0) {
+                $linkId = (int)str_replace('vote_link_down_', '', $key);
+                $voteType = 'down';
+                break;
+            }
+        }
+    }
+
+    if ((isset($linkId) || isset($commentId)) && isset($voteType)) {
+
+        $table = isset($linkId) ? 'link_votes' : 'comment_votes';
+        $karmaTable = isset($linkId) ? 'links' : 'comments';
+        $idField = isset($linkId) ? 'link_id' : 'comment_id';
+
+        if (!$D->query('SELECT * FROM ' . $table . '
+                        WHERE ' . $idField . ' = ' . (isset($linkId) ? (int)$linkId : (int)$commentId) . '
+                            AND `user` = "' . $D->real_escape_string($_SESSION['user']['user']) . '"')->fetch_row()) {
+
+            $D->query('INSERT INTO ' . $table . ' (
+                            `' . $idField . '`,
+                            `user`,
+                            `vote`
+                        ) VALUES (
+                            ' . (isset($linkId) ? (int)$linkId : (int)$commentId) . ',
+                            "' . $D->real_escape_string($_SESSION['user']['user']) . '",
+                            ' . ($voteType == 'up' ? 1 : -1) . '
+                        )');
+
+            $D->query('UPDATE ' . $karmaTable . '
+                        SET karma = karma ' . ($voteType == 'up' ? ' + 1' : ' - 1') . '
+                        ' . (isset($linkId) && $voteType == 'up' ? ', last_upvote_time = ' . time() : '') . '
+                        WHERE ' . $idField . ' = ' . (isset($linkId) ? (int)$linkId : (int)$commentId));
+
+            $D->query('UPDATE `users`
+                        SET karma = karma ' . ($voteType == 'up' ? ' + 1' : ' - 1') . '
+                        WHERE `user` = (
+                            SELECT submitted_by
+                            FROM ' . $karmaTable . '
+                            WHERE ' . $idField . ' = ' . (isset($linkId) ? (int)$linkId : (int)$commentId) . '
+                        )');
+        }
+    }
+}
+
+function exitWithHTTPstatus($status) {
+    $statuses = array();
+    $statuses[401] = 'Unauthorized';
+    $statuses[403] = 'Forbidden';
+    $statuses[404] = 'Not Found';
+
+    if (isset($statuses[$status])) {
+        header('HTTP/1.1 ' . $status . ' ' . $statuses[$status]);
+        header('status: ' . $status . ' ' . $statuses[$status]);
+        exit($statuses[$status]);
+    }
+
+    exit();
+}
+
+function returnKarmaStyle($karma) {
+    if($karma < 0) {
+        $opacity = 1 + $karma * 0.3;
+        if($opacity < 0.05) $opacity = 0.05;
+        return ' style="opacity:' . $opacity . '"';
+    }
+}
+
+function returnVoteButtons($type, $id, $userVotes) {
+    global $L;
+
+    $out = '';
+    if (!in_array($id, $userVotes) && isset($_SESSION['user'])) {
+        $out .= '<input class="voteButton" type="submit" name="vote_' . $type . '_up_' . $id . '" value="' . $L['special_vote_up'] . '" title="' . $L['button_vote_up'] . '">';
+        if ($_SESSION['user']['karma'] >= MIN_KARMA_TO_DOWNVOTE) {
+            $out .= '<input class="voteButton" type="submit" name="vote_' . $type . '_down_' . $id . '" value="' . $L['special_vote_down'] . '" title="' . $L['button_vote_down'] . '">';
+        }
+    }
+    return $out;
+}
+
+function returnFormattedText($in) {
+    $out = str_replace("\r", '', $in);
+    $out = str_replace("\n\n", '</p><p>', $out);
+    $out = str_replace("\n", '<br>', $out);
+    $out = '<p>' . $out . '</p>';
+
+    $urlregex = '~(?:https?)://[a-z0-9+$_-]+(?:\\.[a-z0-9+$_-]+)*(?:/(?:[a-z0-9+$_-]\\.?)+)*/?(?:\\?[a-z+&$_.-][a-z0-9;:@/&%=+$_.-]*)?(?:#[a-z_.-][a-z0-9+$_.-]*)?~i';
+    $out = preg_replace($urlregex, '<a href="$0">$0</a>', $out);
+
+    return $out;
+}
+
+function returnCommentMeta($comment, $showRelatedLink = false) {
+    global $L;
+
+    $meta = '';
+    if (isset($_SESSION['user']) && $_SESSION['user']['user'] == $comment['submitted_by']) {
+        $meta .= str_replace('[x]', $comment['karma'], $comment['karma'] == 1 ? $L['label_point'] : $L['label_points']) . ' ';
+    }
+    $meta .= str_replace('[x]', '<a href="' . BASE . $L['url_users'] . '/' . rawurlencode($comment['submitted_by']) . '">' . $comment['submitted_by'] . '</a>', $L['label_by_user']);
+    $meta .= ' ' . returnTimeAgoStr($comment['entry_time'], $L);
+    if ($showRelatedLink) {
+        $meta .= ', ' . str_replace('[x]', '<a href="' . BASE . $L['url_links'] . '/' . $comment['link_id'] . '#' . $L['url_comment'] . '_' . $comment['comment_id'] . '">' . $comment['link_title'] . '</a>', $L['label_commenting_on']);
+    }
+    return $meta;
+}
+
+function returnLinkMeta($link) {
+    global $L;
+
+    $meta = str_replace('[x]', $link['karma'], $link['karma'] == 1 ? $L['label_point'] : $L['label_points']);
+    $meta .= ' ' . str_replace('[x]', '<a href="' . BASE . $L['url_users'] . '/' . rawurlencode($link['submitted_by']) . '">' . $link['submitted_by'] . '</a>', $L['label_by_user']);
+    $meta .= ' ' . returnTimeAgoStr($link['entry_time'], $L) . ',';
+    $meta .= ' <a href="' . BASE . $L['url_links'] . '/' . $link['link_id'] . '">' . str_replace('[x]', $link['comments'], $link['comments'] == 1 ? $L['label_comment'] : $L['label_comments']) . '</a>';
+    return $meta;
+}
+
+function returnBaseUrl($url) {
+    $parts = explode('/', $url);
+    if (isset($parts[2]))
+        return $parts[2];
+    return 'invalid url';
+}
+
+function returnTimeAgoStr($time, $language) {
+    $minAgo = floor((time() - $time) / 60);
+    if ($minAgo >= 60) {
+        $hoursAgo = floor($minAgo / 60);
+        if ($hoursAgo >= 24) {
+            $daysAgo = floor($hoursAgo / 24);
+            return str_replace('[x]', $daysAgo, $language['label_day' . ($daysAgo == 1 ? '' : 's') . '_ago']);
+        }
+        return str_replace('[x]', $hoursAgo, $language['label_hour' . ($hoursAgo == 1 ? '' : 's') . '_ago']);
+    }
+    return str_replace('[x]', $minAgo, $language['label_minute' . ($minAgo == 1 ? '' : 's') . '_ago']);
+}

File incl.initialize.php

+<?php
+
+session_start();
+
+if (!isset($_SESSION['token'])) {
+    $_SESSION['token'] = base64_encode(openssl_random_pseudo_bytes(32));
+}
+
+if ($_SERVER['REQUEST_METHOD'] == 'POST' && (!isset($_POST['token']) || $_POST['token'] != $_SESSION['token'])) {
+    exitWithHTTPstatus(403);
+}
+
+include('incl.config.php');
+include('incl.lang.nl.php');
+include('incl.functions.php');
+
+$D = new mysqli($db_settings['host'], $db_settings['user'], $db_settings['pass'], $db_settings['name']);
+unset($db_settings);
+
+if (isset($_SESSION['user'])) {
+    $user = $D->query('SELECT `karma` FROM `users` WHERE `user` = "' . $D->real_escape_string($_SESSION['user']['user']) . '"')->fetch_assoc();
+    $_SESSION['user']['karma'] = $user['karma'];
+}

File incl.lang.nl.php

+<?php
+
+$L = array();
+
+$L['title_site_name'] = 'LinkDump';
+$L['title_new'] = 'Nieuw toegevoegd';
+$L['title_submit'] = 'Nieuwe link toegevoegen';
+$L['title_up'] = 'Omhoog';
+$L['title_down'] = 'Omlaag';
+$L['title_login_or_register'] = 'Aanmelden of registreren';
+$L['title_login'] = 'Aanmelden met je account';
+$L['title_register'] = 'Een account registreren';
+$L['title_comments'] = 'Reacties';
+$L['title_links'] = 'Links';
+$L['title_users'] = 'Gebruikers';
+$L['title_links_by_x'] = 'Links toegevoegd door [x]';
+$L['title_comments_by_x'] = 'Reacties door [x]';
+$L['title_change_password'] = 'Wachtwoord wijzigen';
+
+$L['button_site_name'] = 'LinkDump';
+$L['button_new'] = 'Nieuw';
+$L['button_comments'] = 'Reacties';
+$L['button_submit'] = 'Toevoegen';
+$L['button_login'] = 'Aanmelden';
+$L['button_logout'] = 'Afmelden';
+$L['button_register'] = 'Registreren';
+$L['button_place_comment'] = 'Reageer';
+$L['button_more'] = 'Meer';
+$L['button_vote_up'] = 'Omhoog';
+$L['button_vote_down'] = 'Omlaag';
+$L['button_change'] = 'Wijzigen';
+
+$L['head_title'] = 'Titel';
+$L['head_url'] = 'URL';
+$L['head_note'] = 'Opmerking';
+$L['head_comment'] = 'Reactie';
+$L['head_username'] = 'Gebruikersnaam';
+$L['head_password'] = 'Wachtwoord';
+$L['head_new_password'] = 'Nieuw wachtwoord';
+
+$L['label_points'] = '[x] punten';
+$L['label_point'] = '[x] punt';
+$L['label_comments'] = '[x] reacties';
+$L['label_comment'] = '[x] reactie';
+$L['label_links'] = '[x] links';
+$L['label_link'] = '[x] link';
+$L['label_by_user'] = 'door [x]';
+$L['label_minutes_ago'] = '[x] minuten geleden';
+$L['label_minute_ago'] = '[x] minuut geleden';
+$L['label_hours_ago'] = '[x] uren geleden';
+$L['label_hour_ago'] = '[x] uur geleden';
+$L['label_days_ago'] = '[x] dagen geleden';
+$L['label_day_ago'] = '[x] dag geleden';
+$L['label_commenting_on'] = 'in reactie op [x]';
+$L['label_login_to_comment'] = 'Meld je aan om een reactie te geven.';
+$L['label_since_x'] = 'sinds [x]';
+
+$L['error_invalid_url'] = 'Voeg alleen URL\'s toe die beginnen met http:// of https://';
+$L['error_title_required'] = 'Een titel is verplicht';
+$L['error_username_required'] = 'Vul een gebruikersnaam in';
+$L['error_password_required'] = 'Vul een wachtwoord in';
+$L['error_comment_required'] = 'Vul een reactie in';
+$L['error_username_taken'] = 'Deze gebruikersnaam is al in gebruik';
+$L['error_wrong_login'] = 'De ingevulde gegevens zijn onjuist';
+
+$L['special_meta_title_sep'] = ' ← ';
+$L['special_vote_up'] = '▴';
+$L['special_vote_down'] = '▾';
+
+$L['url_links'] = 'links';
+$L['url_new'] = 'nieuw';
+$L['url_comments'] = 'reacties';
+$L['url_comment'] = 'reactie';
+$L['url_submit'] = 'toevoegen';
+$L['url_login'] = 'aanmelden';
+$L['url_logout'] = 'afmelden';
+$L['url_users'] = 'gebruikers';
+

File lib/PasswordHash.php

+<?php
+#
+# Portable PHP password hashing framework.
+#
+# Version 0.3 / genuine.
+#
+# Written by Solar Designer <solar at openwall.com> in 2004-2006 and placed in
+# the public domain.  Revised in subsequent years, still public domain.
+#
+# There's absolutely no warranty.
+#
+# The homepage URL for this framework is:
+#
+#	http://www.openwall.com/phpass/
+#
+# Please be sure to update the Version line if you edit this file in any way.
+# It is suggested that you leave the main version number intact, but indicate
+# your project name (after the slash) and add your own revision information.
+#
+# Please do not change the "private" password hashing method implemented in
+# here, thereby making your hashes incompatible.  However, if you must, please
+# change the hash type identifier (the "$P$") to something different.
+#
+# Obviously, since this code is in the public domain, the above are not
+# requirements (there can be none), but merely suggestions.
+#
+class PasswordHash {
+	var $itoa64;
+	var $iteration_count_log2;
+	var $portable_hashes;
+	var $random_state;
+
+	function PasswordHash($iteration_count_log2, $portable_hashes)
+	{
+		$this->itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
+
+		if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31)
+			$iteration_count_log2 = 8;
+		$this->iteration_count_log2 = $iteration_count_log2;
+
+		$this->portable_hashes = $portable_hashes;
+
+		$this->random_state = microtime();
+		if (function_exists('getmypid'))
+			$this->random_state .= getmypid();
+	}
+
+	function get_random_bytes($count)
+	{
+		$output = '';
+		if (is_readable('/dev/urandom') &&
+		    ($fh = @fopen('/dev/urandom', 'rb'))) {
+			$output = fread($fh, $count);
+			fclose($fh);
+		}
+
+		if (strlen($output) < $count) {
+			$output = '';
+			for ($i = 0; $i < $count; $i += 16) {
+				$this->random_state =
+				    md5(microtime() . $this->random_state);
+				$output .=
+				    pack('H*', md5($this->random_state));
+			}
+			$output = substr($output, 0, $count);
+		}
+
+		return $output;
+	}
+
+	function encode64($input, $count)
+	{
+		$output = '';
+		$i = 0;
+		do {
+			$value = ord($input[$i++]);
+			$output .= $this->itoa64[$value & 0x3f];
+			if ($i < $count)
+				$value |= ord($input[$i]) << 8;
+			$output .= $this->itoa64[($value >> 6) & 0x3f];
+			if ($i++ >= $count)
+				break;
+			if ($i < $count)
+				$value |= ord($input[$i]) << 16;
+			$output .= $this->itoa64[($value >> 12) & 0x3f];
+			if ($i++ >= $count)
+				break;
+			$output .= $this->itoa64[($value >> 18) & 0x3f];
+		} while ($i < $count);
+
+		return $output;
+	}
+
+	function gensalt_private($input)
+	{
+		$output = '$P$';
+		$output .= $this->itoa64[min($this->iteration_count_log2 +
+			((PHP_VERSION >= '5') ? 5 : 3), 30)];
+		$output .= $this->encode64($input, 6);
+
+		return $output;
+	}
+
+	function crypt_private($password, $setting)
+	{
+		$output = '*0';
+		if (substr($setting, 0, 2) == $output)
+			$output = '*1';
+
+		$id = substr($setting, 0, 3);
+		# We use "$P$", phpBB3 uses "$H$" for the same thing
+		if ($id != '$P$' && $id != '$H$')
+			return $output;
+
+		$count_log2 = strpos($this->itoa64, $setting[3]);
+		if ($count_log2 < 7 || $count_log2 > 30)
+			return $output;
+
+		$count = 1 << $count_log2;
+
+		$salt = substr($setting, 4, 8);
+		if (strlen($salt) != 8)
+			return $output;
+
+		# We're kind of forced to use MD5 here since it's the only
+		# cryptographic primitive available in all versions of PHP
+		# currently in use.  To implement our own low-level crypto
+		# in PHP would result in much worse performance and
+		# consequently in lower iteration counts and hashes that are
+		# quicker to crack (by non-PHP code).
+		if (PHP_VERSION >= '5') {
+			$hash = md5($salt . $password, TRUE);
+			do {
+				$hash = md5($hash . $password, TRUE);
+			} while (--$count);
+		} else {
+			$hash = pack('H*', md5($salt . $password));
+			do {
+				$hash = pack('H*', md5($hash . $password));
+			} while (--$count);
+		}
+
+		$output = substr($setting, 0, 12);
+		$output .= $this->encode64($hash, 16);
+
+		return $output;
+	}
+
+	function gensalt_extended($input)
+	{
+		$count_log2 = min($this->iteration_count_log2 + 8, 24);
+		# This should be odd to not reveal weak DES keys, and the
+		# maximum valid value is (2**24 - 1) which is odd anyway.
+		$count = (1 << $count_log2) - 1;
+
+		$output = '_';
+		$output .= $this->itoa64[$count & 0x3f];
+		$output .= $this->itoa64[($count >> 6) & 0x3f];
+		$output .= $this->itoa64[($count >> 12) & 0x3f];
+		$output .= $this->itoa64[($count >> 18) & 0x3f];
+
+		$output .= $this->encode64($input, 3);
+
+		return $output;
+	}
+
+	function gensalt_blowfish($input)
+	{
+		# This one needs to use a different order of characters and a
+		# different encoding scheme from the one in encode64() above.
+		# We care because the last character in our encoded string will
+		# only represent 2 bits.  While two known implementations of
+		# bcrypt will happily accept and correct a salt string which
+		# has the 4 unused bits set to non-zero, we do not want to take
+		# chances and we also do not want to waste an additional byte
+		# of entropy.
+		$itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+
+		$output = '$2a$';
+		$output .= chr(ord('0') + $this->iteration_count_log2 / 10);
+		$output .= chr(ord('0') + $this->iteration_count_log2 % 10);
+		$output .= '$';
+
+		$i = 0;
+		do {
+			$c1 = ord($input[$i++]);
+			$output .= $itoa64[$c1 >> 2];
+			$c1 = ($c1 & 0x03) << 4;
+			if ($i >= 16) {
+				$output .= $itoa64[$c1];
+				break;
+			}
+
+			$c2 = ord($input[$i++]);
+			$c1 |= $c2 >> 4;
+			$output .= $itoa64[$c1];
+			$c1 = ($c2 & 0x0f) << 2;
+
+			$c2 = ord($input[$i++]);
+			$c1 |= $c2 >> 6;
+			$output .= $itoa64[$c1];
+			$output .= $itoa64[$c2 & 0x3f];
+		} while (1);
+
+		return $output;
+	}
+
+	function HashPassword($password)
+	{
+		$random = '';
+
+		if (CRYPT_BLOWFISH == 1 && !$this->portable_hashes) {
+			$random = $this->get_random_bytes(16);
+			$hash =
+			    crypt($password, $this->gensalt_blowfish($random));
+			if (strlen($hash) == 60)
+				return $hash;
+		}
+
+		if (CRYPT_EXT_DES == 1 && !$this->portable_hashes) {
+			if (strlen($random) < 3)
+				$random = $this->get_random_bytes(3);
+			$hash =
+			    crypt($password, $this->gensalt_extended($random));
+			if (strlen($hash) == 20)
+				return $hash;
+		}
+
+		if (strlen($random) < 6)
+			$random = $this->get_random_bytes(6);
+		$hash =
+		    $this->crypt_private($password,
+		    $this->gensalt_private($random));
+		if (strlen($hash) == 34)
+			return $hash;
+
+		# Returning '*' on error is safe here, but would _not_ be safe
+		# in a crypt(3)-like function used _both_ for generating new
+		# hashes and for validating passwords against existing hashes.
+		return '*';
+	}
+
+	function CheckPassword($password, $stored_hash)
+	{
+		$hash = $this->crypt_private($password, $stored_hash);
+		if ($hash[0] == '*')
+			$hash = crypt($password, $stored_hash);
+
+		return $hash == $stored_hash;
+	}
+}
+
+?>
+© Thomas Huijzer 2013
+www.thuijzer.nl
+
+This software is completely free, I'm against copyright ;)
+Use at your own risk.

File linkdump.sql

+CREATE DATABASE  IF NOT EXISTS `linkdump` /*!40100 DEFAULT CHARACTER SET utf8 */;
+USE `linkdump`;
+-- MySQL dump 10.13  Distrib 5.5.29, for debian-linux-gnu (x86_64)
+--
+-- Host: localhost    Database: linkdump
+-- ------------------------------------------------------
+-- Server version	5.5.29-0ubuntu0.12.10.1
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8 */;
+/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
+/*!40103 SET TIME_ZONE='+00:00' */;
+/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
+
+--
+-- Table structure for table `comment_votes`
+--
+
+DROP TABLE IF EXISTS `comment_votes`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `comment_votes` (
+  `comment_id` int(10) unsigned NOT NULL,
+  `user` varchar(128) NOT NULL,
+  `vote` tinyint(1) DEFAULT NULL,
+  PRIMARY KEY (`comment_id`,`user`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `link_votes`
+--
+
+DROP TABLE IF EXISTS `link_votes`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `link_votes` (
+  `link_id` int(10) unsigned NOT NULL,
+  `user` varchar(128) NOT NULL,
+  `vote` tinyint(1) DEFAULT NULL,
+  PRIMARY KEY (`link_id`,`user`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `comments`
+--
+
+DROP TABLE IF EXISTS `comments`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `comments` (
+  `comment_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+  `submitted_by` varchar(128) DEFAULT NULL,
+  `link_id` int(10) unsigned DEFAULT NULL,
+  `entry_time` int(10) unsigned DEFAULT NULL,
+  `comment` text,
+  `parent_comment_id` int(10) unsigned DEFAULT NULL,
+  `karma` int(10) DEFAULT '0',
+  PRIMARY KEY (`comment_id`),
+  KEY `user` (`submitted_by`),
+  KEY `link` (`link_id`),
+  KEY `parent` (`parent_comment_id`),
+  KEY `fk_comments_user` (`submitted_by`),
+  KEY `fk_comments_link` (`link_id`),
+  KEY `fk_comments_parent` (`parent_comment_id`),
+  CONSTRAINT `fk_comments_link` FOREIGN KEY (`link_id`) REFERENCES `links` (`link_id`) ON DELETE CASCADE ON UPDATE NO ACTION,
+  CONSTRAINT `fk_comments_parent` FOREIGN KEY (`parent_comment_id`) REFERENCES `comments` (`comment_id`) ON DELETE CASCADE ON UPDATE NO ACTION,
+  CONSTRAINT `fk_comments_user` FOREIGN KEY (`submitted_by`) REFERENCES `users` (`user`) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `links`
+--
+
+DROP TABLE IF EXISTS `links`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `links` (
+  `link_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+  `url` tinytext,
+  `title` varchar(128) DEFAULT NULL,
+  `entry_time` int(10) unsigned DEFAULT '0',
+  `last_upvote_time` int(10) unsigned DEFAULT '0',
+  `last_comment_time` int(10) unsigned DEFAULT '0',
+  `karma` int(10) DEFAULT '0',
+  `submitted_by` varchar(128) DEFAULT NULL,
+  `comments` int(10) unsigned DEFAULT '0',
+  `note` text,
+  PRIMARY KEY (`link_id`),
+  KEY `submitted_by` (`submitted_by`),
+  KEY `fk_links_users` (`submitted_by`),
+  CONSTRAINT `fk_links_users` FOREIGN KEY (`submitted_by`) REFERENCES `users` (`user`) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `users`
+--
+
+DROP TABLE IF EXISTS `users`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `users` (
+  `user` varchar(128) NOT NULL,
+  `password` varchar(128) DEFAULT NULL,
+  `member_since` datetime DEFAULT NULL,
+  `karma` int(10) unsigned DEFAULT '1',
+  `disabled` tinyint(1) unsigned DEFAULT '0',
+  `deleted` tinyint(1) unsigned DEFAULT '0',
+  PRIMARY KEY (`user`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
+
+/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
+/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
+/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
+/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
+
+-- Dump completed on 2013-02-09 22:09:11

File nbproject/private/private.properties

+index.file=index.php
+url=http://localhost/linkdump/

File nbproject/private/private.xml

+<?xml version="1.0" encoding="UTF-8"?><project-private xmlns="http://www.netbeans.org/ns/project-private/1">
+    <editor-bookmarks xmlns="http://www.netbeans.org/ns/editor-bookmarks/1"/>
+</project-private>

File nbproject/project.properties

+include.path=${php.global.include.path}
+php.version=PHP_53
+source.encoding=UTF-8
+src.dir=.
+tags.asp=false
+tags.short=true
+web.root=.

File nbproject/project.xml

+<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://www.netbeans.org/ns/project/1">
+    <type>org.netbeans.modules.php.project</type>
+    <configuration>
+        <data xmlns="http://www.netbeans.org/ns/php-project/1">
+            <name>linkdump</name>
+        </data>
+    </configuration>
+</project>

File page.comments.php

+<?php
+include('incl.initialize.php');
+
+checkVotePOST();
+
+$page = isset($_GET['page']) ? (int)$_GET['page'] : 0;
+if($page < 0) $page = 0;
+
+$result = $D->query('SELECT SQL_CALC_FOUND_ROWS
+                            comments.*,
+                            links.title AS link_title
+                        FROM comments
+                        JOIN links
+                            ON links.link_id = comments.link_id
+                        GROUP BY comments.comment_id
+                        ORDER BY entry_time DESC
+                        LIMIT ' . ($page * MAX_ENTRIES_PER_PAGE) . ', ' . MAX_ENTRIES_PER_PAGE);
+$comments = array();
+while ($row = $result->fetch_assoc()) {
+    $comments[] = $row;
+    $commentIds[] = $row['comment_id'];
+}
+
+if(!count($comments)) exitWithHTTPstatus(404);
+
+$userVotes = array();
+if (isset($_SESSION['user'])) {
+    $result = $D->query('SELECT comment_id
+                            FROM comment_votes
+                            WHERE `user` = "' . $D->real_escape_string($_SESSION['user']['user']) . '"
+                                AND comment_id IN(' . join(',', $commentIds) . ')');
+    while ($result && $row = $result->fetch_assoc()) {
+        $userVotes[] = $row['comment_id'];
+    }
+}
+
+$row = $D->query('SELECT FOUND_ROWS() AS totalComments')->fetch_assoc();
+$totalComments = $row['totalComments'];
+
+$page_title = $L['title_comments'];
+include('view.top.php');
+?>
+<form method="POST" action="">
+    <h1><?php echo $L['title_comments']; ?></h1>
+    <div id="linkList">
+        <table>
+            <colgroup>
+                <col style="width:2%">
+                <col style="width:2%">
+                <col style="width:96%">
+            </colgroup>
+<?php
+$i = $page * MAX_ENTRIES_PER_PAGE + 1;
+foreach ($comments as $comment) {
+?>
+            <tr>
+                <td class="nbr" rowspan="2"><?php echo $i; ?></td>
+                <td class="vote" rowspan="2"><?php echo returnVoteButtons('comment', $comment['comment_id'], $userVotes); ?></td>
+                <td class="link"<?php echo returnKarmaStyle($comment['karma']); ?>><?php echo returnFormattedText($comment['comment']); ?></td>
+            </tr>
+            <tr>
+                <td class="meta"><?php echo returnCommentMeta($comment, true); ?></td>
+            </tr>
+<?php
+    $i ++;
+}
+if($page < floor($totalComments / MAX_ENTRIES_PER_PAGE)) {
+?>
+           <tr>
+                <td colspan="2"></td>
+                <td><a href="<?php echo BASE . $L['url_comments'] . '/' . ($page + 1); ?>"><?php echo $L['button_more']; ?></a></td>
+            </tr>
+<?php
+}
+?>
+        </table>
+    </div>
+    <input type="hidden" name="token" value="<?php echo $_SESSION['token']; ?>">
+</form>
+<?php
+include('view.bottom.php');

File page.index.php

+<?php
+include('incl.initialize.php');
+
+checkVotePOST();
+
+if(isset($_GET['new'])) {
+    $order = 'entry_time DESC';
+} else {
+    $order = '(entry_time * 1.0 + last_upvote_time * 0.9 + last_comment_time * 0.5) DESC';
+}
+
+$page = isset($_GET['page']) ? (int)$_GET['page'] : 0;
+if($page < 0) $page = 0;
+
+$result = $D->query('SELECT SQL_CALC_FOUND_ROWS
+                        *
+                    FROM links
+                    ORDER BY ' . $order . '
+                    LIMIT ' . ($page * MAX_ENTRIES_PER_PAGE) . ', ' . MAX_ENTRIES_PER_PAGE);
+$links = array();
+$linkIds = array();
+while ($row = $result->fetch_assoc()) {
+    $links[] = $row;
+    $linkIds[] = $row['link_id'];
+}
+
+if(!count($links)) exitWithHTTPstatus(404);
+
+$userVotes = array();
+if (isset($_SESSION['user'])) {
+    $result = $D->query('SELECT link_id
+                            FROM link_votes
+                            WHERE `user` = "' . $D->real_escape_string($_SESSION['user']['user']) . '"
+                                AND link_id IN(' . join(',', $linkIds) . ')');
+    while ($result && $row = $result->fetch_assoc()) {
+        $userVotes[] = $row['link_id'];
+    }
+}
+
+$row = $D->query('SELECT FOUND_ROWS() AS totalLinks')->fetch_assoc();
+$totalLinks = $row['totalLinks'];
+
+$page_title = $L['title_site_name'];
+include('view.top.php');
+?>
+<form method="POST" action="">
+    <h1><?php echo isset($_GET['new']) ? $L['title_new'] : $L['title_site_name']; ?></h1>
+    <div id="linkList">
+        <table>
+            <colgroup>
+                <col style="width:2%">
+                <col style="width:2%">
+                <col style="width:96%">
+            </colgroup>
+<?php
+$i = $page * MAX_ENTRIES_PER_PAGE + 1;
+foreach ($links as $link) {
+?>
+            <tr>
+                <td class="nbr" rowspan="2"><?php echo $i; ?></td>
+                <td class="vote" rowspan="2"><?php echo returnVoteButtons('link', $link['link_id'], $userVotes); ?></td>
+                <td class="link"><a href="<?php echo $link['url']; ?>"><?php echo $link['title']; ?> <small>→&nbsp;<?php echo returnBaseUrl($link['url']); ?></small></a></td>
+            </tr>
+            <tr>
+                <td class="meta"><?php echo returnLinkMeta($link); ?></td>
+            </tr>
+<?php
+    $i ++;
+}
+if($page < floor($totalLinks / MAX_ENTRIES_PER_PAGE)) {
+?>
+           <tr>
+                <td colspan="2"></td>
+                <td><a href="<?php echo BASE . ($page + 1); ?>"><?php echo $L['button_more']; ?></a></td>
+            </tr>
+<?php
+}
+?>
+        </table>
+    </div>
+    <input type="hidden" name="token" value="<?php echo $_SESSION['token']; ?>">
+</form>
+<?php
+include('view.bottom.php');

File page.links.php

+<?php
+include('incl.initialize.php');
+
+if(!isset($_GET['link'])) exitWithHTTPstatus(404);
+$link = $D->query('SELECT * FROM links WHERE link_id = ' . (int)$_GET['link'])->fetch_assoc();
+if(!$link) exitWithHTTPstatus(404);
+
+checkVotePOST();
+
+if(isset($_POST['comment']) && isset($_SESSION['user']))
+{
+    $comment = trim(strip_tags($_POST['comment']));
+    if($comment) {
+        $statement = $D->prepare('INSERT INTO comments (
+                                            link_id,
+                                            submitted_by,
+                                            comment,
+                                            entry_time
+                                        ) VALUES (
+                                            ?, ?, ?, ?
+                                        )');
+        $link_id = $link['link_id'];
+        $submitted_by = $_SESSION['user']['user'];
+        $entry_time = time();
+
+        $statement->bind_param('issi',
+                                $link_id,
+                                $submitted_by,
+                                $comment,
+                                $entry_time);
+        $statement->execute();
+        $comment_id = $statement->insert_id;
+
+        $statement = $D->prepare('UPDATE `links` SET
+                                        `comments` = `comments` + 1,
+                                        `last_comment_time` = ' . time() . '
+                                    WHERE `link_id` = ?');
+        $statement->bind_param('i', $link_id);
+        $statement->execute();
+
+        header('location: ' . BASE . $L['url_links'] . '/' . $link['link_id'] . '#' . $L['url_comment'] . '_' . $comment_id);
+        exit();
+    } else {
+        $error = $L['error_comment_required'];
+    }
+}
+
+$result = $D->query('SELECT
+                            *
+                        FROM comments
+                        WHERE link_id = ' . (int)$link['link_id'] . '
+                        ORDER BY entry_time');
+$comments = array();
+$commentIds = array();
+while ($row = $result->fetch_assoc()) {
+    $comments[] = $row;
+    $commentIds[] = $row['comment_id'];
+}
+
+$userVotes = array();
+if (isset($_SESSION['user'])) {
+    $result = $D->query('SELECT comment_id
+                            FROM comment_votes
+                            WHERE `user` = "' . $D->real_escape_string($_SESSION['user']['user']) . '"
+                                AND comment_id IN(' . join(',', $commentIds) . ')');
+    while ($result && $row = $result->fetch_assoc()) {
+        $userVotes[] = $row['comment_id'];
+    }
+}
+
+$userMayVote = isset($_SESSION['user']) ? true : false;
+if (isset($_SESSION['user']) && $D->query('SELECT link_id
+                                            FROM link_votes
+                                            WHERE `user` = "' . $D->real_escape_string($_SESSION['user']['user']) . '"
+                                                AND link_id = ' . (int)$link['link_id'])->fetch_assoc()) {
+    $userMayVote = false;
+}
+
+$page_title = $link['title'] . $L['special_meta_title_sep'] . $L['title_links'];
+include('view.top.php');
+?>
+<form method="POST" action="">
+    <h1>
+        <?php if($userMayVote) { ?><span style="float:left;margin: -0.5em 0.5em 0em 0em"><?php echo returnVoteButtons('link', $link['link_id'], array()); ?></span><?php } ?>
+        <a href="<?php echo $link['url']; ?>"><?php echo $link['title']; ?> <small>→ <?php echo returnBaseUrl($link['url']); ?></small></a>
+    </h1>
+    <div class="content" style="padding-bottom: 0em">
+        <div class="meta"><?php echo returnLinkMeta($link); ?></div>
+        <?php echo returnFormattedText($link['note']); ?>
+    </div>
+<?php
+if (count($comments)) {
+?>
+    <div id="linkList">
+        <table style="margin-bottom: 0em">
+            <colgroup>
+                <col style="width:2%">
+                <col style="width:2%">
+                <col style="width:96%">
+            </colgroup>
+<?php
+    $i = 1;
+    foreach($comments as $comment) {
+?>
+            <tr>
+                <td class="nbr" rowspan="2"><?php echo $i; ?></td>
+                <td class="vote" rowspan="2"><?php echo returnVoteButtons('comment', $comment['comment_id'], $userVotes); ?></td>
+                <td class="link"<?php echo returnKarmaStyle($comment['karma']); ?>><?php echo returnFormattedText($comment['comment']); ?></td>
+            </tr>
+            <tr>
+                <td class="meta"><?php echo returnCommentMeta($comment); ?></td>
+            </tr>
+<?php
+        $i ++;
+    }
+?>
+        </table>
+    </div>
+    <input type="hidden" name="token" value="<?php echo $_SESSION['token']; ?>">
+</form>
+<?php
+}
+
+if(isset($_SESSION['user'])) {
+    if(isset($error)) echo '<div id="error">' . $error . '</div>';
+?>
+<form method="post" action="">
+    <table>
+        <colgroup>
+            <col style="width:15%">
+            <col style="width:85%">
+        </colgroup>
+        <tr>
+            <td><?php echo $L['head_comment']; ?></td>
+            <td><textarea name="comment" style="width:100%;" cols="20" rows="10"><?php echo isset($_POST['comment']) ? $_POST['comment'] : ''; ?></textarea></td>
+        </tr>
+        <tr>
+            <td></td>
+            <td><input type="submit" name="submit" value="<?php echo $L['button_place_comment']; ?>"></td>
+        </tr>
+    </table>
+    <input type="hidden" name="token" value="<?php echo $_SESSION['token']; ?>">
+</form>
+<?php
+} else {
+    echo '<div class="content">' . $L['label_login_to_comment'] . '</div>';
+}
+include('view.bottom.php');

File page.login.php

+<?php
+include('incl.initialize.php');
+
+if(isset($_GET['logout'])) {
+    unset($_SESSION);
+    session_destroy();
+    header('location: ' . BASE);
+    exit();
+}
+
+if(isset($_SESSION['user'])) {
+    header('location: ' . BASE);
+    exit();
+}
+
+if(isset($_POST['login']) && isset($_POST['username']) && isset($_POST['password'])) {
+    if(!$_POST['username']) {
+        $loginError = $L['error_username_required'];
+    } else if(!$_POST['password']) {
+        $loginError = $L['error_password_required'];
+    } else {
+        $user = $D->query('SELECT * FROM `users` WHERE `user` = "' . $D->real_escape_string($_POST['username']) . '"')->fetch_assoc();
+        if($user) {
+            include('lib/PasswordHash.php');
+            $ph = new PasswordHash(PASSWORD_ITERARION, false);
+            if($ph->CheckPassword($_POST['password'], $user['password'])) {
+                $user['password'] = '';
+                $_SESSION['user'] = $user;
+                header('location: ' . BASE);
+                exit();
+            } else {
+                $loginError = $L['error_wrong_login'];
+            }
+        } else {
+            $loginError = $L['error_wrong_login'];
+        }
+    }
+}
+
+if(isset($_POST['register']) && isset($_POST['r_username']) && isset($_POST['r_password'])) {
+    if(!$_POST['r_username'] || strip_tags($_POST['r_username']) != $_POST['r_username']) {
+        $registerError = $L['error_username_required'];
+    } else if(!$_POST['r_password']) {
+        $registerError = $L['error_password_required'];
+    } else {
+        $statement = $D->prepare('SELECT `user` FROM `users` WHERE `user` = ?');
+        $statement->bind_param("s", $_POST['r_username']);
+        $statement->execute();
+        $statement->bind_result($user);
+        $statement->fetch();
+        if($user) {
+            $registerError = $L['error_username_taken'];
+        } else {
+            include('lib/PasswordHash.php');
+            $ph = new PasswordHash(PASSWORD_ITERARION, false);
+            $statement = $D->prepare('INSERT INTO `users` (
+                                            `user`,
+                                            `password`,
+                                            `member_since`
+                                        ) VALUES (
+                                            ?, ?, NOW()
+                                        )');
+            $statement->bind_param("ss", $_POST['r_username'], $ph->HashPassword($_POST['r_password']));
+            if($statement->execute()) {
+                $_SESSION['user'] = array('user' => $_POST['r_username'], 'karma' => 1);
+                header('location: ' . BASE);
+                exit();
+            }
+        }
+    }
+}
+
+$page_title = $L['title_login_or_register'];
+include('view.top.php');
+?>
+<h1><?php echo $L['title_login_or_register']; ?></h1>
+<form method="post" action="">
+<?php
+if(isset($loginError)) echo '<div id="error">' . $loginError . '</div>';
+?>
+    <table>
+        <colgroup>
+            <col style="width:15%">
+            <col style="width:85%">
+        </colgroup>
+        <tr>
+            <td colspan="2"><h2><?php echo $L['title_login']; ?></h2></td>
+        </tr>
+        <tr>
+            <td><?php echo $L['head_username']; ?></td>
+            <td><input type="text" name="username" style="width:100%;" value="<?php echo isset($_POST['username']) ? $_POST['username'] : ''; ?>"></td>
+        </tr>
+        <tr>
+            <td><?php echo $L['head_password']; ?></td>
+            <td><input type="password" name="password" style="width:100%;"></td>
+        </tr>
+        <tr>
+            <td></td>
+            <td><input type="submit" name="login" value="<?php echo $L['button_login']; ?>"></td>
+        </tr>
+    </table>
+    <input type="hidden" name="token" value="<?php echo $_SESSION['token']; ?>">
+</form>
+<form method="post" action="">
+<?php
+if(isset($registerError)) echo '<div id="error">' . $registerError . '</div>';
+?>
+    <table>
+        <colgroup>
+            <col style="width:15%">
+            <col style="width:85%">
+        </colgroup>
+        <tr>
+            <td colspan="2"><h2><?php echo $L['title_register']; ?></h2></td>
+        </tr>
+        <tr>
+            <td><?php echo $L['head_username']; ?></td>
+            <td><input type="text" name="r_username" style="width:100%;" value="<?php echo isset($_POST['r_username']) ? $_POST['r_username'] : ''; ?>"></td>
+        </tr>
+        <tr>
+            <td><?php echo $L['head_password']; ?></td>
+            <td><input type="password" name="r_password" style="width:100%;"></td>
+        </tr>
+        <tr>
+            <td></td>
+            <td><input type="submit" name="register" value="<?php echo $L['button_register']; ?>"></td>
+        </tr>
+    </table>
+    <input type="hidden" name="token" value="<?php echo $_SESSION['token']; ?>">
+</form>
+<?php
+include('view.bottom.php');

File page.submit.php

+<?php
+include('incl.initialize.php');
+
+if(!isset($_SESSION['user'])) exitWithHTTPstatus(401);
+
+if(isset($_POST['title']) && isset($_POST['url']) && isset($_POST['note'])) {
+    if(!trim($_POST['title'])) {
+        $error = $L['error_title_required'];
+    } else if(!preg_match('/^https?:\/\//', $_POST['url'])) {
+        $error = $L['error_invalid_url'];
+    } else {
+        $statement = $D->prepare('SELECT link_id FROM links WHERE url = ? AND entry_time > ' . (time() - 86400 * 365));
+        $statement->bind_param("s", $_POST['url']);
+        $statement->execute();
+        $statement->bind_result($link_id);
+        $statement->fetch();
+        if(!$link_id) {
+            $statement = $D->prepare('INSERT INTO links (
+                                            url,
+                                            title,
+                                            entry_time,
+                                            submitted_by,
+                                            note
+                                        ) VALUES (
+                                            ?, ?, ?, ?, ?
+                                        )');
+            $url = $_POST['url'];
+            $title = $_POST['title'];
+            $entry_time = time();
+            $submitted_by = $_SESSION['user']['user'];
+            $note = strip_tags($_POST['note']);
+            $statement->bind_param("ssiss",
+                                    $url,
+                                    $title,
+                                    $entry_time,
+                                    $submitted_by,
+                                    $note);
+            $statement->execute();
+            $link_id = $statement->insert_id;
+        }
+        header('location: ' . BASE . $L['url_links'] . '/' . $link_id);
+        exit();
+    }
+}
+
+$page_title = $L['title_submit'];
+include('view.top.php');
+?>
+<h1><?php echo $L['title_submit']; ?></h1>
+<form method="post" action="">
+<?php
+if(isset($error)) echo '<div id="error">' . $error . '</div>';
+?>
+    <table>
+        <colgroup>
+            <col style="width:15%">
+            <col style="width:85%">
+        </colgroup>
+        <tr>
+            <td><?php echo $L['head_title']; ?></td>
+            <td><input type="text" name="title" style="width:100%;" value="<?php echo isset($_POST['title']) ? $_POST['title'] : ''; ?>"></td>
+        </tr>
+        <tr>
+            <td><?php echo $L['head_url']; ?></td>
+            <td><input type="text" name="url" style="width:100%;" value="<?php echo isset($_POST['url']) ? $_POST['url'] : ''; ?>"></td>
+        </tr>
+        <tr>
+            <td><?php echo $L['head_note']; ?></td>
+            <td><textarea name="note" style="width:100%;" cols="20" rows="10"><?php echo isset($_POST['note']) ? $_POST['note'] : ''; ?></textarea></td>
+        </tr>
+        <tr>
+            <td></td>
+            <td><input type="submit" name="submit" value="<?php echo $L['button_submit']; ?>"></td>
+        </tr>
+    </table>
+    <input type="hidden" name="token" value="<?php echo $_SESSION['token']; ?>">
+</form>
+<?php
+include('view.bottom.php');

File page.user.comments.php

+<?php
+include('incl.initialize.php');
+
+if(!isset($_GET['user'])) exitWithHTTPstatus(404);
+$user = $D->query('SELECT * FROM `users` WHERE `user` = "' . $D->real_escape_string($_GET['user']) . '"')->fetch_assoc();
+if(!$user) exitWithHTTPstatus(404);
+
+checkVotePOST();
+
+$page = isset($_GET['page']) ? (int)$_GET['page'] : 0;
+if($page < 0) $page = 0;
+
+$result = $D->query('SELECT SQL_CALC_FOUND_ROWS
+                            comments.*,
+                            links.title AS link_title
+                        FROM comments
+                        JOIN links
+                            ON links.link_id = comments.link_id
+                        WHERE comments.submitted_by = "' . $D->real_escape_string($user['user']) . '"
+                        GROUP BY comments.comment_id
+                        ORDER BY entry_time DESC
+                        LIMIT ' . ($page * MAX_ENTRIES_PER_PAGE) . ', ' . MAX_ENTRIES_PER_PAGE);
+
+$comments = array();
+$commentIds = array();
+while ($row = $result->fetch_assoc()) {
+    $comments[] = $row;
+    $commentIds[] = $row['comment_id'];
+}
+
+if(!count($comments)) exitWithHTTPstatus(404);
+
+$userVotes = array();
+if (isset($_SESSION['user'])) {
+    $result = $D->query('SELECT comment_id
+                            FROM comment_votes
+                            WHERE `user` = "' . $D->real_escape_string($_SESSION['user']['user']) . '"
+                                AND comment_id IN(' . join(',', $commentIds) . ')');
+    while ($result && $row = $result->fetch_assoc()) {
+        $userVotes[] = $row['comment_id'];
+    }
+}
+
+$row = $D->query('SELECT FOUND_ROWS() AS totalComments')->fetch_assoc();
+$totalComments = $row['totalComments'];
+
+$page_title = $L['title_comments'] . $L['special_meta_title_sep'] . $user['user'] . $L['special_meta_title_sep'] . $L['title_users'];
+include('view.top.php');
+?>
+<form method="POST" action="">
+    <h1><?php echo str_replace('[x]', '<a href="' . BASE . $L['url_users'] . '/' . rawurlencode($user['user']) . '">' . $user['user'] . '</a>', $L['title_comments_by_x']); ?></h1>
+    <div id="linkList">
+        <table>
+            <colgroup>
+                <col style="width:2%">
+                <col style="width:2%">
+                <col style="width:96%">
+            </colgroup>
+<?php
+$i = $page * MAX_ENTRIES_PER_PAGE + 1;
+foreach ($comments as $comment) {
+?>
+            <tr>
+                <td class="nbr" rowspan="2"><?php echo $i; ?></td>
+                <td class="vote" rowspan="2"><?php echo returnVoteButtons('comment', $comment['comment_id'], $userVotes); ?></td>
+                <td class="link"<?php echo returnKarmaStyle($comment['karma']); ?>><?php echo returnFormattedText($comment['comment']); ?></td>
+            </tr>
+            <tr>
+                <td class="meta"><?php echo returnCommentMeta($comment, true); ?></td>
+            </tr>
+<?php
+    $i ++;
+}
+if($page < floor($totalComments / MAX_ENTRIES_PER_PAGE)) {
+?>
+            <tr>
+                <td colspan="2"></td>
+                <td><a href="<?php echo BASE . $L['url_users'] . '/' . rawurlencode($user['user']) . '/' . $L['url_comments'] . '/' . ($page + 1); ?>"><?php echo $L['button_more']; ?></a></td>
+            </tr>
+<?php
+}
+?>
+        </table>
+    </div>
+    <input type="hidden" name="token" value="<?php echo $_SESSION['token']; ?>">
+</form>
+<?php
+include('view.bottom.php');

File page.user.links.php

+<?php
+include('incl.initialize.php');
+
+if(!isset($_GET['user'])) exitWithHTTPstatus(404);
+$user = $D->query('SELECT * FROM `users` WHERE `user` = "' . $D->real_escape_string($_GET['user']) . '"')->fetch_assoc();
+if(!$user) exitWithHTTPstatus(404);
+
+checkVotePOST();
+
+$page = isset($_GET['page']) ? (int)$_GET['page'] : 0;
+if($page < 0) $page = 0;
+
+$result = $D->query('SELECT SQL_CALC_FOUND_ROWS
+                        *
+                    FROM links
+                    WHERE submitted_by = "' . $D->real_escape_string($user['user']) . '"
+                    ORDER BY entry_time DESC
+                    LIMIT ' . ($page * MAX_ENTRIES_PER_PAGE) . ', ' . MAX_ENTRIES_PER_PAGE);
+
+$links = array();
+$linkIds = array();
+while ($row = $result->fetch_assoc()) {
+    $links[] = $row;
+    $linkIds[] = $row['link_id'];
+}
+
+if(!count($links)) exitWithHTTPstatus(404);
+
+$userVotes = array();
+if (isset($_SESSION['user'])) {
+    $result = $D->query('SELECT link_id
+                            FROM link_votes
+                            WHERE `user` = "' . $D->real_escape_string($_SESSION['user']['user']) . '"
+                                AND link_id IN(' . join(',', $linkIds) . ')');
+    while ($result && $row = $result->fetch_assoc()) {
+        $userVotes[] = $row['link_id'];
+    }
+}
+
+$row = $D->query('SELECT FOUND_ROWS() AS totalLinks')->fetch_assoc();
+$totalLinks = $row['totalLinks'];
+
+$page_title = $L['title_links'] . $L['special_meta_title_sep'] . $user['user'] . $L['special_meta_title_sep'] . $L['title_users'];
+include('view.top.php');
+?>
+<form method="POST" action="">
+    <h1><?php echo str_replace('[x]', '<a href="' . BASE . $L['url_users'] . '/' . rawurlencode($user['user']) . '">' . $user['user'] . '</a>', $L['title_links_by_x']); ?></h1>
+    <div id="linkList">
+        <table>
+            <colgroup>
+                <col style="width:2%">
+                <col style="width:2%">
+                <col style="width:96%">
+            </colgroup>
+<?php
+$i = $page * MAX_ENTRIES_PER_PAGE + 1;
+foreach ($links as $link) {
+?>
+            <tr>
+                <td class="nbr" rowspan="2"><?php echo $i; ?></td>
+                <td class="vote" rowspan="2"><?php echo returnVoteButtons('link', $link['link_id'], $userVotes); ?></td>
+                <td class="link"><a href="<?php echo $link['url']; ?>"><?php echo $link['title']; ?> <small>→&nbsp;<?php echo returnBaseUrl($link['url']); ?></small></a></td>
+            </tr>
+            <tr>
+                <td class="meta"><?php echo returnLinkMeta($link); ?></td>
+            </tr>
+<?php
+    $i ++;
+}
+if($page < floor($totalLinks / MAX_ENTRIES_PER_PAGE)) {
+?>
+            <tr>
+                <td colspan="2"></td>
+                <td><a href="<?php echo BASE . $L['url_users'] . '/' . rawurlencode($user['user']) . '/' . $L['url_links'] . '/' . ($page + 1); ?>"><?php echo $L['button_more']; ?></a></td>
+            </tr>
+<?php
+}
+?>
+        </table>
+    </div>
+    <input type="hidden" name="token" value="<?php echo $_SESSION['token']; ?>">
+</form>
+<?php
+include('view.bottom.php');

File page.users.php

+<?php
+include('incl.initialize.php');
+
+if(!isset($_GET['user'])) exitWithHTTPstatus(404);
+$user = $D->query('SELECT * FROM `users` WHERE `user` = "' . $D->real_escape_string($_GET['user']) . '"')->fetch_assoc();
+if(!$user) exitWithHTTPstatus(404);
+
+checkVotePOST();
+
+if(isset($_SESSION['user'])
+        && $_SESSION['user']['user'] == $user['user']
+        && isset($_POST['change'])
+        && isset($_POST['new_password'])
+        && $_POST['new_password']) {
+    include('lib/PasswordHash.php');
+    $ph = new PasswordHash(PASSWORD_ITERARION, false);
+    $D->query('UPDATE `users` SET
+                `password` = "' . $ph->HashPassword($_POST['new_password']) . '"
+                WHERE `user` = "' . $D->real_escape_string($user['user']) . '"');
+}
+
+$result = $D->query('SELECT SQL_CALC_FOUND_ROWS
+                            comments.*,
+                            links.title AS link_title
+                        FROM comments
+                        JOIN links
+                            ON links.link_id = comments.link_id
+                        WHERE comments.submitted_by = "' . $D->real_escape_string($user['user']) . '"
+                        GROUP BY comments.comment_id
+                        ORDER BY entry_time DESC
+                        LIMIT 3');
+$comments = array();
+$commentIds = array();
+while ($row = $result->fetch_assoc()) {
+    $comments[] = $row;
+    $commentIds[] = $row['comment_id'];
+}
+
+$userCommentVotes = array();
+if (isset($_SESSION['user'])) {
+    $result = $D->query('SELECT comment_id
+                            FROM comment_votes
+                            WHERE `user` = "' . $D->real_escape_string($_SESSION['user']['user']) . '"
+                                AND comment_id IN(' . join(',', $commentIds) . ')');
+    while ($result && $row = $result->fetch_assoc()) {
+        $userCommentVotes[] = $row['comment_id'];
+    }
+}
+
+$row = $D->query('SELECT FOUND_ROWS() AS totalComments')->fetch_assoc();
+$totalComments = $row['totalComments'];
+
+$result = $D->query('SELECT SQL_CALC_FOUND_ROWS
+                        *
+                    FROM links
+                    WHERE submitted_by = "' . $D->real_escape_string($user['user']) . '"
+                    ORDER BY entry_time DESC
+                    LIMIT 3');
+$links = array();
+$linkIds = array();
+while ($row = $result->fetch_assoc()) {
+    $links[] = $row;
+    $linkIds[] = $row['link_id'];
+}
+
+$userLinkVotes = array();
+if (isset($_SESSION['user'])) {
+    $result = $D->query('SELECT link_id
+                            FROM link_votes
+                            WHERE `user` = "' . $D->real_escape_string($_SESSION['user']['user']) . '"
+                                AND link_id IN(' . join(',', $linkIds) . ')');
+    while ($result && $row = $result->fetch_assoc()) {
+        $userLinkVotes[] = $row['link_id'];
+    }
+}
+
+$row = $D->query('SELECT FOUND_ROWS() AS totalLinks')->fetch_assoc();
+$totalLinks = $row['totalLinks'];
+
+$userMeta = str_replace('[x]', $user['karma'], $L['label_point' . ($user['karma'] == 1 ? '' : 's')]);
+$userMeta .= ', ' . str_replace('[x]', $totalComments, $L['label_comment' . ($totalComments == 1 ? '' : 's')]);
+$userMeta .= ', ' . str_replace('[x]', $totalLinks, $L['label_link' . ($totalLinks == 1 ? '' : 's')]);
+$userMeta .= ', ' . str_replace('[x]', $user['member_since'], $L['label_since_x']);
+
+$page_title = $user['user'] . $L['special_meta_title_sep'] . $L['title_users'];
+include('view.top.php');
+?>
+<form method="POST" action="">
+    <h1><?php echo $user['user']; ?></h1>
+    <div class="content" style="padding-bottom: 0em">
+        <div class="meta"><?php echo $user['user'] . ': ' . $userMeta; ?></div>
+    </div>
+
+    <div id="linkList">
+        <table>
+            <colgroup>
+                <col style="width:2%">
+                <col style="width:96%">
+            </colgroup>
+            <tr>
+                <td colspan="2"><h2><?php echo $L['title_comments']; ?></h2></td>
+            </tr>
+<?php
+foreach ($comments as $comment) {
+?>
+            <tr>
+                <td class="vote" rowspan="2"><?php echo returnVoteButtons('comment', $comment['comment_id'], $userCommentVotes); ?></td>
+                <td class="link"<?php echo returnKarmaStyle($comment['karma']); ?>><?php echo returnFormattedText($comment['comment']); ?></td>
+            </tr>
+            <tr>
+                <td class="meta"><?php echo returnCommentMeta($comment, true); ?></td>
+            </tr>
+<?php
+}
+?>
+           <tr>
+                <td></td>
+                <td><a href="<?php echo BASE . $L['url_users'] . '/' . $user['user'] . '/' . $L['url_comments']; ?>"><?php echo $L['button_more']; ?></a></td>
+            </tr>
+        </table>
+    </div>
+
+    <div id="linkList">
+        <table>
+            <colgroup>
+                <col style="width:2%">
+                <col style="width:96%">
+            </colgroup>
+            <tr>
+                <td colspan="2"><h2><?php echo $L['title_links']; ?></h2></td>
+            </tr>
+<?php
+foreach ($links as $link) {
+?>
+            <tr>
+                <td class="vote" rowspan="2"><?php echo returnVoteButtons('link', $link['link_id'], $userLinkVotes); ?></td>
+                <td class="link"><a href="<?php echo $link['url']; ?>"><?php echo $link['title']; ?> <small>→&nbsp;<?php echo returnBaseUrl($link['url']); ?></small></a></td>
+            </tr>
+            <tr>
+                <td class="meta"><?php echo returnLinkMeta($link); ?></td>
+            </tr>
+<?php
+}
+?>
+           <tr>
+                <td></td>
+                <td><a href="<?php echo BASE . $L['url_users'] . '/' . rawurlencode($user['user']) . '/' . $L['url_links']; ?>"><?php echo $L['button_more']; ?></a></td>
+            </tr>
+        </table>
+    </div>
+    <input type="hidden" name="token" value="<?php echo $_SESSION['token']; ?>">
+</form>
+<?php
+if(isset($_SESSION['user']) && $_SESSION['user']['user'] == $user['user']) {
+?>
+<form method="post" action="">
+    <table>
+        <colgroup>
+            <col style="width:30%">
+            <col style="width:70%">
+        </colgroup>
+        <tr>
+            <td colspan="2"><h2><?php echo $L['title_change_password']; ?></h2></td>
+        </tr>
+        <tr>
+            <td><?php echo $L['head_new_password']; ?></td>
+            <td><input type="password" name="new_password" style="width:100%;"></td>
+        </tr>
+        <tr>
+            <td></td>
+            <td><input type="submit" name="change" value="<?php echo $L['button_change']; ?>"></td>
+        </tr>
+    </table>
+    <input type="hidden" name="token" value="<?php echo $_SESSION['token']; ?>">
+</form>
+<?php
+}
+include('view.bottom.php');
+/* ELEMENTS __________________________________________________________________*/
+* {
+    box-sizing: border-box;
+    font-family: inherit;
+}
+HTML, BODY, H1, H2, H3 {
+    margin: 0px;
+    padding: 0px;
+}
+BODY {
+    font-family: Ubuntu, sans-serif;
+    color: #5a686f;
+}
+H1 {
+    padding: 0.5em;
+    font-size: 1em;
+    font-weight: normal;
+    background: #899499;
+    color: #ffffff;
+}
+H1 A
+{
+    color: #ffffff !important;
+}
+H1 A SMALL
+{
+    color:  #e7eaeb !important;
+}
+H1 .vote {
+    font-size: 0.8em;
+    line-height: 1em;
+    vertical-align: top;
+    display: inline-block;
+    margin-right: 0.25em;
+}
+H2
+{
+    font-size: 1em;
+    font-weight: normal;
+    color: #142933;
+    line-height: 1.6em;
+}
+A {
+    color: #142933;
+    text-decoration: none;
+}
+A SMALL {
+    color: #5a686f;
+}
+A:hover {
+    text-decoration: underline;
+}
+A:visited {
+    color: #5a686f;
+}
+A:visited SMALL {
+    color: #899499;
+}
+A.selected {
+    text-decoration: underline;
+}
+TABLE {
+    border-collapse: collapse;
+    margin: 2em;
+    width: 66%;
+}
+TD {
+    padding: 0.1em 1em 0.1em 0em;
+    vertical-align: top;
+}
+P
+{
+    margin: 0em;
+    padding: 0em 0em 0.5em 0em;
+    line-height: 1.6em;
+}
+
+/* IDS _______________________________________________________________________*/
+#container {
+    max-width: 1000px;
+    margin: 0px auto;
+    background: #e7eaeb;
+    border-bottom: 0.5em solid #142933;
+}
+#footer {
+    border-bottom: 0.5em solid #899499;
+}
+#top {
+    padding: 0.5em;
+    background: #142933;
+    color: #ffffff;
+}
+#top A {
+    color: #ffffff;
+    display: inline-block;
+    margin-right: 1em;
+}
+#homeLink {
+    font-weight: bold;
+    text-decoration: none;
+}
+#userLinks {
+    float: right;
+}
+#userLinks A {
+    margin-right: 0em;
+    margin-left: 1em;
+}
+#linkList .link SMALL {
+    font-weight: normal;
+}
+#linkList .meta {
+    font-size: 0.7em;
+    padding-bottom: 1em;
+}
+#linkList .nbr {
+    text-align: right;
+}
+#linkList TABLE {
+    width: 100%;
+}
+#error
+{
+    color: #990000;
+    padding: 0.5em 2em;
+}
+
+/* CLASSES ___________________________________________________________________*/
+.meta
+{
+    font-size: 0.7em;
+}
+.content
+{
+    padding: 2em;
+}
+.comment
+{
+    padding: 0em 2em 2em 2em;
+    width: 66%;
+}
+.link P:last-child, .comment P:last-child {
+    padding: 0em;
+}
+.voteButton {
+    display: block;
+    border: none;
+    width: 1.25em;
+    height: 1.25em;
+    background: none;
+    cursor: pointer;
+}

File view.bottom.php

+            <div id="footer"></div>
+        </div>
+    </body>
+</html>

File view.top.php

+<!doctype html>
+<html>
+    <head>
+        <meta charset="UTF-8">
+        <title><?php echo $page_title; ?></title>
+        <link rel="stylesheet" href="<?php echo BASE; ?>style.css">
+    </head>
+    <body>
+        <div id="container">
+            <div id="top">
+<?php
+$menu = array();
+$menu[] = array('url' => BASE, 'label' => $L['button_site_name']);
+$menu[] = array('url' => BASE . $L['url_new'] . '/', 'label' => $L['button_new']);
+$menu[] = array('url' => BASE . $L['url_comments'] . '/', 'label' => $L['button_comments']);
+if (isset($_SESSION['user'])) {
+    $menu[] = array('url' => BASE . $L['url_submit'] . '/', 'label' => $L['button_submit']);
+}
+foreach($menu as $item)
+{
+    $selected = $_SERVER['REQUEST_URI'] == $item['url'] ? ' class="selected"' : '';
+    $homeLink = $item['url'] == BASE ? ' id="homeLink"' : '';
+    echo '<a href="' . $item['url'] . '"' . $homeLink . $selected . '>' . $item['label'] . '</a>';
+}
+?>
+                <div id="userLinks">
+<?php
+$menu = array();
+if (isset($_SESSION['user'])) {
+    $menu[] = array('url' => BASE . $L['url_users'] . '/' . rawurlencode($_SESSION['user']['user']), 'label' => $_SESSION['user']['user'] . ' <small>' . str_replace('[x]', $_SESSION['user']['karma'], $L['label_point' . ($_SESSION['user']['karma'] == 1 ? '' : 's')]) . '</small>');
+    $menu[] = array('url' => BASE . $L['url_logout'] . '/', 'label' => $L['button_logout']);
+} else {
+    $menu[] = array('url' => BASE . $L['url_login'] . '/', 'label' => $L['button_login']);
+}
+foreach ($menu as $item)
+{
+    $selected = $_SERVER['REQUEST_URI'] == $item['url'] ? ' class="selected"' : '';
+    echo '<a href="' . $item['url'] . '"' . $selected . '>' . $item['label'] . '</a>';
+}
+?>
+                </div>
+            </div>