Let's just say: major progress and still only 20% complete. So many changes I forgot to commit.
authorDan
Wed, 17 Oct 2007 20:23:51 -0400
changeset 1 6f8b7c6fac02
parent 0 0417a5a0c7be
child 2 253118325c65
Let's just say: major progress and still only 20% complete. So many changes I forgot to commit.
decir/bbcode.php
decir/common.php
decir/delete.php
decir/edit.php
decir/forum_index.php
decir/functions.php
decir/install.sql
decir/js/bbcedit.js
decir/posting.php
decir/viewforum.php
decir/viewtopic.php
plugins/Decir.php
--- a/decir/bbcode.php	Wed Jun 13 22:33:54 2007 -0400
+++ b/decir/bbcode.php	Wed Oct 17 20:23:51 2007 -0400
@@ -12,46 +12,39 @@
  * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
  */
  
-function str_replace_once($needle1, $needle2, $haystack)
-{
-  $len_h = strlen($haystack);
-  $len_1 = strlen($needle1);
-  $len_2 = strlen($needle2);
-  if ( $len_h < $len_1 )
-    return $haystack;
-  if ( $needle1 == $haystack )
-    return $needle1;
-  for ( $i = 0; $i < $len_h; $i++ )
-  {
-    if ( substr($haystack, $i, $len_1) == $needle1 )
-    {
-      $haystack = substr($haystack, 0, $i) .
-                  $needle2 .
-                  substr($haystack, $i + $len_1);
-      return $haystack;
-    }
-  }
-}
-
-function render_bbcode($text, $bbcode_uid)
+function render_bbcode($text, $bbcode_uid = false)
 {
   // First things first, strip out all [code] sections
   $text = decir_bbcode_strip_code($text, $bbcode_uid, $_code);
   
+  if ( $bbcode_uid )
+    $bbcode_uid = ':' . $bbcode_uid;
+  
   // Bold text
-  $text = preg_replace("/\[b:$bbcode_uid\](.*?)\[\/b:$bbcode_uid\]/is", '<b>\\1</b>', $text);
+  $text = preg_replace("/\[b$bbcode_uid\](.*?)\[\/b$bbcode_uid\]/is", '<b>\\1</b>', $text);
   
   // Italicized text
-  $text = preg_replace("/\[i:$bbcode_uid\](.*?)\[\/i:$bbcode_uid\]/is", '<i>\\1</i>', $text);
+  $text = preg_replace("/\[i$bbcode_uid\](.*?)\[\/i$bbcode_uid\]/is", '<i>\\1</i>', $text);
   
-  // Uunderlined text
-  $text = preg_replace("/\[u:$bbcode_uid\](.*?)\[\/u:$bbcode_uid\]/is", '<u>\\1</u>', $text);
+  // Underlined text
+  $text = preg_replace("/\[u$bbcode_uid\](.*?)\[\/u$bbcode_uid\]/is", '<u>\\1</u>', $text);                        
   
   // Colored text
-  $text = preg_replace("/\[color=\#([A-F0-9]*){3,6}:$bbcode_uid\](.*?)\[\/color:$bbcode_uid\]/is", '<span style="color: #\\1">\\2</span>', $text);
+  $text = preg_replace("/\[color$bbcode_uid=#([A-Fa-f0-9][A-Fa-f0-9][A-Fa-f0-9]([A-Fa-f0-9][A-Fa-f0-9][A-Fa-f0-9])?)\](.*?)\[\/color$bbcode_uid\]/is", '<span style="color: #\\1">\\3</span>', $text);
+  
+  // Size
+  $text = preg_replace("/\[size$bbcode_uid=([0-4]+(\.[0-9]+)?)\](.*?)\[\/size$bbcode_uid\]/is", '<span style="font-size: \\1em;">\\3</span>', $text);
   
   // Quotes
-  $text = preg_replace("/\[quote:$bbcode_uid\](.*?)\[\/quote:$bbcode_uid\]/is", '<blockquote>\\1</blockquote>', $text);
+  $text = preg_replace("/\[quote$bbcode_uid=\"?([^]]+?)\"?\](.*?)\[\/quote$bbcode_uid\]/is", '<span class="decir-quoteheader">\\1 wrote:</span><blockquote>\\2</blockquote>', $text);
+  $text = preg_replace("/\[quote$bbcode_uid\](.*?)\[\/quote$bbcode_uid\]/is", '<blockquote class="decir-quotebody">\\1</blockquote>', $text);
+  
+  // https?:\/\/((([a-z0-9-]+\.)*)[a-z0-9-]+)(\/[A-z0-9_%\|~`!\!@#\$\^&\*\(\):;\.,\/-]*(\?(([a-z0-9_-]+)(=[A-z0-9_%\|~`\!@#\$\^&\*\(\):;\.,\/-\[\]]+)?((&([a-z0-9_-]+)(=[A-z0-9_%\|~`!\!@#\$\^&\*\(\):;\.,\/-]+)?)*))?)?)?
+  
+  // Links
+  // Trial and error.
+  $regexp = "/\[url$bbcode_uid(=(https?:\/\/((([a-z0-9-]+\.)*)[a-z0-9-]+)(\/[A-z0-9_%\|~`!\!@#\$\^&\*\(\):;\.,\/-]*(\?(([a-z0-9_-]+)(=[A-z0-9_%\|~`\!@#\$\^&\*\(\):;\.,\/-\[\]]+)?((&([a-z0-9_-]+)(=[A-z0-9_%\|~`!\!@#\$\^&\*\(\):;\.,\/-]+)?)*))?)?)?))?\](.*?)\[\/url$bbcode_uid\]/is";
+  $text = preg_replace($regexp, '<a href="\\2">\\15</a>', $text);
   
   // Newlines
   $text = str_replace("\n", "<br />\n", $text);
@@ -60,7 +53,7 @@
   $text = decir_bbcode_restore_code($text, $bbcode_uid, $_code);
   
   // Code
-  $text = preg_replace("/\[code:$bbcode_uid\](.*?)\[\/code:$bbcode_uid\]/is", '<pre>\\1</pre>', $text);
+  $text = preg_replace("/\[code$bbcode_uid\](.*?)\[\/code$bbcode_uid\]/is", '<pre>\\1</pre>', $text);
   
   return $text;
 }
@@ -96,3 +89,35 @@
   return $bbcode;
 }
 
+function bbcode_inject_uid($text, &$uid)
+{
+  $seed = md5( implode('.', explode(' ', microtime)) );
+  $uid = substr($seed, 0, 10);
+  // Bold text
+  $text = preg_replace("/\[b\](.*?)\[\/b\]/is", "[b:$uid]\\1[/b:$uid]", $text);
+  
+  // Italicized text
+  $text = preg_replace("/\[i\](.*?)\[\/i\]/is", "[i:$uid]\\1[/i:$uid]", $text);
+  
+  // Uunderlined text
+  $text = preg_replace("/\[u\](.*?)\[\/u\]/is", "[u:$uid]\\1[/u:$uid]", $text);
+  
+  // Colored text
+  $text = preg_replace("/\[color=\#([A-Fa-f0-9][A-Fa-f0-9][A-Fa-f0-9]([A-Fa-f0-9][A-Fa-f0-9][A-Fa-f0-9])?)\](.*?)\[\/color\]/is", "[color:$uid=#\\1]\\3[/color:$uid]", $text);
+  
+  // Size
+  $text = preg_replace('/\[size=([0-4]+(\.[0-9]+)?)\](.*?)\[\/size\]/is', "[size:$uid=\\1]\\3[/size:$uid]", $text);
+  
+  // Quotes
+  $text = preg_replace("/\[quote\](.*?)\[\/quote\]/is", "[quote:$uid]\\1[/quote:$uid]", $text);
+  $text = preg_replace("/\[quote=\"?([^]]+)\"?\](.*?)\[\/quote\]/is", "[quote:$uid=\\1]\\2[/quote:$uid]", $text);
+  
+  // Code
+  $text = preg_replace("/\[code\](.*?)\[\/code\]/is", "[code:$uid]\\1[/code:$uid]", $text);
+  
+  // URLs
+  $text = preg_replace('/\[url(=https?:\/\/([^ ]+))?\](.*?)\[\/url\]/is', "[url:$uid\\1]\\3[/url:$uid]", $text);
+  
+  return $text;
+}
+
--- a/decir/common.php	Wed Jun 13 22:33:54 2007 -0400
+++ b/decir/common.php	Wed Oct 17 20:23:51 2007 -0400
@@ -21,6 +21,7 @@
 }
 
 require('constants.php');
+require('functions.php');
 
 $html = '    <!-- Decir\'s updated namespace extractor function -->
     <script type="text/javascript">
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/decir/delete.php	Wed Oct 17 20:23:51 2007 -0400
@@ -0,0 +1,105 @@
+<?php
+/*
+ * Decir
+ * Version 0.1
+ * Copyright (C) 2007 Dan Fuhry
+ * edit.php - edit posts that already exist
+ *
+ * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ */
+
+require('common.php');
+
+$pid = $paths->getParam(1);
+if ( strval(intval($pid)) !== $pid )
+{
+  die_friendly('Error', '<p>Invalid post ID</p>');
+}
+
+$pid = intval($pid);
+
+// Obtain post info
+$q = $db->sql_query('SELECT p.post_id, p.topic_id, p.post_subject, t.post_text, t.bbcode_uid, p.poster_id, p.post_deleted FROM '.table_prefix."decir_posts AS p
+                       LEFT JOIN ".table_prefix."decir_posts_text AS t
+                         ON (t.post_id = p.post_id)
+                       WHERE p.post_id = $pid;");
+if ( !$q )
+  $db->_die('Decir delete.php');
+
+if ( $db->numrows() < 1 )
+{
+  die_friendly('Error', '<p>The post you requested does not exist.</p>');
+}
+
+$row = $db->fetchrow();
+$db->free_result();
+
+$tid = intval($row['topic_id']);
+
+$acl_type = ( $row['poster_id'] == $session->user_id && $session->user_logged_in ) ? 'decir_edit_own' : 'decir_edit_other';
+  
+$post_perms = $session->fetch_page_acl(strval($pid), 'DecirPost');
+if ( !$post_perms->get_permissions($acl_type) )
+{
+  die_friendly('Error', '<p>You do not have permission to edit this post.</p>');
+}
+
+$edit_reason = '';
+if ( isset($_GET['act']) && $_GET['act'] == 'submit' )
+{
+  if ( isset($_POST['do']['delete']) )
+  {
+    // Nuke it
+    $result = decir_delete_post($pid, $_POST['edit_reason'], ( $_POST['delete_method'] == 'hard' ));
+    if ( $result )
+    {
+      $url = makeUrlNS('Special', 'Forum/Topic/' . $tid, false, true) . '#post' . $pid;
+      redirect($url, 'Post deleted', 'The selected post has been deleted.', 4);
+    }
+    $edit_reason = htmlspecialchars($_POST['edit_reason']);
+  }
+  else if ( isset($_POST['do']['restore']) )
+  {
+    $result = decir_restore_post($pid);
+    if ( $result )
+    {
+      $url = makeUrlNS('Special', 'Forum/Topic/' . $tid, false, true) . '#post' . $pid;
+      redirect($url, 'Post restored', 'The selected post has been restored.', 4);
+    }
+  }
+  else if ( isset($_POST['do']['noop']) )
+  {
+    $url = makeUrlNS('Special', 'Forum/Post/' . $pid, false, true) . '#post' . $pid;
+    redirect($url, '', '', 0);
+  }
+}
+
+$template->header(true);
+$form_submit_url = makeUrlNS('Special', 'Forum/Delete/' . $pid, 'act=submit', true);
+?>
+<form action="<?php echo $form_submit_url; ?>" method="post" enctype="multipart/form-data">
+  <?php if ( $row['post_deleted'] == 1 ):
+  if ( isset($_GET['act']) && $_GET['act'] == 'restore' ): ?>
+  <p>Are you sure you want to restore this post so that it is visible to the public?</p>
+  <p><input type="submit" name="do[restore]" value="Restore post" tabindex="3" /> <input tabindex="4" type="submit" name="do[noop]" value="Cancel" /></p>
+  <?php else: ?>
+  <p>Are you sure you want to permanently delete this post?</p>
+  <p><input type="hidden" name="delete_method" value="hard" /><input type="submit" name="do[delete]" value="Delete post" tabindex="3" /> <input tabindex="4" type="submit" name="do[noop]" value="Cancel" /></p>
+  <?php endif;
+        else: ?>
+  <p>To delete this post, please enter a reason for deletion and click the appropriate button below.</p>
+  <p>Please note that if this the first post in the thread, the entire thread will be removed.</p>
+  <p><label><input type="radio" name="delete_method" value="soft" onclick="document.getElementById('decir_reason_box').style.display = 'inline';" checked="checked" tabindex="1" /> Soft delete</label> - <small>Post is replaced with the message you enter here. The original post is not removed from the database and is still visible to administrators.</small></p>
+  <p><input type="text" name="edit_reason" value="<?php echo $edit_reason; ?>" tabindex="2" style="width: 97%;" id="decir_reason_box" /></p>
+  <p><label><input type="radio" name="delete_method" value="hard" onclick="document.getElementById('decir_reason_box').style.display = 'none';" /> Physically remove post</label> - <small>Irreversibly removes the post from the database.</small></p>
+  <p><input type="submit" name="do[delete]" value="Delete post" tabindex="3" /> <input tabindex="4" type="submit" name="do[noop]" value="Cancel" /></p>
+  <?php endif; ?>
+</form>
+<?php
+$template->footer(true);
+
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/decir/edit.php	Wed Oct 17 20:23:51 2007 -0400
@@ -0,0 +1,159 @@
+<?php
+/*
+ * Decir
+ * Version 0.1
+ * Copyright (C) 2007 Dan Fuhry
+ * edit.php - edit posts that already exist
+ *
+ * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ */
+
+require('common.php');
+require('bbcode.php');
+
+$pid = $paths->getParam(1);
+if ( strval(intval($pid)) !== $pid )
+{
+  die_friendly('Error', '<p>Invalid post ID</p>');
+}
+
+$pid = intval($pid);
+
+// Obtain post info
+$q = $db->sql_query('SELECT p.post_id, p.post_subject, t.post_text, t.bbcode_uid, p.poster_id FROM '.table_prefix."decir_posts AS p
+                       LEFT JOIN ".table_prefix."decir_posts_text AS t
+                         ON (t.post_id = p.post_id)
+                       WHERE p.post_id = $pid;");
+if ( !$q )
+  $db->_die('Decir edit.php');
+
+if ( $db->numrows() < 1 )
+{
+  die_friendly('Error', '<p>The post you requested does not exist.</p>');
+}
+
+$row = $db->fetchrow();
+$db->free_result();
+
+$acl_type = ( $row['poster_id'] == $session->user_id && $session->user_logged_in ) ? 'decir_edit_own' : 'decir_edit_other';
+  
+$post_perms = $session->fetch_page_acl(strval($pid), 'DecirPost');
+if ( !$post_perms->get_permissions($acl_type) )
+{
+  die_friendly('Error', '<p>You do not have permission to edit this post.</p>');
+}
+
+$show_preview = false;
+$form_submit_url = makeUrlNS('Special', 'Forum/Edit/' . $pid, 'act=submit', true);
+$post_text = htmlspecialchars(bbcode_strip_uid($row['post_text'], $row['bbcode_uid']));
+$post_subject = htmlspecialchars($row['post_subject']);
+$edit_reason = '';
+
+if ( isset($_GET['act']) && $_GET['act'] == 'submit' )
+{
+  if ( isset($_POST['do']['preview']) )
+  {
+    $show_preview = true;
+    $post_text = htmlspecialchars($_POST['post_text']);
+    $post_subject = htmlspecialchars($_POST['post_subject']);
+    $edit_reason = htmlspecialchars($_POST['edit_reason']);
+    $message_render = render_bbcode($post_text);
+    $message_render = RenderMan::smilieyize($message_render);
+  }
+  else if ( isset($_POST['do']['save']) )
+  {
+    // Save changes
+    if ( isset($_POST['do']['delete']) )
+    {
+      // Nuke it
+      $result = decir_delete_post($pid, $_POST['edit_reason']);
+      if ( $result )
+      {
+        $url = makeUrlNS('Special', 'Forum/Post/' . $pid, false, true) . '#post' . $pid;
+        redirect($url, 'Post deleted', 'The selected post has been deleted.', 4);
+      }
+    }
+    $post_text = trim(htmlspecialchars($_POST['post_text']));
+    $post_subject = trim(htmlspecialchars($_POST['post_subject']));
+    $edit_reason = trim(htmlspecialchars($_POST['edit_reason']));
+    $errors = array();
+    
+    if ( empty($post_text) )
+      $errors[] = 'Please enter some post text.';
+    
+    $result = decir_edit_post($pid, $post_subject, $post_text, $edit_reason);
+    if ( $result )
+    {
+      $url = makeUrlNS('Special', 'Forum/Post/' . $pid, false, true) . '#post' . $pid;
+      redirect($url, 'Post successful', 'Your changes to this post have been saved.', 4);
+    }
+  }
+  else if ( isset($_POST['do']['noop']) )
+  {
+    $url = makeUrlNS('Special', 'Forum/Post/' . $pid, false, true) . '#post' . $pid;
+    redirect($url, '', '', 0);
+  }
+}
+
+// add JS for editor
+$template->add_header('<!-- DECIR BEGIN -->
+    <script type="text/javascript" src="' . scriptPath . '/decir/js/bbcedit.js"></script>
+    <script type="text/javascript" src="' . scriptPath . '/decir/js/colorpick/jquery.js"></script>
+    <script type="text/javascript" src="' . scriptPath . '/decir/js/colorpick/farbtastic.js"></script>
+    <link rel="stylesheet" type="text/css" href="' . scriptPath . '/decir/js/bbcedit.css" />
+    <link rel="stylesheet" type="text/css" href="' . scriptPath . '/decir/js/colorpick/farbtastic.css" />
+    <!-- DECIR END -->');
+
+$template->header();
+
+if ( $show_preview )
+{
+  echo '<div style="border: 1px solid #222222; background-color: #F0F0F0; padding: 10px; max-height: 300px; clip: rect(0px,auto,auto,0px); overflow: auto; margin: 10px 0;">
+          <h2>Post preview</h2>
+          <p>' . $message_render . '</p>
+        </div>';
+}
+
+?>
+<form action="<?php echo $form_submit_url; ?>" method="post" enctype="multipart/form-data">
+  <div class="tblholder">
+    <table border="0" cellspacing="1" cellpadding="4">
+      <tr>
+        <th colspan="2">Editing post: <?php echo $post_subject; ?></th>
+      </tr>
+      <tr>
+        <td class="row2">Delete post:</td>
+        <td class="row1"><label><input type="checkbox" name="do[delete]" /> To delete this post, check this box and click Save.</label><br /><small>If this is the first post in the thread, the entire thread will be deleted.</small></td>
+      </tr>
+      <tr>
+        <td class="row2">Post subject:</td>
+        <td class="row1"><input type="text" name="post_subject" value="<?php echo $post_subject; ?>" style="width: 100%;" /></td>
+      </tr>
+      <tr>
+        <td class="row2">Reason for editing:</td>
+        <td class="row1"><input type="text" name="edit_reason" value="<?php echo $edit_reason; ?>" style="width: 100%;" /></td>
+      </tr>
+      <tr>
+        <td class="row3" colspan="2">
+          <textarea name="post_text" class="bbcode" rows="20" cols="80"><?php echo $post_text; ?></textarea>
+        </td>
+      </tr>
+      <tr>
+        <th class="subhead" colspan="2">
+          <input type="submit" name="do[save]" value="Save changes" />
+          <input type="submit" name="do[preview]" value="Show preview" />
+          <input type="submit" name="do[noop]" value="Cancel" />
+        </th>
+      </tr>
+    </table>
+  </div>
+</form>
+<?php
+
+$template->footer();
+
+?>
--- a/decir/forum_index.php	Wed Jun 13 22:33:54 2007 -0400
+++ b/decir/forum_index.php	Wed Oct 17 20:23:51 2007 -0400
@@ -49,16 +49,24 @@
       case FORUM_FORUM:
         $color = ( $row['user_level'] >= USER_LEVEL_ADMIN ) ? 'AA0000' : ( ( $row['user_level'] >= USER_LEVEL_MOD ) ? '00AA00' : '0000AA' );
         // Forum
+        if ( $row['post_id'] )
+        {
+          $last_post_data = '<small>
+                   <a href="' . makeUrlNS('DecirTopic', $row['topic_id']) . '#post' . $row['post_id'] . '">' . $row['topic_title'] . '</a><br />
+                   ' . date('d M Y h:i a', $row['timestamp']) . '<br />
+                   by <b><a style="color: #' . $color . '" href="' . makeUrlNS('User', $row['username']) . '">' . $row['username'] . '</a></b>
+                 </small>';
+        }
+        else
+        {
+          $last_post_data = 'No posts';
+        }
         echo '<tr><td class="row3" style="text-align: center;">&lt;icon&gt;</td><td class="row2"><b><a href="' . makeUrlNS('DecirForum', $row['forum_id']) . '">'
              . $row['forum_name'] . '</a></b><br />' . $row['forum_desc'].'</td>
              <td class="row3" style="text-align: center;">' . $row['num_topics'] . '</td>
              <td class="row3" style="text-align: center;">' . $row['num_posts'] . '</td>
              <td class="row1" style="text-align: center;">
-               <small>
-                 <a href="' . makeUrlNS('DecirTopic', $row['topic_id']) . '#post' . $row['post_id'] . '">' . $row['topic_title'] . '</a><br />
-                 ' . date('d M Y h:i a', $row['timestamp']) . '<br />
-                 by <b><a style="color: #' . $color . '" href="' . makeUrlNS('User', $row['username']) . '">' . $row['username'] . '</a></b>
-               </small>
+               ' . $last_post_data . '
              </td>
              </tr>';
         break;
@@ -75,7 +83,7 @@
 }
 else
 {
-  echo '<td class="row1" colspan="4">This board has no forums.</td>';
+  echo '<td class="row1" colspan="5">This board has no forums.</td>';
 }
 if ( $cat_open )
   echo '</tbody>';
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/decir/functions.php	Wed Oct 17 20:23:51 2007 -0400
@@ -0,0 +1,355 @@
+<?php
+/*
+ * Decir
+ * Version 0.1
+ * Copyright (C) 2007 Dan Fuhry
+ * functions.php - Utility functions used by most Decir modules
+ *
+ * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ */
+
+/**
+ * Prints out breadcrumbs.
+ */
+ 
+function decir_breadcrumbs()
+{
+  // placeholder
+}
+
+/**
+ * Inserts a post in reply to a topic. Does NOT check any type of authorization at all.
+ * @param int Topic ID
+ * @param string Post subject
+ * @param string Post text
+ * @param reference Will be set to the new post ID.
+ */
+
+function decir_submit_post($topic_id, $post_subject, $post_text, &$post_id = false)
+{
+  global $db, $session, $paths, $template, $plugins; // Common objects
+  
+  if ( !is_int($topic_id) )
+    return false;
+  
+  $poster_id = $session->user_id;
+  $poster_name = ( $session->user_logged_in ) ? $db->escape($session->username) : 'Anonymous';
+  $timestamp = time();
+  
+  $post_text = bbcode_inject_uid($post_text, $bbcode_uid);
+  $post_text = $db->escape($post_text);
+  
+  $post_subject = $db->escape($post_subject);
+  
+  $q = $db->sql_query('INSERT INTO '.table_prefix."decir_posts(topic_id,poster_id,poster_name,post_subject,timestamp) VALUES($topic_id, $poster_id, '$poster_name', '$post_subject', $timestamp);");
+  if ( !$q )
+    $db->_die('Decir functions.php in decir_submit_post()');
+  
+  $post_id = $db->insert_id();
+  $q = $db->sql_query('INSERT INTO '.table_prefix."decir_posts_text(post_id, post_text, bbcode_uid) VALUES($post_id, '$post_text', '$bbcode_uid');");
+  if ( !$q )
+    $db->_die('Decir functions.php in decir_submit_post()');
+  
+  return true;
+}
+
+/**
+ * Registers a new topic. Does not perform any type of authorization checks at all.
+ * @param int Forum ID
+ * @param string Post subject
+ * @param string Post text
+ * @param reference Will be set to the new topic ID
+ */
+
+function decir_submit_topic($forum_id, $post_subject, $post_text, &$topic_id = false, &$post_id = false)
+{
+  global $db, $session, $paths, $template, $plugins; // Common objects
+  
+  if ( !is_int($forum_id) )
+    return false;
+  
+  $poster_id = $session->user_id;
+  $timestamp = time();
+  
+  $topic_subject = $db->escape($post_subject);
+  
+  $q = $db->sql_query('INSERT INTO ' . table_prefix . "decir_topics(forum_id, topic_title, topic_starter, timestamp) VALUES( $forum_id, '$topic_subject', $poster_id, $timestamp );");
+  if ( !$q )
+    $db->_die('Decir functions.php in decir_submit_topic()');
+  $topic_id = $db->insert_id();
+  
+  // Submit the post
+  $postsub = decir_submit_post($topic_id, $post_subject, $post_text, $post_id);
+  
+  if ( !$postsub )
+    return false;
+  
+  // Update "last post"
+  $q = $db->sql_query('UPDATE '.table_prefix."decir_topics SET last_post=$post_id WHERE topic_id=$topic_id;");
+  if ( !$q )
+    $db->_die('Decir functions.php in decir_submit_topic()');
+  
+  return true;
+}
+
+/**
+ * Modifies a post's text. Does not perform any type of authorization checks at all.
+ * @param int Post ID
+ * @param string Post subject
+ * @param string Post text
+ * @param string Reason for editing
+ */
+
+function decir_edit_post($post_id, $subject, $message, $reason)
+{
+  global $db, $session, $paths, $template, $plugins; // Common objects
+  
+  if ( !is_int($post_id) )
+    return false;
+  
+  $last_edited_by = $session->user_id;
+  $edit_reason = $db->escape($reason);
+  $post_subject = $db->escape($subject);
+  $post_text = bbcode_inject_uid($message, $bbcode_uid);
+  $post_text = $db->escape($post_text);
+  
+  $q = $db->sql_query('UPDATE '.table_prefix."decir_posts SET edit_count = edit_count + 1, edit_reason='$edit_reason', post_subject='$post_subject', last_edited_by=$last_edited_by WHERE post_id=$post_id;");
+  if ( !$q )
+    $db->_die('Decir functions.php in decir_edit_post()');
+  
+  $q = $db->sql_query('UPDATE '.table_prefix."decir_posts_text SET post_text='$post_text' WHERE post_id=$post_id;");
+  if ( !$q )
+    $db->_die('Decir functions.php in decir_edit_post()');
+  
+  return true;
+}
+
+/**
+ * Deletes a post, or a topic if the post is the first topic in the thread. Does not perform any type of authorization checks at all.
+ * @param int Post id
+ * @param string Reason for deletion
+ * @param bool If true, removes the post physically from the database instead of "soft" deleting it
+ */
+
+function decir_delete_post($post_id, $del_reason, $for_real = false)
+{
+  global $db, $session, $paths, $template, $plugins; // Common objects
+  
+  if ( !is_int($post_id) )
+    return false;
+  
+  // Is this the first post in the thread?
+  $q = $db->sql_query('SELECT topic_id FROM '.table_prefix."decir_posts WHERE post_id = $post_id;");
+  if ( !$q )
+    $db->_die('Decir functions.php in decir_delete_post()');
+  if ( $db->numrows() < 1 )
+    // Post doesn't exist
+    return false;
+  $row = $db->fetchrow();
+  $db->free_result();
+  
+  $topic_id = intval($row['topic_id']);
+  
+  // while we're at it, also get the forum id
+  $q = $db->sql_query('SELECT p.post_id, t.forum_id FROM '.table_prefix."decir_posts AS p
+                         LEFT JOIN ".table_prefix."decir_topics AS t
+                           ON ( t.topic_id = p.topic_id )
+                         WHERE p.topic_id = $topic_id ORDER BY p.timestamp ASC LIMIT 1;");
+  if ( !$q )
+    $db->_die('Decir functions.php in decir_delete_post()');
+  $row = $db->fetchrow();
+  $db->free_result();
+  
+  $forum_id = intval($row['forum_id']);
+  
+  if ( $row['post_id'] == $post_id )
+  {
+    // first post in the thread
+    return decir_delete_topic($topic_id, $del_reason);
+  }
+  
+  $del_reason = $db->escape($del_reason);
+  
+  if ( $for_real )
+  {
+    $q = $db->sql_query('DELETE FROM '.table_prefix."decir_posts_text WHERE post_id = $post_id;");
+    if ( !$q )
+      $db->_die('Decir functions.php in decir_delete_post()');
+    $q = $db->sql_query('DELETE FROM '.table_prefix."decir_posts WHERE post_id = $post_id;");
+    if ( !$q )
+      $db->_die('Decir functions.php in decir_delete_post()');
+  }
+  else
+  {
+    // Delete the post
+    $q = $db->sql_query('UPDATE '.table_prefix."decir_posts SET post_deleted = 1, last_edited_by = $session->user_id, edit_reason = '$del_reason' WHERE post_id = $post_id;");
+    if ( !$q )
+      $db->_die('Decir functions.php in decir_delete_post()');
+  }
+  
+  // update forum stats
+  $q = $db->sql_query('UPDATE '.table_prefix."decir_forums SET num_posts = num_posts - 1 WHERE forum_id = $forum_id;");
+  if ( !$q )
+      $db->_die('Decir functions.php in decir_delete_post()');
+    
+  // update last post and topic
+  decir_update_forum_stats($forum_id);
+  
+  return true;
+}
+
+/**
+ * Deletes a topic. Does not perform any type of authorization checks at all.
+ * @param int Topic ID
+ * @param string Reason for deleting the topic
+ * @param bool If true, physically removes the topic from the database; else, just turns on the delete switch
+ */
+
+function decir_delete_topic($topic_id, $del_reason, $unlink = false)
+{
+  global $db, $session, $paths, $template, $plugins; // Common objects
+  
+  if ( !is_int($topic_id) )
+    return false;
+  
+  // Obtain a list of posts in the topic
+  $q = $db->sql_query('SELECT post_id FROM '.table_prefix.'decir_posts WHERE topic_id = ' . $topic_id . ';');
+  if ( !$q )
+    $db->_die('Decir functions.php in decir_delete_topic()');
+  if ( $db->numrows() < 1 )
+    return false;
+  $posts = array();
+  while ( $row = $db->fetchrow() )
+  {
+    $posts[] = $row['post_id'];
+  }
+  
+  // Obtain forum ID
+  $q = $db->sql_query('SELECT forum_id FROM '.table_prefix."decir_topics WHERE topic_id = $topic_id;");
+  if ( !$q )
+    $db->_die('Decir functions.php in decir_delete_topic()');
+  list($forum_id) = $db->fetchrow_num();
+  $db->free_result();
+  
+  // Perform delete
+  if ( $unlink )
+  {
+    // Remove all posts from the database
+    $post_list = implode(' OR post_id=', $posts);
+    $q = $db->sql_query('DELETE FROM '.table_prefix."decir_posts_text WHERE $post_list;");
+    if ( !$q )
+      $db->_die('Decir functions.php in decir_delete_topic()');
+    $q = $db->sql_query('DELETE FROM '.table_prefix."decir_posts WHERE $post_list;");
+    if ( !$q )
+      $db->_die('Decir functions.php in decir_delete_topic()');
+    // Remove the topic itself
+    $q = $db->sql_query('DELETE FROM '.table_prefix."decir_topics WHERE topic_id = $topic_id;");
+    if ( !$q )
+      $db->_die('Decir functions.php in decir_delete_topic()');
+  }
+  else
+  {
+    $reason = $db->escape($del_reason);
+    $topic_deletor = $session->user_id;
+    $q = $db->sql_query('UPDATE ' . table_prefix . "decir_topics SET topic_deleted = 1, topic_deletor = $topic_deletor, topic_delete_reason = '$reason' WHERE topic_id = $topic_id;");
+  }
+  
+  // Update forum stats
+  $post_count = count($posts);
+  $q = $db->sql_query('UPDATE '.table_prefix."decir_forums SET num_topics = num_topics - 1, num_posts = num_posts - $post_count WHERE forum_id = $forum_id;");
+  if ( !$q )
+    $db->_die('Decir functions.php in decir_delete_topic()');
+  decir_update_forum_stats($forum_id);
+  
+  return true;
+}
+
+/**
+ * Updates the last post information for the specified forum.
+ * @param int Forum ID
+ */
+
+function decir_update_forum_stats($forum_id)
+{
+  global $db, $session, $paths, $template, $plugins; // Common objects
+  
+  if ( !is_int($forum_id) )
+    return false;
+  
+  $sql = 'SELECT p.post_id, p.poster_id, p.topic_id FROM ' . table_prefix . "decir_posts AS p
+            LEFT JOIN ".table_prefix."decir_topics AS t
+              ON ( t.topic_id = p.topic_id )
+            WHERE t.forum_id = $forum_id
+              AND p.post_deleted != 1
+            ORDER BY p.timestamp DESC
+            LIMIT 1;";
+  $q = $db->sql_query($sql);
+  if ( !$q )
+    $db->_die('Decir functions.php in decir_update_forum_stats()');
+  
+  if ( $db->numrows() < 1 )
+  {
+    $last_post_id = 'NULL';
+    $last_post_topic = 'NULL';
+    $last_post_user = 'NULL';
+  }
+  else
+  {
+    $row = $db->fetchrow();
+    $last_post_id = intval($row['post_id']);
+    $last_post_topic = intval($row['topic_id']);
+    $last_post_user = intval($row['poster_id']);
+  }
+  $db->free_result();
+  
+  $sql = 'UPDATE ' . table_prefix . "decir_forums SET last_post_id = $last_post_id, last_post_topic = $last_post_topic,
+            last_post_user = $last_post_user WHERE forum_id = $forum_id;";
+  if ( $db->sql_query($sql) )
+    return true;
+  else
+    $db->_die('Decir functions.php in decir_update_forum_stats()');
+}
+
+/**
+ * Un-deletes a post so that the public can see it.
+ * @param int Post ID
+ */
+
+function decir_restore_post($post_id)
+{
+  global $db, $session, $paths, $template, $plugins; // Common objects
+  
+  if ( !is_int($post_id) )
+    return false;
+  
+  $q = $db->sql_query('UPDATE ' . table_prefix . "decir_posts SET post_deleted = 0, edit_count = 0, last_edited_by = NULL, edit_reason = '' WHERE post_id = $post_id AND post_deleted = 1;");
+  if ( !$q )
+    $db->_die('Decir functions.php in decir_restore_post()');
+  
+  if ( $db->sql_affectedrows() > 0 )
+  {
+    // get forum id
+    $q = $db->sql_query('SELECT t.forum_id FROM '.table_prefix."decir_posts AS p
+                           LEFT JOIN ".table_prefix."decir_topics AS t
+                             ON ( p.topic_id = t.topic_id )
+                           WHERE p.post_id = $post_id;");
+    if ( !$q )
+      $db->_die('Decir functions.php in decir_restore_post()');
+    $row = $db->fetchrow();
+    $db->free_result();
+    $forum_id = intval($row['forum_id']);
+    // Update forum stats
+    $q = $db->sql_query('UPDATE ' . table_prefix . "decir_forums SET num_posts = num_posts + 1 WHERE forum_id = $forum_id;");
+    if ( !$q )
+      $db->_die('Decir functions.php in decir_restore_post()');
+    decir_update_forum_stats($forum_id);
+    return true;
+  }
+  return false;
+}
+
+?>
--- a/decir/install.sql	Wed Jun 13 22:33:54 2007 -0400
+++ b/decir/install.sql	Wed Oct 17 20:23:51 2007 -0400
@@ -23,6 +23,9 @@
   topic_locked tinyint(1) unsigned NOT NULL DEFAULT 0,
   topic_moved tinyint(1) unsigned NOT NULL DEFAULT 0,
   timestamp int(11) unsigned NOT NULL,
+  topic_deleted tinyint(1) NOT NULL DEFAULT 0,
+  topic_deletor int(12) DEFAULT NULL,
+  topic_delete_reason varchar(255) DEFAULT NULL,
   PRIMARY KEY ( topic_id )
 );
 CREATE TABLE decir_posts(
@@ -30,10 +33,12 @@
   topic_id bigint(15) unsigned NOT NULL,
   poster_id int(12) unsigned NOT NULL,
   poster_name varchar(255) NOT NULL,
+  post_subject varchar(255) NOT NULL DEFAULT '',
   timestamp int(11) unsigned NOT NULL,
   last_edited_by int(12) unsigned DEFAULT NULL,
-  edit_count int(5) unsigned,
+  edit_count int(5) unsigned NOT NULL DEFAULT 0,
   edit_reason varchar(255),
+  post_deleted tinyint(1) NOT NULL DEFAULT 0,
   PRIMARY KEY ( post_id )
 );
 CREATE TABLE decir_posts_text(
--- a/decir/js/bbcedit.js	Wed Jun 13 22:33:54 2007 -0400
+++ b/decir/js/bbcedit.js	Wed Oct 17 20:23:51 2007 -0400
@@ -10,15 +10,6 @@
 	var is_opera_seven = (window.opera && document.childNodes);
 }
 
-var $_GET=new Object();
-var aParams=document.location.search.substr(1).split('&');
-for ( i = 0; i < aParams.length; i++ ) {
-  var aParam=aParams[i].split('=');
-  var sParamName=aParam[0];
-  var sParamValue=aParam[1];
-  $_GET[sParamName]=sParamValue;
-}
-
 // List of BBcode buttons
 
 var buttons = [
@@ -65,6 +56,20 @@
       'start' : '[quote]',
       'end'   : '[/quote]',
       'desc'  : 'Quote'
+    },
+    {
+      'start' : '[url]',
+      'end'   : '[/url]',
+      'custom':true,
+      'func'  : function() { openUrlInput(this); },
+      'desc'  : 'URL'
+    },
+    {
+      'start' : '[[',
+      'end'   : ']]',
+      'custom':true,
+      'func'  : function() { openWikilinkInput(this); },
+      'desc'  : 'Wikilink'
     }
   ];
 
@@ -148,8 +153,6 @@
     fl.appendChild(lb);
     var used = [];
     
-    var scriptPath = ''; // REMOVE FOR ENANO IMPLEMENTATION!
-    
     var frm = document.createElement('form');
     frm.action='javascript:void(0)';
     frm.onsubmit = function(){return false;};
@@ -159,6 +162,7 @@
     tbl.cellspacing = '0';
     tbl.cellpadding = '0';
     tbl.width = '100%';
+    tbl.style.backgroundColor = 'transparent';
     
     var tr = document.createElement('tr');
     var tick = -1;
@@ -422,6 +426,56 @@
 }
 
 //
+// URL INPUT
+//
+
+function openUrlInput(button)
+{
+  var url = prompt('Please enter the URL to the page you want to link to:', 'http://');
+  if ( url == '' || url == 'http://' || !url )
+    return false;
+  
+  var start = '[url]';
+  var inner = url;
+  var end = '[/url]';
+  
+  var text = prompt('Please enter some text to be displayed as the link (optional):');
+  if ( text != '' && ! (!text) )
+  {
+    start = '[url=' + url + ']';
+    inner = text;
+    end = '[/url]';
+  }
+  
+  formatBBCode(button, start, end, inner);
+}
+
+//
+// WIKILINK INPUT
+//
+
+function openWikilinkInput(button)
+{
+  var url = prompt('Please enter the title of the page to link to:', '');
+  if ( url == '' || !url )
+    return false;
+  
+  var start = '[[';
+  var inner = url;
+  var end = ']]';
+  
+  var text = prompt('Please enter some text to be displayed as the link (optional):');
+  if ( text != '' && ! (!text) )
+  {
+    start = '[[' + url + '|';
+    inner = text;
+    end = ']]';
+  }
+  
+  formatBBCode(button, start, end, inner);
+}
+
+//
 // HTML RENDERER
 //
 
@@ -531,19 +585,22 @@
   el = parent.getElementsByTagName(type);
   for ( var i in el )
   {
-    if(el[i].className)
+    if(el[i])
     {
-      if(el[i].className.indexOf(' ') > 0)
+      if(el[i].className)
       {
-        classes = el[i].className.split(' ');
+        if(el[i].className.indexOf(' ') > 0)
+        {
+          classes = el[i].className.split(' ');
+        }
+        else
+        {
+          classes = new Array();
+          classes.push(el[i].className);
+        }
+        if ( in_array(cls, classes) )
+          ret.push(el[i]);
       }
-      else
-      {
-        classes = new Array();
-        classes.push(el[i].className);
-      }
-      if ( in_array(cls, classes) )
-        ret.push(el[i]);
     }
   }
   return ret;
--- a/decir/posting.php	Wed Jun 13 22:33:54 2007 -0400
+++ b/decir/posting.php	Wed Oct 17 20:23:51 2007 -0400
@@ -60,12 +60,15 @@
   }
   else if ( isset($_POST['do']['post']) )
   {
+    $errors = Array();
+    
     // Decrypt authorization array
     $parms = $aes->decrypt($_POST['authorization'], $session->private_key, ENC_HEX);
+    if ( !$parms )
+      $errors[] = 'Could not decrypt authorization key.';
     $parms = unserialize($parms);
     
     // Perform a little input validation
-    $errors = Array();
     if ( empty($_POST['post_text']) )
       $errors[] = 'Please enter a post.';
     if ( empty($_POST['subject']) && $parms['mode'] == 'topic' )
@@ -74,14 +77,47 @@
     if ( !$parms['authorized'] )
       $errors[] = 'Invalid authorization key';
     
-    if ( sizeof($errors) > 0 )
+    if ( sizeof($errors) < 1 )
     {
       // Collect other options
       
       // Submit post
-      decir_submit_post();
+      if ( $parms['mode'] == 'reply' || $parms['mode'] == 'quote' )
+      {
+        $result = decir_submit_post($parms['topic_in'], $_POST['subject'], $_POST['post_text'], $post_id);
+        if ( $result )
+        {
+          // update forum stats
+          $user = $db->escape($session->username);
+          $q = $db->sql_query('UPDATE '.table_prefix."decir_forums SET num_posts = num_posts+1, last_post_id = $post_id, last_post_topic = {$parms['topic_in']}, last_post_user = $session->user_id WHERE forum_id={$parms['forum_in']};");
+          if ( !$q )
+          {
+            $db->_die('Decir posting.php under Submit post [reply]');
+          }
+          $url = makeUrlNS('Special', 'Forum/Topic/' . $parms['topic_in'], false, true);
+          redirect($url, 'Post submitted', 'Your post has been submitted successfully.', 4);
+        }
+      }
+      else if ( $parms['mode'] == 'topic' )
+      {
+        $result = decir_submit_topic($parms['forum_id'], $_POST['subject'], $_POST['post_text'], $topic_id, $post_id);
+        if ( $result )
+        {
+          // update forum stats
+          $q = $db->sql_query('UPDATE '.table_prefix."decir_forums SET num_posts = num_posts+1, num_topics = num_topics+1, last_post_id = $post_id, last_post_topic = $topic_id, last_post_user = $session->user_id WHERE forum_id={$parms['forum_id']};");
+          if ( !$q )
+          {
+            $db->_die('Decir posting.php under Submit post [topic]');
+          }
+          $url = makeUrlNS('Special', 'Forum/Topic/' . $topic_id, false, true);
+          redirect($url, 'Post submitted', 'Your post has been submitted successfully.', 4);
+        }
+      }
       return;
     }
+    $mode = 'already_taken_care_of';
+    $parms2 = $parms;
+    $parms = htmlspecialchars($_POST['authorization']);
   }
 }
 
@@ -90,6 +126,7 @@
   if ( $mode == 'reply' )
   {
     $message = '';
+    $subject = '';
     // Validate topic ID
     $topic_id = intval($paths->getParam(2));
     if ( empty($topic_id) )
@@ -108,7 +145,7 @@
       die_friendly('Error', '<p>Invalid post ID</p>');
     
     // Get post text and topic ID
-    $q = $db->sql_query('SELECT p.topic_id,t.post_text,t.bbcode_uid,p.poster_name FROM '.table_prefix.'decir_posts AS p
+    $q = $db->sql_query('SELECT p.topic_id,t.post_text,t.bbcode_uid,p.poster_name,p.post_subject FROM '.table_prefix.'decir_posts AS p
                            LEFT JOIN '.table_prefix.'decir_posts_text AS t
                              ON ( p.post_id = t.post_id )
                            WHERE p.post_id=' . $post_id . ';');
@@ -123,6 +160,7 @@
     $db->free_result();
     
     $message = '[quote="' . $row['poster_name'] . '"]' . bbcode_strip_uid( $row['post_text'], $row['bbcode_uid'] ) . '[/quote]';
+    $subject = 'Re: ' . htmlspecialchars($row['post_subject']);
     $quote_poster = $row['poster_name'];
     $topic_id = intval($row['topic_id']);
     
@@ -139,8 +177,8 @@
   $row = $db->fetchrow();
   $db->free_result();
   
-  $forum_perms = $session->fetch_page_acl('DecirForum', $row['forum_id']);
-  $topic_perms = $session->fetch_page_acl('DecirTopic', $row['topic_id']);
+  $forum_perms = $session->fetch_page_acl($row['forum_id'], 'DecirForum');
+  $topic_perms = $session->fetch_page_acl($row['topic_id'], 'DecirTopic');
   
   if ( !$forum_perms->get_permissions('decir_see_forum') )
     die_friendly('Error', '<p>The forum you requested does not exist.</p>');
@@ -166,6 +204,7 @@
 else if ( $mode == 'topic' )
 {
   $message = '';
+  $subject = '';
   // Validate topic ID
   $forum_id = intval($paths->getParam(2));
   if ( empty($forum_id) )
@@ -173,7 +212,7 @@
   $title = 'Post new topic';
   
   // Topic ID is good, verify topic status
-  $q = $db->sql_query('SELECT forum_id FROM '.table_prefix.'decir_forums WHERE forum_id=' . $forum_id . ';');
+  $q = $db->sql_query('SELECT forum_id, forum_name FROM '.table_prefix.'decir_forums WHERE forum_id=' . $forum_id . ';');
   
   if ( !$q )
     $db->_die();
@@ -184,14 +223,14 @@
   $row = $db->fetchrow();
   $db->free_result();
   
-  $forum_perms = $session->fetch_page_acl('DecirForum', $row['forum_id']);
+  $forum_perms = $session->fetch_page_acl($row['forum_id'], 'DecirForum');
   
   if ( !$forum_perms->get_permissions('decir_see_forum') )
     die_friendly('Error', '<p>The forum you requested does not exist.</p>');
   
   $parms = Array(
       'mode' => $mode,
-      'forum_in' => $forum_in,
+      'forum_id' => $forum_id,
       'timestamp' => time(),
       'authorized' => true
     );
@@ -203,7 +242,7 @@
 else if ( $mode == 'already_taken_care_of' )
 {
   $mode = $parms2['mode'];
-  $title = ( $mode == 'topic' ) ? 'Post new topic' : ( $mode == 'reply' ) ? 'Reply to topic' : ( $mode  == 'quote' ) ? 'Reply to topic with quote' : 'Duh...';
+  $title = ( $mode == 'topic' ) ? 'Post new topic' : ( ( $mode == 'reply' ) ? 'Reply to topic' : ( $mode  == 'quote' ) ? 'Reply to topic with quote' : 'Duh...' );
 }
 else
 {
@@ -221,17 +260,43 @@
 
 $template->header();
 
+if ( isset($errors) )
+{
+  echo '<div class="error-box" style="margin: 10px 0;">
+          <b>Your post could not be submitted.</b>
+          <ul>
+            <li>' . implode("</li>\n            <li>", $errors) . '</li>
+          </ul>
+        </div>';
+}
+
 if ( $do_preview )
 {
-  echo 'Doing preview';
+  $message = $_POST['post_text'];
+  $subject = htmlspecialchars($_POST['subject']);
+  $message_render = render_bbcode($message);
+  $message_render = RenderMan::smilieyize($message_render);
+  echo '<div style="border: 1px solid #222222; background-color: #F0F0F0; padding: 10px; max-height: 300px; clip: rect(0px,auto,auto,0px); overflow: auto; margin: 10px 0;">
+          <h2>Post preview</h2>
+          <p>' . $message_render . '</p>
+        </div>';
 }
 
 $url = makeUrlNS('Special', 'Forum/New', 'act=post', true);
 echo '<br />
       <form action="' . $url . '" method="post" enctype="multipart/form-data">';
+echo '<div class="tblholder">
+        <table border="0" cellspacing="1" cellpadding="4">';
+echo '<tr><td class="row2">Post subject:</td><td class="row1"><input name="subject" type="text" size="50" style="width: 100%;" value="' . $subject . '" /></td>';
+echo '<tr><td class="row3" colspan="2">';
 echo '<textarea name="post_text" class="bbcode" rows="20" cols="80">' . $message . '</textarea>';
-echo '<input type="hidden" name="authorization" value="' . $parms . '" />';
-echo '<div style="text-align: center; margin-top: 10px;"><input type="submit" name="do[post]" value="Submit post" style="font-weight: bold;" />&nbsp;<input type="submit" name="do[preview]" value="Show preview" /></div>';
+echo '</td></tr>';
+echo '
+      <!-- This authorization code is encrypted with '.AES_BITS.'-bit AES. -->
+      ';
+echo '<tr><th colspan="2" class="subhead"><input type="hidden" name="authorization" value="' . $parms . '" />';
+echo '<input type="submit" name="do[post]" value="Submit post" style="font-weight: bold;" />&nbsp;<input type="submit" name="do[preview]" value="Show preview" /></th></tr>';
+echo '</table></div>';
 echo '</form>';
 
 $template->footer();
--- a/decir/viewforum.php	Wed Jun 13 22:33:54 2007 -0400
+++ b/decir/viewforum.php	Wed Oct 17 20:23:51 2007 -0400
@@ -35,19 +35,22 @@
 $sort_column = ( isset($_GET['sort_column']) && in_array($_GET['sort_column'], array('t.timestamp', 't.topic_title')) ) ? $_GET['sort_column'] : 't.timestamp';
 $sort_dir    = ( isset($_GET['sort_dir'])    && in_array($_GET['sort_dir'],    array('ASC', 'DESC')) ) ? $_GET['sort_dir'] : 'DESC';
 
-$q = $db->sql_query('SELECT t.topic_id,t.topic_title,t.topic_type,t.topic_icon,COUNT(p.post_id)-1 AS num_replies,
+$q = $db->sql_query('SELECT t.topic_id,t.topic_title,t.topic_type,t.topic_icon,
                      COUNT(h.hit_id) AS num_views,t.topic_starter AS starter_id, u.username AS topic_starter,
-                     p.poster_name AS last_post_name, p.timestamp AS last_post_time
+                     p.poster_name AS last_post_name, p.timestamp AS last_post_time, t.topic_deleted, u2.username AS deletor,
+                     t.topic_delete_reason
                        FROM '.table_prefix.'decir_topics AS t
                      LEFT JOIN '.table_prefix.'decir_posts AS p
-                       ON (t.last_post=p.post_id)
+                       ON (t.topic_id = p.topic_id)
                      LEFT JOIN '.table_prefix.'decir_hits AS h
                        ON (t.topic_id=h.topic_id)
                      LEFT JOIN '.table_prefix.'users AS u
                        ON (u.user_id=t.topic_starter)
+                     LEFT JOIN '.table_prefix.'users AS u2
+                       ON (u2.user_id = t.topic_deletor OR t.topic_deletor IS NULL)
                      WHERE t.forum_id='.$fid.'
-                     GROUP BY t.topic_id
-                     ORDER BY '.$sort_column.' '.$sort_dir.';');
+                     GROUP BY p.post_id
+                     ORDER BY '.$sort_column.' '.$sort_dir.', p.timestamp DESC;');
 
 if(!$q)
   $db->_die();
@@ -64,22 +67,61 @@
 
 if ( $row = $db->fetchrow() )
 {
+  $last_row = $row;
+  $i = 0;
+  $num_replies = -1;
   do
   {
-    echo '<tr>
-            <td class="row2"></td>
-            <td class="row2"></td>
-            <td class="row2" style="width: 100%;"><b><a href="' . makeUrlNS('DecirTopic', $row['topic_id']) . '">' . $row['topic_title'] . '</a></b></td>
-            <td class="row3" style="text-align: center; max-width: 100px;">' . $row['topic_starter'] . '</td>
-            <td class="row1" style="text-align: center; width: 50px;">' . $row['num_replies'] . '</td>
-            <td class="row1" style="text-align: center; width: 50px;">' . $row['num_views'] . '</td>
-            <td class="row3" style="text-align: center;"><small style="white-space: nowrap;">' . date('d M Y h:i a', $row['last_post_time']) . '<br />by '.$row['last_post_name'].'</small></td>
-          </tr>';
+    $i++;
+    if ( $last_row['topic_id'] != $row['topic_id'] || $i == $db->numrows() )
+    {
+      if ( $last_row['topic_deleted'] == 1 )
+      {
+        $thread_link = '';
+        // FIXME: This will be controlled by an ACL rule
+        if ( $session->user_level >= USER_LEVEL_MOD )
+        {
+          $thread_link = '<b><a class="wikilink-nonexistent" href="' . makeUrlNS('DecirTopic', $last_row['topic_id']) . '">' . $last_row['topic_title'] . '</a></b>';
+        }
+        echo '<tr>
+              <td class="row2"></td>
+              <td class="row2"></td>
+              <td class="row2" style="width: 100%;">' . $thread_link . '</td>
+              <td class="row3" style="text-align: center;" colspan="4">Thread deleted by <b>' . htmlspecialchars($row['deletor']) . '</b><br />Reason: <i>' . htmlspecialchars($row['topic_delete_reason']) . '</i></td>
+            </tr>';
+      }
+      else
+      {
+        echo '<tr>
+              <td class="row2"></td>
+              <td class="row2"></td>
+              <td class="row2" style="width: 100%;"><b><a href="' . makeUrlNS('DecirTopic', $last_row['topic_id']) . '">' . $last_row['topic_title'] . '</a></b></td>
+              <td class="row3" style="text-align: center; max-width: 100px;">' . $last_row['topic_starter'] . '</td>
+              <td class="row1" style="text-align: center; width: 50px;">' . $num_replies . '</td>
+              <td class="row1" style="text-align: center; width: 50px;">' . $last_row['num_views'] . '</td>
+              <td class="row3" style="text-align: center;"><small style="white-space: nowrap;">' . date('d M Y h:i a', $last_row['last_post_time']) . '<br />by '.$last_row['last_post_name'].'</small></td>
+            </tr>';
+      }
+      $num_replies = 0;
+    }
+    $num_replies++;
+    $last_row = $row;
   } while ( $row = $db->fetchrow() );
 }
+else
+{
+  echo '<tr>
+          <td colspan="7" style="text-align: center;" class="row1">There are no topics in this forum.</td>
+        </tr>';
+}
 
 echo '</table></div>';
 
+if ( $perms->get_permissions('decir_post') )
+{
+  echo '<p><a href="' . makeUrlNS('Special', 'Forum/New/Topic/' . $fid) . '">Post new topic</a></p>';
+}
+
 $template->footer();
 
 ?>
--- a/decir/viewtopic.php	Wed Jun 13 22:33:54 2007 -0400
+++ b/decir/viewtopic.php	Wed Oct 17 20:23:51 2007 -0400
@@ -11,7 +11,7 @@
  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
  * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
  */
-
+ 
 require('common.php');
 require('bbcode.php');
 
@@ -36,6 +36,8 @@
     $db->_die();
   
   $row = $db->fetchrow();
+  $tid = intval($row['topic_id']);
+  $db->free_result();
 }
 else
 {
@@ -73,15 +75,59 @@
 <a name="{POST_ID}" id="{POST_ID}"></a>
 <div class="post tblholder">
   <table border="0" cellspacing="1" cellpadding="4" style="width: 100%;">
+    <!-- BEGIN post_deleted -->
+    <tr>
+      <td class="row3" valign="top" style="height: 100%;">
+        <i>This post was deleted by {LAST_EDITED_BY}.<br />
+        <b>Reason:</b> {EDIT_REASON}</i>
+        <!-- BEGIN show_post -->
+        <br />
+        <br />
+        <b>The deleted post is shown below:</b>
+        <!-- END show_post -->
+      </td>
+      <td class="row1" style="width: 120px;" valign="top">
+        {USER_LINK}
+      </td>
+    </tr>
+    <!-- END post_deleted -->
+    <!-- BEGIN show_post -->
     <tr>
       <th colspan="2" style="text-align: left;">Posted: {TIMESTAMP}</th>
     </tr>
     <tr>
-      <td class="row3" valign="top">
-        {POST_TEXT}
+      <td class="row3" valign="top" style="height: 100%;">
+        <table border="0" width="100%" style="height: 100%; background-color: transparent;">
+          <tr>
+            <td valign="top" colspan="2">
+              {POST_TEXT}
+            </td>
+          </tr>
+          <!-- BEGINNOT post_deleted -->
+          <tr>
+            <td valign="bottom" style="text-align: left; font-size: smaller;">
+              <!-- BEGIN post_edited -->
+              <i>Last edited by {LAST_EDITED_BY}; edited <b>{EDIT_COUNT}</b> time{EDIT_COUNT_S} in total<br />
+              <b>Reason:</b> {EDIT_REASON}</i>
+              <!-- END post_edited -->
+            </td>
+            <td valign="bottom" style="text-align: right;">
+              <small><a href="{EDIT_LINK}">edit</a> | <a href="{DELETE_LINK}">delete</a> | <a href="{QUOTE_LINK}">+ quote</a></small>
+            </td>
+          </tr>
+          <!-- BEGINELSE post_deleted -->
+          <tr>
+            <td valign="bottom" style="text-align: left; font-size: smaller;">
+            </td>
+            <td valign="bottom" style="text-align: right;">
+              <small><a href="{RESTORE_LINK}">restore post</a> | <a href="{DELETE_LINK}">physically delete</a></small>
+            </td>
+          </tr>
+          <!-- END post_deleted -->
+        </table>
       </td>
       <td class="row1" style="width: 120px;" valign="top">
-        <div class="menu">
+        <div class="menu_nojs">
           {USER_LINK}
           <ul>
             <li><a>View profile</a></li>
@@ -106,16 +152,20 @@
         <!-- END whos_online_support -->
       </td>
     </tr>
+    <!-- END show_post -->
   </table>
 </div>
 TPLCODE;
 
-$sql = 'SELECT p.post_id,p.poster_name,p.poster_id,u.username,p.timestamp,u.user_level,u.reg_time,t.post_text,t.bbcode_uid FROM '.table_prefix.'decir_posts AS p
+$sql = 'SELECT p.post_id,p.poster_name,p.poster_id,u.username,p.timestamp,p.edit_count,p.last_edited_by,p.post_deleted,u2.username AS editor,p.edit_reason,u.user_level,u.reg_time,t.post_text,t.bbcode_uid FROM '.table_prefix.'decir_posts AS p
           LEFT JOIN '.table_prefix.'users AS u
-            ON u.user_id=poster_id
+            ON u.user_id=p.poster_id
+          LEFT JOIN '.table_prefix.'users AS u2
+            ON (u2.user_id=p.last_edited_by OR p.last_edited_by IS NULL)
           LEFT JOIN '.table_prefix.'decir_posts_text AS t
             ON p.post_id=t.post_id
           WHERE p.topic_id='.$tid.'
+          GROUP BY p.post_id
           ORDER BY p.timestamp ASC;';
 
 $q = $db->sql_query($sql);
@@ -134,6 +184,7 @@
   $poster_name = ( $row['poster_id'] == 1 ) ? $row['poster_name'] : $row['username'];
   $datetime = date('F d, Y h:i a', $row['timestamp']);
   $post_text = render_bbcode($row['post_text'], $row['bbcode_uid']);
+  $post_text = RenderMan::smilieyize($post_text);
   $regtime = date('F Y', $row['reg_time']);
   
   $user_color = '#0000AA';
@@ -150,7 +201,10 @@
   {
     $user_link = '<big>'.$poster_name.'</big>';
   }
-  $quote_link = makeUrlNS('Special', 'Forum/New/Quote/' . $row['post_id'], false, true);
+  $quote_link  = makeUrlNS('Special', 'Forum/New/Quote/' . $row['post_id'], false, true);
+  $edit_link   = makeUrlNS('Special', 'Forum/Edit/' . $row['post_id'], false, true);
+  $delete_link = makeUrlNS('Special', 'Forum/Delete/' . $row['post_id'], false, true);
+  $restore_link = makeUrlNS('Special', 'Forum/Delete/' . $row['post_id'], 'act=restore', true);
   $user_title = 'Anonymous user';
   switch ( $row['user_level'] )
   {
@@ -159,6 +213,13 @@
     case USER_LEVEL_MEMBER:$user_title = 'Member'; break;
     case USER_LEVEL_GUEST: $user_title = 'Guest'; break;
   }
+  $leb_link = '';
+  if ( $row['editor'] )
+  {
+    $userpage_url = makeUrlNS('User', sanitize_page_id($row['editor']), false, true);
+    $row['editor'] = htmlspecialchars($row['editor']);
+    $leb_link = "<a href=\"$userpage_url\">{$row['editor']}</a>";
+  }
   $parser->assign_vars(Array(
       'POST_ID' => (string)$row['post_id'],
       'USERNAME' => $poster_name,
@@ -167,10 +228,17 @@
       'TIMESTAMP' => $datetime,
       'POST_TEXT' => $post_text,
       'USER_TITLE' => $user_title,
-      'QUOTE_LINK' => $quote_link
+      'QUOTE_LINK' => $quote_link,
+      'EDIT_LINK' => $edit_link,
+      'DELETE_LINK' => $delete_link,
+      'RESTORE_LINK' => $restore_link,
+      'EDIT_COUNT' => $row['edit_count'],
+      'EDIT_COUNT_S' => ( $row['edit_count'] == 1 ? '' : 's' ),
+      'LAST_EDITED_BY' => $leb_link,
+      'EDIT_REASON' => htmlspecialchars($row['edit_reason'])
     ));
   // Decir can integrate with the Who's Online plugin
-  $who_support = $plugins->loaded('WhosOnline');
+  $who_support = $plugins->loaded('WhosOnline') && $row['user_level'] >= USER_LEVEL_GUEST;
   $user_online = false;
   if ( $who_support && in_array($row['username'], $whos_online['users']) )
   {
@@ -182,7 +250,11 @@
   }
   $parser->assign_bool(Array(
       'whos_online_support' => $who_support,
-      'user_is_online' => $user_online
+      'user_is_online' => $user_online,
+      'post_edited' => ( $row['edit_count'] > 0 ),
+      'post_deleted' => ( $row['post_deleted'] == 1 ),
+      // FIXME: This should check something on ACLs
+      'show_post' => ( $row['post_deleted'] != 1 || $session->user_level >= USER_LEVEL_MOD )
     ));
   echo $parser->run();
 }
@@ -194,8 +266,8 @@
   $can_post_replies = false;
   $can_post_topics  = false;
   
-  $forum_perms = $session->fetch_page_acl('DecirForum', $forum_id);
-  $topic_perms = $session->fetch_page_acl('DecirTopic', $topic_id);
+  $forum_perms = $session->fetch_page_acl($forum_id, 'DecirForum');
+  $topic_perms = $session->fetch_page_acl($topic_id, 'DecirTopic');
   
   if ( $forum_perms->get_permissions('decir_post') )
     $can_post_topics = true;
@@ -219,5 +291,9 @@
   echo '</p>';
 }
 
+// log the hit
+$time = time();
+$q = $db->sql_query('INSERT INTO '.table_prefix."decir_hits(user_id, topic_id, timestamp) VALUES($session->user_id, $tid, $time);");
+
 $template->footer();
 
--- a/plugins/Decir.php	Wed Jun 13 22:33:54 2007 -0400
+++ b/plugins/Decir.php	Wed Oct 17 20:23:51 2007 -0400
@@ -36,14 +36,16 @@
 function decir_early_init(&$paths, &$session)
 {
   $paths->addAdminNode('Decir forum configuration', 'General settings', 'DecirGeneral');
-  $paths->nslist['DecirForum']  = $paths->nslist['Special'] . 'Forum/ViewForum/';
-  $paths->nslist['DecirPost']   = $paths->nslist['Special'] . 'Forum/Post/';
-  $paths->nslist['DecirTopic']  = $paths->nslist['Special'] . 'Forum/Topic/';
+  $paths->create_namespace('DecirForum', $paths->nslist['Special'] . 'Forum/ViewForum/');
+  $paths->create_namespace('DecirPost',  $paths->nslist['Special'] . 'Forum/Post/');
+  $paths->create_namespace('DecirTopic', $paths->nslist['Special'] . 'Forum/Topic/');
   
   $session->register_acl_type('decir_see_forum',  AUTH_ALLOW, 'See forum in index', Array('read'),             'DecirForum');
   $session->register_acl_type('decir_view_forum', AUTH_ALLOW, 'View forum',         Array('decir_see_forum'),  'DecirForum');
   $session->register_acl_type('decir_post',       AUTH_ALLOW, 'Post new topics',    Array('decir_view_forum'), 'DecirForum');
   $session->register_acl_type('decir_reply',      AUTH_ALLOW, 'Reply to topics',    Array('decir_post'),       'DecirTopic');
+  $session->register_acl_type('decir_edit_own',   AUTH_ALLOW, 'Edit own posts',     Array('decir_post'),       'DecirPost');
+  $session->register_acl_type('decir_edit_other', AUTH_DISALLOW, 'Edit others\' posts', Array('decir_post'),   'DecirPost');
 }
 
 function page_Special_Forum()
@@ -53,6 +55,7 @@
   if ( getConfig('decir_version') != ENANO_DECIR_VERSION || isset($_POST['do_install_finish']) )
   {
     require(DECIR_ROOT . '/install.php');
+    return false;
   }
   
   $act = strtolower( ( $n = $paths->getParam(0) ) ? $n : 'Index' );
@@ -77,6 +80,12 @@
     case 'new':
       require('posting.php');
       break;
+    case 'edit':
+      require('edit.php');
+      break;
+    case 'delete':
+      require('delete.php');
+      break;
   }
   
   chdir($curdir);
@@ -85,7 +94,13 @@
 
 function page_Admin_DecirGeneral()
 {
-  global $db, $session, $paths, $template, $plugins; if($session->auth_level < USER_LEVEL_ADMIN || $session->user_level < USER_LEVEL_ADMIN) { header('Location: '.makeUrl($paths->nslist['Special'].'Administration'.urlSeparator.'noheaders')); die('Hacking attempt'); }
+  global $db, $session, $paths, $template, $plugins; // Common objects
+  if ( $session->auth_level < USER_LEVEL_ADMIN || $session->user_level < USER_LEVEL_ADMIN )
+  {
+    echo '<h3>Error: Not authenticated</h3><p>It looks like your administration session is invalid or you are not authorized to access this administration page. Please <a href="' . makeUrlNS('Special', 'Login/' . $paths->nslist['Special'] . 'Administration', 'level=' . USER_LEVEL_ADMIN, true) . '">re-authenticate</a> to continue.</p>';
+    return;
+  }
+  
   echo 'Hello world!';
 }