First commit! Woohoo! Basic status of things is in extended description.
authorDan
Tue, 11 Dec 2007 02:03:54 -0500
changeset 0 a09fb41e48d5
child 1 6e76ca311f2d
First commit! Woohoo! Basic status of things is in extended description. * You can write blog posts and view them in a standard blog-like index page * All blogs are per-user and blogs can be made public or private * Setting permissions on a per-blog/per-post basis should work except for some inheritance issues * Planets are not in any way implemented * Tagging posts does not work * Basically the only thing you can do is write and display posts and make comments. It's basic, but it works. More to come very soon!
plugins/Nuggie.php
plugins/nuggie/install.php
plugins/nuggie/postbit.php
plugins/nuggie/schema.sql
plugins/nuggie/style.css
plugins/nuggie/usercp.php
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/Nuggie.php	Tue Dec 11 02:03:54 2007 -0500
@@ -0,0 +1,141 @@
+<?php
+/*
+Plugin Name: Nuggie
+Plugin URI: http://enanocms.org/Nuggie
+Description: Nuggie provides a complete blogging suite for Enano-based websites. Named after Scottish water sprites.
+Author: Dan Fuhry
+Version: 0.1
+Author URI: http://enanocms.org/
+*/
+
+/*
+ * Nuggie
+ * Version 0.1
+ * Copyright (C) 2007 Dan Fuhry
+ *
+ * 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.
+ */
+
+global $db, $session, $paths, $template, $plugins; // Common objects
+
+if ( getConfig('nuggie_installed') != '1' )
+{
+  $plugins->attachHook('base_classes_initted', '
+      $paths->add_page(Array(
+        \'name\'=>\'Install Nuggie\',
+        \'urlname\'=>\'NuggieInstall\',
+        \'namespace\'=>\'Special\',
+        \'special\'=>0,\'visible\'=>0,\'comments_on\'=>0,\'protected\'=>1,\'delvotes\'=>0,\'delvote_ips\'=>\'\',
+        ));
+    ');
+  require( ENANO_ROOT . '/plugins/nuggie/install.php' );
+}
+
+$plugins->attachHook('base_classes_initted', '
+    list($page_id, $namespace) = RenderMan::strToPageId($paths->get_pageid_from_url());
+    
+    if ( $page_id == "Preferences" && $namespace == "Special" )
+    {
+      require( ENANO_ROOT . "/plugins/nuggie/usercp.php" );
+    }
+  ');
+
+$plugins->attachHook('acl_rule_init', 'nuggie_namespace_setup();');
+
+function nuggie_namespace_setup()
+{
+  global $db, $session, $paths, $template, $plugins; // Common objects
+  
+  // Insert additional namespaces
+  
+  $paths->create_namespace('Blog', 'Blog:');
+  $paths->create_namespace('Planet', 'Planet:');
+  
+  // Create custom permissions for Nuggie
+  
+  $session->register_acl_type('nuggie_post', AUTH_DISALLOW, 'Post blog entries or create blog', Array(), 'Blog');
+  $session->register_acl_type('nuggie_edit_own', AUTH_DISALLOW, 'Edit own blog posts', Array(), 'Blog');
+  $session->register_acl_type('nuggie_edit_other', AUTH_DISALLOW, 'Edit others\' blog posts', Array(), 'Blog');
+  $session->register_acl_type('nuggie_create_planet', AUTH_DISALLOW, 'Create new planets', Array(), 'Planet');
+  $session->register_acl_type('nuggie_publicize_planet', AUTH_DISALLOW, 'Make own planets searchable', Array('nuggie_create_planet'), 'Planet');
+  $session->register_acl_type('nuggie_protect_planet', AUTH_DISALLOW, 'Protect planets from public modification', Array(), 'Planet');
+  $session->register_acl_type('nuggie_edit_planet_own', AUTH_DISALLOW, 'Edit own planets', Array(), 'Planet');
+  $session->register_acl_type('nuggie_edit_planet_other', AUTH_DISALLOW, 'Edit others\' planets', Array(), 'Planet');
+  $session->register_acl_type('nuggie_even_when_protected', AUTH_DISALLOW, 'Override protection on editing planets', Array(), 'Planet');
+  $session->register_acl_type('nuggie_see_non_public', AUTH_DISALLOW, 'See non-public blogs', Array(), 'Blog');
+  
+  // Extend the core permission set
+  
+  $session->acl_extend_scope('read', 'Blog|Planet', $paths);
+  $session->acl_extend_scope('edit_comments', 'Blog', $paths);
+  $session->acl_extend_scope('post_comments', 'Blog', $paths);
+  $session->acl_extend_scope('mod_comments', 'Blog', $paths);
+}
+
+$plugins->attachHook('page_type_string_set', 'nuggie_set_page_string();');
+
+function nuggie_set_page_string()
+{
+  global $db, $session, $paths, $template, $plugins; // Common objects
+  
+  if ( $paths->namespace == 'Blog' )
+  {
+    $paths->cpage['comments_on'] = 0;
+    $template->namespace_string = 'blog';
+    if ( strstr($paths->cpage['urlname_nons'], '/') )
+    {
+      $paths->cpage['comments_on'] = 1;
+      $template->namespace_string = 'blog post';
+    }
+  }
+  else if ( $paths->namespace == 'Planet' )
+  {
+    $paths->cpage['comments_on'] = 0;
+    $template->namespace_string = 'planet';
+  }
+}
+
+$plugins->attachHook('page_not_found', 'nuggie_handle_namespace($this);');
+
+function nuggie_handle_namespace($processor)
+{
+  global $db, $session, $paths, $template, $plugins; // Common objects
+  
+  if ( $processor->namespace == 'Blog' )
+  {
+    require( ENANO_ROOT . '/plugins/nuggie/postbit.php' );
+    $result = nuggie_blog_uri_handler($processor->page_id);
+    if ( $result === '_err_access_denied' )
+    {
+      $processor->err_access_denied();
+      return true;
+    }
+  }
+  else if ( $processor->namespace == 'Planet' )
+  {
+    $result = nuggie_planet_uri_handler($processor->page_id);
+    if ( $result === '_err_access_denied' )
+    {
+      $processor->err_access_denied();
+      return true;
+    }
+  }
+}
+
+/**
+ * Sanitizes a string for a Nuggie permalink.
+ * @param string String to sanitize
+ * @return string
+ */
+
+function nuggie_sanitize_title($title)
+{
+  // Placeholder for now - this may become more elaborate in the future, we'll see
+  return sanitize_page_id($title);
+}
+
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/nuggie/install.php	Tue Dec 11 02:03:54 2007 -0500
@@ -0,0 +1,368 @@
+<?php
+
+/*
+ * Nuggie
+ * Version 0.1
+ * Copyright (C) 2007 Dan Fuhry
+ *
+ * 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.
+ */
+
+function page_Special_NuggieInstall()
+{
+  global $db, $session, $paths, $template, $plugins; // Common objects
+  
+  if ( getConfig('nuggie_installed') == '1' )
+  {
+    die_friendly('Nuggie already installed', '<p>Nuggie is already installed - you can\'t reinstall it from here. To upgrade a Nuggie installation, use the upgrade tool.</p>');
+  }
+  
+  if ( $session->auth_level < USER_LEVEL_ADMIN )
+  {
+    redirect(makeUrlNS('Special', 'Login/' . $paths->page, 'level=' . USER_LEVEL_ADMIN), 'Login required', 'You need to be an administrator with elevated auth to install Nuggie. You will now be transferred to the login page.');
+    exit;
+  }
+  
+  $mode = ( $x = $paths->getParam(0) ) ? $x : 'welcome';
+  switch ( $mode )
+  {
+    default:
+      die_friendly('Invalid action', '<p>Invalid installer action</p>');
+      break;
+    case 'welcome':
+      $template->header();
+      $q = $db->sql_query('SELECT group_id, group_name FROM ' . table_prefix . 'groups ORDER BY group_name ASC;');
+      if ( !$q )
+        $db->_die('plugins/nuggie/install.php selecting group information');
+      $groups = array();
+      while ( (list($group_id, $group_name) = $db->fetchrow_num()) )
+      {
+        $groups[$group_id] = $group_name;
+      }
+      ?>
+      <script type="text/javascript">
+        function nuggie_click(value)
+        {
+          var theform = document.forms['nuggieinstall'];
+          if ( !theform )
+            return false;
+          switch(value)
+          {
+            case 'everyone':
+            case 'noone':
+              $('nuggieinstall_use_group').object.style.display = 'none';
+              $('nuggieinstall_create_group').object.style.display = 'none';
+              break;
+            case 'use_group':
+              $('nuggieinstall_use_group').object.style.display = 'block';
+              $('nuggieinstall_create_group').object.style.display = 'none';
+              break;
+            case 'create_group':
+              $('nuggieinstall_use_group').object.style.display = 'none';
+              $('nuggieinstall_create_group').object.style.display = 'block';
+              break;
+          }
+        }
+      </script>
+      <?php
+      echo '<form action="' . makeUrlNS('Special', 'NuggieInstall/install_base') . '" method="post" name="nuggieinstall">';
+      echo '<h3>Welcome to Nuggie - the only blogging engine you\'ll ever need.</h3>';
+      echo '<p>Before you can start blogging, we\'ll need to perform a couple of short steps to set up Nuggie on your server. Since
+               you\'re running Nuggie on top of Enano, you won\'t need to re-enter database information &ndash; we just need to create a
+               few extra tables in your database.</p>';
+      echo '<p>To get started, who would you like to give posting abilities to?</p>';
+      echo '<p><label><input onclick="nuggie_click(this.value);" type="radio" name="blog_perms" value="everyone" checked="checked" /> Let everybody with an account create their own blog</label></p>';
+      echo '<p><label><input onclick="nuggie_click(this.value);" type="radio" name="blog_perms" value="use_group" /> Only people in the following group can have blogs:</label></p>';
+      echo '<p id="nuggieinstall_use_group" style="display: none; margin-left: 46px;"><select name="use_group_id">';
+      foreach ( $groups as $group_id => $group_name )
+      {
+        echo "<option value=\"$group_id\">" . htmlspecialchars($group_name) . "</option>";
+      }
+      echo '</select></p>';
+      echo '<p><label><input onclick="nuggie_click(this.value);" type="radio" name="blog_perms" value="create_group" /> Create a new group and only allow people in that group to have a blog:</label></p>';
+      echo '<p id="nuggieinstall_create_group" style="display: none; margin-left: 46px;">Group name: <input type="text" name="create_group_name" size="30" /><br />
+              <small>You\'ll be added to this group automatically.</small>
+            </p>';
+      echo '<p><label><input onclick="nuggie_click(this.value);" type="radio" name="blog_perms" value="noone" /> Don\'t allow anyone to have a blog yet - I\'ll set up permissions myself. <small>(advanced)</small></label></p>';
+      echo '<p style="text-align: center;"><button><big>Next &raquo;</big></button></p>';
+      echo '</form>';
+      $template->footer();
+      break;
+    case 'install_base':
+      if ( !file_exists( ENANO_ROOT . '/plugins/nuggie/schema.sql' ) )
+      {
+        die_friendly('Can\'t load schema file', '<p>Can\'t find the schema.sql file that should be in /plugins/nuggie. Check your Nuggie setup.</p>');
+      }
+      $schema = @file_get_contents( ENANO_ROOT . '/plugins/nuggie/schema.sql' );
+      if ( empty($schema) )
+      {
+        die_friendly('Can\'t load schema file', '<p>Can\'t read the schema.sql file that should be in /plugins/nuggie. Check your file permissions.</p>');
+      }
+      
+      if ( !isset($_POST['blog_perms']) )
+        die('Missing essential form field');
+      
+      if ( !in_array($_POST['blog_perms'], array('everyone', 'use_group', 'create_group', 'noone')) )
+        die('You tried to hack the form');
+      
+      if ( $_POST['blog_perms'] == 'use_group' && strval(intval($_POST['use_group_id'])) !== $_POST['use_group_id'] )
+        die('You tried to hack the form');
+      
+      if ( $_POST['blog_perms'] == 'create_group' && !isset($_POST['create_group_name']) )
+        die('You tried to hack the form');
+      
+      //
+      // PARSE SCHEMA
+      //
+      
+      // Step 1: remove comments and blank lines
+      $schema = str_replace("\r", '', $schema);
+      $schema = explode("\n", $schema);
+      foreach ( $schema as $i => $_ )
+      {
+        $line =& $schema[$i];
+        $line = preg_replace('/--(.*)$/', '', $line);
+        $line = trim($line);
+        if ( empty($line) )
+          unset($schema[$i]);
+      }
+      $schema = array_values($schema);
+      
+      // Step 2: Split into separate queries
+      
+      $queries = array('');
+      $query =& $queries[0];
+      foreach ( $schema as $line )
+      {
+        if ( preg_match('/;$/', $line) )
+        {
+          $query .= "\n  $line";
+          $queries[] = '';
+          unset($query);
+          $query =& $queries[count($queries) - 1];
+        }
+        else
+        {
+          $query .= "\n  $line";
+        }
+      }
+      unset($query);
+      foreach ( $queries as $i => $query )
+      {
+        $query = trim($query);
+        if ( empty($query) )
+          unset($queries[$i]);
+        else
+          $queries[$i] = $query;
+      }
+      $schema = array_values($queries);
+      unset($queries, $query, $i);
+      
+      // Step 3: Assign variables
+      
+      foreach ( $schema as $i => $_ )
+      {
+        $sql =& $schema[$i];
+        $sql = str_replace('{{TABLE_PREFIX}}', table_prefix, $sql);
+        unset($sql);
+      }
+      unset($sql);
+
+      // Step 4: Check queries
+      foreach ( $schema as $sql )
+      {
+        if ( !$db->check_query($sql) )
+        {
+          die_friendly('Error during installation', '<p>DBAL rejected query citing syntax errors. This is probably a bug.</p>');
+        }
+      }
+      
+      // echo '<pre>' . htmlspecialchars(print_r($schema, true)) . '</pre>';
+      
+      // Step 5: Install
+      foreach ( $schema as $sql )
+      {
+        if ( !$db->sql_query($sql) )
+        {
+          $db->_die('Nuggie during mainstream installation');
+        }
+      }
+      
+      $template->header(true);
+      echo '<h3>Base install complete</h3>';
+      echo '<p>The base install has completed. Please click Next to continue with the setup of ACL rules.</p>';
+      echo '<form action="' . makeUrlNS('Special', 'NuggieInstall/install_acl') . '" method="post">';
+      $group_name = htmlspecialchars($_POST['create_group_name']);
+      $group_name = str_replace('"', '&quot;', $group_name);
+      // This is SAFE! It's verified against a whitelist
+      echo '<input type="hidden" name="blog_perms" value="' . $_POST['blog_perms'] . '" />';
+      echo "<input type=\"hidden\" name=\"use_group_id\" value=\"{$_POST['use_group_id']}\" />";
+      echo "<input type=\"hidden\" name=\"create_group_name\" value=\"{$group_name}\" />";
+      echo '<p style="text-align: center;"><button><big>Next &raquo;</big></button></p>';
+      echo '</form>';
+      $template->footer(true);
+      
+      break;
+    case 'install_acl':
+      
+      if ( !isset($_POST['blog_perms']) )
+        die('Missing essential form field');
+      
+      if ( !in_array($_POST['blog_perms'], array('everyone', 'use_group', 'create_group', 'noone')) )
+        die('You tried to hack the form');
+      
+      if ( $_POST['blog_perms'] == 'use_group' && strval(intval($_POST['use_group_id'])) !== $_POST['use_group_id'] )
+        die('You tried to hack the form');
+      
+      if ( $_POST['blog_perms'] == 'create_group' && !isset($_POST['create_group_name']) )
+        die('You tried to hack the form');
+      
+      switch ( $_POST['blog_perms'] )
+      {
+        case 'everyone':
+          $q = $db->sql_query('SELECT rules,rule_id FROM ' . table_prefix . 'acl WHERE target_type = ' . ACL_TYPE_GROUP . ' AND target_id = 1 AND page_id IS NULL AND namespace IS NULL;');
+          if ( !$q )
+            $db->_die('Nuggie installer selecting existing ACL rules');
+          if ( $db->numrows() < 1 )
+          {
+            // The rule doesn't exist, create it
+            $rule = $session->perm_to_string(array(
+                'nuggie_post' => AUTH_ALLOW,
+                'nuggie_edit_own' => AUTH_ALLOW,
+                'nuggie_edit_other' => AUTH_DISALLOW,
+                'nuggie_create_planet' => AUTH_ALLOW,
+                'nuggie_publicize_planet' => AUTH_WIKIMODE,
+                'nuggie_protect_planet' => AUTH_DISALLOW,
+                'nuggie_edit_planet_own' => AUTH_ALLOW,
+                'nuggie_edit_planet_other' => AUTH_DISALLOW,
+                'nuggie_even_when_protected' => AUTH_DISALLOW,
+                'nuggie_see_non_public' => AUTH_DISALLOW
+              ));
+            $q = $db->sql_query('INSERT INTO ' . table_prefix . 'acl(rules, target_type, target_id, page_id, namespace)' .
+                              "\n  VALUES( '$rule', " . ACL_TYPE_GROUP . ", 1, NULL, NULL );");
+            if ( !$q )
+              $db->_die('Nuggie installer setting up permissions');
+          }
+          else
+          {
+            list($rule, $rule_id) = $db->fetchrow_num();
+            $rule = $session->string_to_perm($rule);
+            $rule = $session->acl_merge_complete($rule, array(
+                'nuggie_post' => AUTH_ALLOW,
+                'nuggie_edit_own' => AUTH_ALLOW,
+                'nuggie_edit_other' => AUTH_DISALLOW,
+                'nuggie_create_planet' => AUTH_ALLOW,
+                'nuggie_publicize_planet' => AUTH_WIKIMODE,
+                'nuggie_protect_planet' => AUTH_DISALLOW,
+                'nuggie_edit_planet_own' => AUTH_ALLOW,
+                'nuggie_edit_planet_other' => AUTH_DISALLOW,
+                'nuggie_even_when_protected' => AUTH_DISALLOW,
+                'nuggie_see_non_public' => AUTH_DISALLOW
+              ));
+            $rule = $session->perm_to_string($rule);
+            $q = $db->sql_query('UPDATE ' . table_prefix . 'acl' .
+                              "\n  SET rules='$rule'\n"
+                              . "     WHERE rule_id = $rule_id;");
+            if ( !$q )
+              $db->_die('Nuggie installer setting up permissions');
+          }
+          break;
+        case "create_group":
+          $group_name = $db->escape($_POST['create_group_name']);
+          
+          $q = $db->sql_query('INSERT INTO ' . table_prefix . "groups ( group_name ) VALUES ( '$group_name' );");
+          if ( !$q )
+            $db->_die('Nuggie installer creating group');
+          
+          $group_id = $db->insert_id();
+          $q = $db->sql_query('INSERT INTO ' . table_prefix . "group_members( group_id, user_id ) VALUES ( $group_id, {$session->user_id} );");
+          if ( !$q )
+            $db->_die('Nuggie installer adding user to new group');
+          
+        case "use_group":
+          if ( !isset($group_id) )
+          {
+            $group_id = intval($_POST['use_group_id']);
+            $q = $db->sql_query('SELECT group_name, group_id FROM ' . table_prefix . "groups WHERE group_id = $group_id;");
+            if ( !$q )
+              $db->_die('Nuggie installer determining group information');
+            if ( $db->numrows() < 1 )
+              die('Hacking attempt');
+            list($group_name, $group_id) = $db->fetchrow_num();
+          }
+          
+          $q = $db->sql_query('SELECT rules,rule_id FROM ' . table_prefix . 'acl WHERE target_type = ' . ACL_TYPE_GROUP . " AND target_id = $group_id AND page_id IS NULL AND namespace IS NULL;");
+          if ( !$q )
+            $db->_die('Nuggie installer selecting existing ACL rules');
+          if ( $db->numrows() < 1 )
+          {
+            // The rule doesn't exist, create it
+            $rule = $session->perm_to_string(array(
+                'nuggie_post' => AUTH_ALLOW,
+                'nuggie_edit_own' => AUTH_ALLOW,
+                'nuggie_edit_other' => AUTH_DISALLOW,
+                'nuggie_create_planet' => AUTH_ALLOW,
+                'nuggie_publicize_planet' => AUTH_WIKIMODE,
+                'nuggie_protect_planet' => AUTH_DISALLOW,
+                'nuggie_edit_planet_own' => AUTH_ALLOW,
+                'nuggie_edit_planet_other' => AUTH_DISALLOW,
+                'nuggie_even_when_protected' => AUTH_DISALLOW,
+                'nuggie_see_non_public' => AUTH_DISALLOW
+              ));
+            $q = $db->sql_query('INSERT INTO ' . table_prefix . 'acl(rules, target_type, target_id, page_id, namespace)' .
+                              "\n  VALUES( '$rule', " . ACL_TYPE_GROUP . ", $group_id, NULL, NULL );");
+            if ( !$q )
+              $db->_die('Nuggie installer setting up permissions');
+          }
+          else
+          {
+            list($rule, $rule_id) = $db->fetchrow_num();
+            $rule = $session->string_to_perm($rule);
+            $rule = $session->acl_merge_complete($rule, array(
+                'nuggie_post' => AUTH_ALLOW,
+                'nuggie_edit_own' => AUTH_ALLOW,
+                'nuggie_edit_other' => AUTH_DISALLOW,
+                'nuggie_create_planet' => AUTH_ALLOW,
+                'nuggie_publicize_planet' => AUTH_WIKIMODE,
+                'nuggie_protect_planet' => AUTH_DISALLOW,
+                'nuggie_edit_planet_own' => AUTH_ALLOW,
+                'nuggie_edit_planet_other' => AUTH_DISALLOW,
+                'nuggie_even_when_protected' => AUTH_DISALLOW,
+                'nuggie_see_non_public' => AUTH_DISALLOW
+              ));
+            $rule = $session->perm_to_string($rule);
+            $q = $db->sql_query('UPDATE ' . table_prefix . 'acl' .
+                              "\n  SET rules='$rule'\n"
+                              . "     WHERE rule_id = $rule_id;");
+            if ( !$q )
+              $db->_die('Nuggie installer setting up permissions');
+          }
+          
+          break;
+        case "noone":
+          // Don't touch permissions, let the webmaster handle it
+          break;
+        default:
+          die('PHP = douche bag');
+          break;
+      }
+      
+      // Mark it as installed to prevent installer module from loading
+      setConfig('nuggie_installed', '1');
+      
+      $template->header(true);
+      echo '<h3>Nuggie has been installed.</h3>';
+      echo '<p>You\'ve successfully installed Nuggie. Congratulations!</p>';
+      echo '<form action="' . makeUrlNS('Special', 'Preferences/Blog') . '" method="post">';
+      echo '<p style="text-align: center;"><big><button>Start blogging &raquo;</button></big>';
+      echo '</form>';
+      $template->footer(true);
+      
+      break;
+  }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/nuggie/postbit.php	Tue Dec 11 02:03:54 2007 -0500
@@ -0,0 +1,409 @@
+<?php
+
+/*
+ * Nuggie
+ * Version 0.1
+ * Copyright (C) 2007 Dan Fuhry
+ *
+ * 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.
+ */
+
+/**
+ * Class for displaying a Nuggie blog post.
+ * @package Enano
+ * @subpackage Nuggie
+ */
+
+class NuggiePostbit
+{
+  /**
+   * The unique row ID of the post. This can be false if
+   * the post is being displayed as part of a preview or
+   * otherwise doesn't actually exist in the database.
+   * @var int
+   */
+  
+  var $post_id = false;
+  
+  /**
+   * The title of the post.
+   * @var string
+   */
+  
+  var $post_title;
+  
+  /**
+   * The cleaned title of the post. This is calculated
+   * internally and need not be set.
+   * @var string
+   */
+  
+  var $post_title_clean;
+  
+  /**
+   * Who wrote this post (user ID).
+   * @var int
+   */
+  
+  var $post_author = 1;
+  
+  /**
+   * When the post was posted. UNIX timestamp.
+   * @var int
+   */
+  
+  var $post_timestamp = 1;
+  
+  /**
+   * The actual content of the post.
+   * @var string
+   */
+  
+  var $post_text = '';
+  
+  /**
+   * Whether the user can edit the post or not.
+   * @var bool
+   */
+  
+  var $auth_edit = false;
+  
+  /**
+   * The number of comments on the post
+   * @var int
+   */
+  
+  var $num_comments = 0;
+  
+  /**
+   * The master permission set for the blog. Only used during pagination, don't worry about this
+   * @var object
+   */
+  
+  var $blog_perms;
+  
+  /**
+   * Renders the post.
+   */
+  
+  function render_post()
+  {
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    
+    if ( file_exists( ENANO_ROOT . "/themes/{$template->theme_id}/blog_post.tpl" ) )
+    {
+      $parser = $template->makeParser('blog_post.tpl');
+    }
+    else
+    {
+      $tpl_code = <<<TPLBLOCK
+
+      <!-- Start of blog post -->
+      
+      <div class="blog-post">
+        <div class="blog-post-header">
+          <div class="blog-post-datemark">
+            {DATE_D} {DATE_j}<br />
+            {DATE_M} {DATE_Y}
+          </div>
+          <h3><a href="{PERMALINK}">{POST_TITLE}</a></h3>
+          <div class="blog-post-author">
+            Posted by {USER_LINK} on {TIMESTAMP}
+          </div>
+          <div class="blog-post-buttons">
+          <a href="{PERMALINK}#do:comments" onclick="ajaxComments();">{COMMENT_STRING}</a>
+          <!-- BEGIN auth_edit -->
+          &bull;
+          <a href="{EDIT_LINK}">Edit this post</a>
+          <!-- END auth_edit -->
+          </div>
+        </div>
+        <div class="blog-post-body">
+          {POST_TEXT}
+        </div>
+      </div>
+      
+      <!-- Finish blog post -->
+      
+TPLBLOCK;
+      $parser = $template->makeParserText($tpl_code);
+    }
+    
+    $this->post_title_clean = nuggie_sanitize_title($this->post_title);
+    
+    // List of valid characters for date()
+    $date_chars = 'dDjlNSwzWFmMntLoYyaABgGhHiseIOTZcrU';
+    $date_chars = enano_str_split($date_chars);
+    
+    $strings = array();
+    foreach ( $date_chars as $char )
+    {
+      $strings["DATE_$char"] = date($char, $this->post_timestamp);
+    }
+    
+    $strings['POST_TITLE'] = htmlspecialchars($this->post_title);
+    $strings['POST_TEXT'] = RenderMan::render($this->post_text);
+    $strings['PERMALINK'] = makeUrlNS('Blog', $this->post_author . date('/Y/n/j/', $this->post_timestamp) . $this->post_title_clean, false, true);
+    $strings['EDIT_LINK'] = makeUrlNS('Special', "Preferences/Blog/Write/{$this->post_id}", false, true);
+    
+    if ( $this->num_comments == 0 )
+      $comment_string = 'No comments';
+    else if ( $this->num_comments == 1 )
+      $comment_string = '1 comment';
+    else
+      $comment_string = intval($this->num_comments) . ' comments';
+      
+    $strings['COMMENT_STRING'] = $comment_string;
+    $strings['TIMESTAMP'] = date('l, F j, Y \a\t h:i <\s\m\a\l\l>A</\s\m\a\l\l>', $this->post_timestamp);
+    
+    $parser->assign_vars($strings);
+    $parser->assign_bool(array(
+        'auth_edit' => ( $this->auth_edit )
+      ));
+    
+    return $parser->run();
+  }
+  /**
+   * Don't worry about this, it's only called from the paginator.
+   * @access private
+   */
+   
+  function paginate_handler($_, $row)
+  {
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    
+    if ( !is_object($this->blog_perms) )
+    {
+      $this->blog_perms = $session->fetch_page_acl($row['username'], 'Blog');
+    }
+    
+    $perms = $session->fetch_page_acl("{$row['post_timestamp']}_{$row['post_id']}", 'Blog');
+    $perms->perms = $session->acl_merge($this->blog_perms->perms, $perms->perms);
+    
+    /*
+    if ( !$perms->get_permissions('read') )
+    {
+      return "POST {$this->post_id} DENIED";
+    }
+    */
+    
+    $this->post_id = intval($row['post_id']);
+    $this->post_title = $row['post_title'];
+    $this->post_text = $row['post_text'];
+    $this->post_author = $row['username'];
+    $this->post_timestamp = intval($row['post_timestamp']);
+    $this->num_comments = intval($row['num_comments']);
+    
+    return $this->render_post();
+  }
+}
+
+function nuggie_blog_uri_handler($uri)
+{
+  global $db, $session, $paths, $template, $plugins; // Common objects
+  $template->add_header('<link rel="stylesheet" type="text/css" href="' . scriptPath . '/plugins/nuggie/style.css" />');
+  if ( strstr($uri, '/') )
+  {
+    //
+    // Permalinked post
+    //
+    
+    // Split and parse URI
+    $particles = explode('/', $uri);
+    if ( count($particles) < 5 )
+      return false;
+    $sz = count($particles);
+    for ( $i = 5; $i < $sz; $i++ )
+    {
+      $particles[4] .= '/' . $particles[$i];
+      unset($particles[$i]);
+    }
+    
+    $particles[4] = nuggie_sanitize_title($particles[4]);
+    $poster =& $particles[0];
+    $year =& $particles[1];
+    $month =& $particles[2];
+    $day =& $particles[3];
+    $post_title_clean =& $particles[4];
+    
+    $particlecomp = $db->escape(implode('/', $particles));
+    
+    $year = intval($year);
+    $month = intval($month);
+    $day = intval($day);
+    
+    $time_min = mktime(0, 0, 0, $month, $day, $year);
+    $time_max = $time_min + 86400;
+    
+    $ptc = $db->escape($post_title_clean);
+    $uname = $db->escape(dirtify_page_id($poster));
+    
+    $q = $db->sql_query("SELECT p.post_id, p.post_title, p.post_title_clean, p.post_author, p.post_timestamp, p.post_text, b.blog_name,\n"
+                      . "       b.blog_subtitle, b.blog_type, b.allowed_users, u.username, u.user_level, COUNT(c.comment_id) AS num_comments\n"
+                      . "      FROM " . table_prefix . "blog_posts AS p\n"
+                      . "  LEFT JOIN " . table_prefix . "blogs AS b\n"
+                      . "    ON ( b.user_id = p.post_author )\n"
+                      . "  LEFT JOIN " . table_prefix . "users AS u\n"
+                      . "    ON ( u.user_id = p.post_author )\n"
+                      . "  LEFT JOIN " . table_prefix . "comments AS c\n"
+                      . "    ON ( ( c.page_id = '{$particlecomp}' AND c.namespace = 'Blog' ) OR ( c.page_id IS NULL AND c.namespace IS NULL ) )\n"
+                      . "  WHERE p.post_timestamp >= $time_min AND p.post_timestamp <= $time_max\n"
+                      . "    AND p.post_title_clean = '$ptc' AND u.username = '$uname'\n"
+                      . "  GROUP BY p.post_id;");
+    if ( !$q )
+      $db->_die('Nuggie post handler selecting main post data');
+    
+    if ( $db->numrows() < 1 )
+      return false;
+    
+    if ( $db->numrows() > 1 )
+    {
+      die_friendly('Ambiguous blog posts', '<p>FIXME: You have two posts with the same title posted on the same day by the same user. I was
+                                               not able to distinguish which post you wish to view.</p>');
+    }
+    
+    $row = $db->fetchrow();
+    
+    //
+    // Determine permissions
+    //
+    
+    // The way we're doing this is first fetching permissions for the blog, and then merging them
+    // with permissions specific to the post. This way the admin can set custom permissions for the
+    // entire blog, and they'll be inherited unless individual posts have overriding permissions.
+    $perms_blog = $session->fetch_page_acl($row['username'], 'Blog');
+    $perms = $session->fetch_page_acl("{$row['post_timestamp']}_{$row['post_id']}", 'Blog');
+    $perms->perms = $session->acl_merge($perms->perms, $perms_post->perms);
+    unset($perms_blog);
+    
+    if ( $row['blog_type'] == 'private' )
+    {
+      $allowed_users = unserialize($row['allowed_users']);
+      if ( !in_array($session->username, $allowed_users) && !$perms->get_permissions('nuggie_see_non_public') && $row['username'] != $session->username )
+      {
+        return '_err_access_denied';
+      }
+    }
+    
+    $acl_type = ( $row['post_author'] == $session->user_id ) ? 'nuggie_edit_own' : 'nuggie_edit_other';
+    
+    if ( !$perms->get_permissions('read') )
+      return '_err_access_denied';
+    
+    // We're validated - display post
+    $postbit = new NuggiePostbit();
+    $postbit->post_id = intval($row['post_id']);
+    $postbit->post_title = $row['post_title'];
+    $postbit->post_text = $row['post_text'];
+    $postbit->post_author = $row['username'];
+    $postbit->post_timestamp = intval($row['post_timestamp']);
+    $postbit->auth_edit = $perms->get_permissions($acl_type);
+    $postbit->num_comments = intval($row['num_comments']);
+    
+    $template->tpl_strings['PAGE_NAME'] = htmlspecialchars($row['post_title']) . ' &laquo; ' . htmlspecialchars($row['blog_name']);
+    
+    $template->header();
+    echo '&lt; <a href="' . makeUrlNS('Blog', $row['username']) . '">' . htmlspecialchars($row['blog_name']) . '</a>';
+    echo $postbit->render_post();
+    $template->footer();
+    
+    return true;
+  }
+  else
+  {
+    return nuggie_blog_index($uri);
+  }
+}
+
+function nuggie_blog_index($username)
+{
+  global $db, $session, $paths, $template, $plugins; // Common objects
+  
+  $username_esc = $db->escape($username);
+  
+  // First look for the user's blog so we can get permissions
+  $q = $db->sql_query('SELECT b.blog_type, b.allowed_users, b.user_id, b.blog_name, b.blog_subtitle FROM ' . table_prefix . "blogs AS b LEFT JOIN " . table_prefix . "users AS u ON ( u.user_id = b.user_id ) WHERE u.username = '$username_esc';");
+  if ( !$q )
+    $db->_die('Nuggie main blog page doing preliminary security check');
+  
+  if ( $db->numrows() < 1 )
+    return false;
+  
+  list($blog_type, $allowed_users, $user_id, $blog_name, $blog_subtitle) = $db->fetchrow_num();
+  $db->free_result();
+  
+  $perms = $session->fetch_page_acl($username, 'Blog');
+  
+  if ( $blog_type == 'private' )
+  {
+    $allowed_users = unserialize($allowed_users);
+    if ( !in_array($session->username, $allowed_users) && !$perms->get_permissions('nuggie_see_non_public') && $username != $session->username )
+    {
+      return '_err_access_denied';
+    }
+  }
+  
+  // Determine number of posts and prefetch ACL info
+  $q = $db->sql_query('SELECT post_timestamp, post_id FROM ' . table_prefix . 'blog_posts WHERE post_author = ' . $user_id . ' AND post_published = 1;');
+  if ( !$q )
+    $db->_die('Nuggie main blog page doing rowcount of blog posts');
+  
+  $count = $db->numrows();
+  
+  while ( $row = $db->fetchrow($q) )
+  {
+    $session->fetch_page_acl("{$row['post_timestamp']}_{$row['post_id']}", 'Blog');
+  }
+  
+  $db->free_result($q);
+  
+  $q = $db->sql_unbuffered_query("SELECT p.post_id, p.post_title, p.post_title_clean, p.post_author, p.post_timestamp, p.post_text, b.blog_name,\n"
+                    . "       b.blog_subtitle, u.username, u.user_level, COUNT(c.comment_id) AS num_comments\n"
+                    . "      FROM " . table_prefix . "blog_posts AS p\n"
+                    . "  LEFT JOIN " . table_prefix . "blogs AS b\n"
+                    . "    ON ( b.user_id = p.post_author )\n"
+                    . "  LEFT JOIN " . table_prefix . "users AS u\n"
+                    . "    ON ( u.user_id = p.post_author )\n"
+                    . "  LEFT JOIN " . table_prefix . "comments AS c\n"
+                    . "    ON ( ( c.page_id REGEXP CONCAT('([0-9]+)/([0-9]+)/([0-9]+)/', p.post_title_clean) AND c.namespace = 'Blog' ) OR ( c.page_id IS NULL AND c.namespace IS NULL ) )\n"
+                    . "  WHERE p.post_author = $user_id AND p.post_published = 1\n"
+                    . "  GROUP BY p.post_id\n"
+                    . "  ORDER BY p.post_timestamp DESC;");
+  if ( !$q )
+    $db->_die('Nuggie main blog page selecting the whole shebang');
+    
+  if ( $count < 1 )
+  {
+    // Either the user hasn't created a blog yet, or he isn't even registered
+    return false;
+  }
+  
+  $template->tpl_strings['PAGE_NAME'] = htmlspecialchars($blog_name) . ' &raquo; ' . htmlspecialchars($blog_subtitle);
+  
+  $postbit = new NuggiePostbit();
+  // $q, $tpl_text, $num_results, $result_url, $start = 0, $perpage = 10, $callers = Array(), $header = '', $footer = ''
+  $html = paginate(
+      $q,
+      '{post_id}',
+      $count,
+      makeUrlNS('Blog', $username, "start=%s", true),
+      0,
+      10,
+      array( 'post_id' => array($postbit, 'paginate_handler') ),
+      '<span class="menuclear"></span>'
+    );
+  
+  $template->header();
+  
+  echo $html;
+  
+  $template->footer();
+  
+  return true;
+}
+
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/nuggie/schema.sql	Tue Dec 11 02:03:54 2007 -0500
@@ -0,0 +1,41 @@
+-- Nuggie
+-- Version 0.1
+-- Copyright (C) 2007 Dan Fuhry
+
+-- 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.
+
+CREATE TABLE {{TABLE_PREFIX}}blogs(
+  blog_id int(12) NOT NULL auto_increment,
+  blog_name varchar(255) NOT NULL,
+  blog_subtitle text NOT NULL,
+  user_id int(12) NOT NULL,
+  blog_type ENUM('private', 'public') NOT NULL DEFAULT 'public',
+  allowed_users text,
+  PRIMARY KEY ( blog_id )
+) ENGINE = MyISAM CHARACTER SET utf8 COLLATE utf8_bin;
+
+CREATE TABLE {{TABLE_PREFIX}}planets(
+  planet_id smallint(6) NOT NULL auto_increment,
+  planet_name varchar(255) NOT NULL,
+  planet_subtitle text NOT NULL,
+  planet_creator int(12) NOT NULL DEFAULT 1,
+  planet_public tinyint(1) NOT NULL DEFAULT 0,
+  planet_visible tinyint(1) NOT NULL DEFAULT 1,
+  PRIMARY KEY ( planet_id )
+) ENGINE = MyISAM CHARACTER SET utf8 COLLATE utf8_bin;
+
+CREATE TABLE {{TABLE_PREFIX}}blog_posts(
+  post_id int(15) NOT NULL auto_increment,
+  post_title text NOT NULL,
+  post_title_clean text NOT NULL,
+  post_author int(12) NOT NULL DEFAULT 1,
+  post_text longtext NOT NULL,
+  post_timestamp int(32) NOT NULL DEFAULT 0,
+  post_published tinyint(1) NOT NULL DEFAULT 0,
+  PRIMARY KEY ( post_id )
+) ENGINE = MyISAM CHARACTER SET utf8 COLLATE utf8_bin;
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/nuggie/style.css	Tue Dec 11 02:03:54 2007 -0500
@@ -0,0 +1,32 @@
+div.blog-post-header {
+  border-bottom: 1px solid #C0C0C0;
+  margin: 0 0 10px 0;
+  padding: 0 0 3px 0;
+}
+div.blog-post-header div.blog-post-datemark {
+  float: right;
+  text-align: center;
+  font-weight: bold;
+  color: #505050;
+  background-color: #E0E0E0;
+  font-size: 12pt;
+  padding: 7px;
+}
+div.blog-post-header h3 {
+  font-size: 16pt;
+  font-family: myriad pro, verdana, arial, helvetica, sans-serif;
+  font-weight: normal;
+  margin-bottom: 4px;
+}
+div.blog-post-header h3 a {
+  color: #202020;
+  text-decoration: none;
+  border-bottom: 1px dotted #C0C0C0;
+}
+div.blog-post-header h3 a:hover {
+  border-bottom: 1px solid #808080;
+  color: #305070;
+}
+div.blog-post-body {
+  clear: both;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/nuggie/usercp.php	Tue Dec 11 02:03:54 2007 -0500
@@ -0,0 +1,462 @@
+<?php
+
+/*
+ * Nuggie
+ * Version 0.1
+ * Copyright (C) 2007 Dan Fuhry
+ *
+ * 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.
+ */
+
+function nuggie_user_cp($section)
+{
+  global $db, $session, $paths, $template, $plugins; // Common objects
+  if ( $section != 'Blog' )
+    return false;
+  
+  if ( getConfig('nuggie_installed') != '1' )
+  {
+    echo '<h3>Nuggie not installed</h3>';
+    echo '<p>It looks like Nuggie isn\'t installed yet. You\'ll need to <a href="' . makeUrlNS('Special', 'NuggieInstall') . '">install Nuggie</a> before you can do anything more.</p>';
+    return true;
+  }
+  
+  $subsection = $paths->getParam(1);
+  $initted = true;
+  
+  $q = $db->sql_query('SELECT blog_id, blog_name, blog_subtitle, blog_type, allowed_users FROM ' . table_prefix . "blogs WHERE user_id = {$session->user_id};");
+  if ( !$q )
+    $db->_die('Nuggie User CP selecting blog info');
+  
+  if ( $db->numrows() < 1 )
+  {
+    $subsection = 'Settings';
+    $initted = false;
+  }
+  
+  list(, $blog_name, $blog_desc, $blog_type, $allowed_users) = $db->fetchrow_num($q);
+  
+  switch($subsection)
+  {
+    case false:
+    case 'Home':
+      echo 'module Home';
+      break;
+    case 'Settings':
+      
+      switch ( isset($_POST['do_save']) )
+      {
+        // We're doing this so we can break out if we need to (if form validation fails)
+        case true:
+          
+          $errors = array();
+          
+          $blog_name     = trim($_POST['blog_name']);
+          $blog_desc     = trim($_POST['blog_desc']);
+          $blog_access   = trim($_POST['blog_access']);
+          $allowed_users = $_POST['allowed_users'];
+          
+          if ( empty($blog_name) )
+            $errors[] = 'Please enter a name for your blog.';
+          
+          if ( !in_array($blog_access, array('public', 'private')) )
+            $errors[] = 'Hacking attempt on blog_access: must be one of public, private.';
+          
+          if ( count($allowed_users) > 500 )
+            $errors[] = 'You\'re asking that an unreasonable number of users have access to this blog. If you really have that many readers, you may want to ask the administrator of this site to make a usergroup with read access to your blog.';
+          
+          if ( count($allowed_users) < 1 && $blog_access == 'private' )
+            $errors[] = 'Please enter at least one username that will have access to your blog. Note that your account always has access to your blog.';
+          
+          if ( $blog_access == 'public' )
+          {
+            $allowed_users = 'NULL';
+          }
+          else
+          {
+            if ( is_array($allowed_users) && count($errors) < 1 )
+            {
+              $allowed_users = array_values(array_unique($allowed_users));
+              foreach ( $allowed_users as $i => $_ )
+              {
+                if ( empty( $allowed_users[$i] ) )
+                {
+                  unset($allowed_users[$i]);
+                }
+                else
+                {
+                  $allowed_users[$i] = $db->escape($allowed_users[$i]);
+                }
+              }
+              $fragment = "username='" . implode("' OR username='", $allowed_users) . "'";
+              $e = $db->sql_query('SELECT COUNT(username) AS num_valid FROM ' . table_prefix . "users WHERE $fragment;");
+              if ( !$e )
+                $db->_die('Nuggie user CP validating usernames');
+              
+              $row = $db->fetchrow();
+              if ( intval($row['num_valid']) != count($allowed_users) )
+                $errors[] = 'One or more of the usernames you entered does not exist.';
+            }
+            else
+            {
+              $errors[] = 'Invalid datatype on allowed_users.';
+            }
+          }
+          
+          if ( count($errors) > 0 )
+          {
+            $initted = true;
+            echo '<div class="error-box" style="margin: 0 0 10px 0">
+                    <b>The following problems prevented your blog settings from being saved:</b>
+                    <ul>
+                      <li>
+                        ' . implode("</li>\n                      <li>", $errors) . '
+                      </li>
+                    </ul>
+                  </div>';
+            break;
+          }
+          else
+          {
+            // Save changes
+            
+            if ( !is_string($allowed_users) )
+              $allowed_users = "'" . $db->escape( serialize($allowed_users) ) . "'";
+            
+            $blog_name = $db->escape($blog_name);
+            $blog_desc = $db->escape($blog_desc);
+            
+            if ( $initted )
+            {
+              $sql = 'UPDATE ' . table_prefix . "blogs SET blog_name = '$blog_name', blog_subtitle = '$blog_desc', blog_type = '$blog_access', allowed_users = $allowed_users;";
+            }
+            else
+            {
+              $sql = 'INSERT INTO ' . table_prefix . 'blogs(blog_name, blog_subtitle, blog_type, allowed_users, user_id)' .
+                     "\n  VALUES ( '$blog_name', '$blog_desc', '$blog_access', $allowed_users, {$session->user_id} );";
+            }
+            
+            if ( $db->sql_query($sql) )
+            {
+              echo '<div class="info-box" style="margin: 0 0 10px 0;">' .
+                      ( $initted ? 'Your changes have been saved.' : 'Your blog has been created; you can now
+                        <a href="' . makeUrlNS('Special', 'Preferences/Blog/Write', false, true) . '">start writing some posts</a> and
+                        then <a href="' . makeUrlNS('Blog', $session->username, false, true) . '">view your blog</a>.' )
+                 . '</div>';
+            }
+            else
+            {
+              $db->_die('Nuggie user CP saving settings');
+            }
+            
+            // Re-select the blog data
+            $db->free_result($q);
+            
+            $q = $db->sql_query('SELECT blog_id, blog_name, blog_subtitle, blog_type, allowed_users FROM ' . table_prefix . "blogs WHERE user_id = {$session->user_id};");
+            if ( !$q )
+              $db->_die('Nuggie User CP selecting blog info');
+            
+            list(, $blog_name, $blog_desc, $blog_type, $allowed_users) = $db->fetchrow_num($q);
+          }
+          
+          $initted = true;
+      }
+      
+      if ( !$initted )
+      {
+        echo '<div class="error-box" style="margin: 0 0 10px 0;">
+                <b>It looks like your blog isn\'t set up yet.</b><br />
+                You\'ll need to set up your blog by entering some basic information here before you can write any posts.
+              </div>';
+        $blog_name = htmlspecialchars($session->username) . "'s blog";
+        $blog_desc = '';
+      }
+      else
+      {
+        $blog_name = htmlspecialchars(strtr($blog_name, array('"' => '&quot;')));
+        $blog_desc = htmlspecialchars(strtr($blog_desc, array('"' => '&quot;')));
+      }
+      
+      if ( !isset($blog_type) )
+        $blog_type = 'public';
+      
+      if ( !isset($allowed_users) )
+        $allowed_users = serialize(array());
+      
+      $form_action = makeUrlNS('Special', 'Preferences/Blog/Settings', false, true);
+      echo "<form action=\"$form_action\" method=\"post\" enctype=\"multipart/form-data\">";
+      
+      ?>
+      <div class="tblholder">
+        <table border="0" cellspacing="1" cellpadding="4">
+          <tr>
+            <th colspan="2">
+              <?php echo ( $initted ) ? 'Manage blog settings' : 'Create blog'; ?>
+            </th>
+          </tr>
+          <tr>
+            <td class="row2">
+              Blog name:
+            </td>
+            <td class="row1">
+              <input type="text" name="blog_name" size="60" value="<?php echo $blog_name; ?>" tabindex="1" />
+            </td>
+          </tr>
+          <tr>
+            <td class="row2">
+              Blog description:<br />
+              <small>You're best off keeping this short and sweet.</small>
+            </td>
+            <td class="row1">
+              <input type="text" name="blog_desc" size="60" value="<?php echo $blog_desc; ?>" tabindex="2" />
+            </td>
+          </tr>
+          <tr>
+            <td class="row2">
+              Blog access:
+            </td>
+            <td class="row1">
+              <label><input onclick="$('nuggie_allowed_users').object.style.display='none';"  tabindex="3" type="radio" name="blog_access" value="public"<?php echo ( $blog_type == 'public' ) ? ' checked="checked"' : ''; ?> /> Let everyone read my blog</label><br />
+              <label><input onclick="$('nuggie_allowed_users').object.style.display='block';" tabindex="4" type="radio" name="blog_access" value="private"<?php echo ( $blog_type == 'private' ) ? ' checked="checked"' : ''; ?> /> Only allow the users I list below</label><br />
+              <small style="margin-left: 33px;">Administrators can always read all blogs, including private ones.</small>
+              <div id="nuggie_allowed_users"<?php echo ( $blog_type == 'public' ) ? ' style="display: none;"' : ''; ?>>
+                <?php
+                if ( $initted )
+                {
+                  $allowed_users = unserialize($allowed_users);
+                  foreach ( $allowed_users as $user )
+                  {
+                    echo '<input type="text" name="allowed_users[]" tabindex="5" value="' . $user . '" size="25" style="margin-bottom: 5px;" onkeyup="new AutofillUsername(this);" /><br />';
+                  }
+                  echo '<input type="text" name="allowed_users[]" tabindex="5" value="" size="25" style="margin-bottom: 5px;" onkeyup="new AutofillUsername(this);" /><br />';
+                }
+                else
+                {
+                  ?>
+                  <input type="text" name="allowed_users[]" tabindex="5" value="" size="25" style="margin-bottom: 5px;" onkeyup="new AutofillUsername(this);" /><br />
+                  <input type="text" name="allowed_users[]" tabindex="5" value="" size="25" style="margin-bottom: 5px;" onkeyup="new AutofillUsername(this);" /><br />
+                  <input type="text" name="allowed_users[]" tabindex="5" value="" size="25" style="margin-bottom: 5px;" onkeyup="new AutofillUsername(this);" /><br />
+                  <input type="text" name="allowed_users[]" tabindex="5" value="" size="25" style="margin-bottom: 5px;" onkeyup="new AutofillUsername(this);" /><br />
+                  <input type="text" name="allowed_users[]" tabindex="5" value="" size="25" style="margin-bottom: 5px;" onkeyup="new AutofillUsername(this);" /><br />
+                  <?php
+                }
+                ?>
+                <input type="button" tabindex="6" onclick="var x = document.createElement('input'); x.tabindex = '5'; x.onkeyup = function() { new AutofillUsername(this); }; x.size='25'; x.style.marginBottom='5px'; x.type='text'; x.name='allowed_users[]'; $('nuggie_allowed_users').object.insertBefore(x, this); $('nuggie_allowed_users').object.insertBefore(document.createElement('br'), this); x.focus();" value="+ Add another" />
+              </div>
+            </td>
+          </tr>
+          <tr>
+            <th class="subhead" colspan="2">
+              <input tabindex="7" type="submit" name="do_save" value="<?php echo ( $initted ) ? 'Save changes' : 'Create my blog &raquo;' ?>" />
+            </th>
+          </tr>
+        </table>
+      </div>
+      <?php
+      
+      echo '</form>';
+      
+      break;
+    case 'Posts':
+      echo 'module Posts';
+      break;
+    case 'Write':
+      
+      $post_text = '';
+      $post_title = 'Post title';
+      
+      $post_id = $paths->getParam(2);
+      if ( isset($_POST['post_id']) )
+      {
+        $post_id = $_POST['post_id'];
+      }
+      if ( $post_id )
+      {
+        /*
+         * FIXME: Validate blog public/private status before sending text
+         * FIXME: Avoid ambiguous post_title_cleans through appending numbers when needed
+         */
+        
+        $post_id = intval($post_id);
+        $q = $db->sql_query('SELECT p.post_id, p.post_title, p.post_title_clean, p.post_author, p.post_text, p.post_timestamp, u.username ' 
+                            . 'FROM ' . table_prefix . 'blog_posts AS p'
+                            . '  LEFT JOIN ' . table_prefix . 'users AS u'
+                            . '    ON ( p.post_author = u.user_id )'
+                            . '  WHERE post_id = ' . $post_id . ';');
+        
+        if ( !$q )
+          $db->_die('Nuggie user CP obtaining post info');
+        
+        if ( $db->numrows() > 0 )
+        {
+          $row = $db->fetchrow();
+          if ( $session->user_id != $row['post_author'] )
+          {
+            // We have a possible security issue on our hands - the user is trying
+            // to edit someone else's post. Verify read and write permissions.
+            $post_page_id = "{$row['post_timestamp']}_{$row['post_id']}";
+            $perms = $session->fetch_page_acl($post_page_id, 'Blog');
+            if ( !$perms->get_permissions('read') || !$perms->get_permissions('nuggie_edit_other') )
+            {
+              echo '<h3>Post editing error</h3>';
+              echo '<p>You do not have permission to edit this blog post.</p>';
+              
+              unset($row);
+              unset($row);
+              
+              $db->free_result();
+              // Break out of this entire user CP module
+              return true;
+            }
+          }
+          else
+          {
+            $post_page_id = "{$row['post_timestamp']}_{$row['post_id']}";
+            $perms = $session->fetch_page_acl($post_page_id, 'Blog');
+            if ( !$perms->get_permissions('nuggie_edit_own') || !$perms->get_permissions('read') )
+            {
+              echo '<h3>Post editing error</h3>';
+              echo '<p>You do not have permission to edit this blog post.</p>';
+              
+              unset($row);
+              unset($row);
+              
+              $db->free_result();
+              // Break out of this entire user CP module
+              return true;
+            }
+          }
+          // We have permission - load post
+          $post_title = $row['post_title'];
+          $post_text = $row['post_text'];
+        }
+      }
+      
+      if ( isset($_POST['submit']) )
+      {
+        switch($_POST['submit'])
+        {
+          case 'save_publish':
+            $publish = '1';
+          case 'save_draft':
+            if ( !isset($publish) )
+              $publish = '0';
+            
+            $save_post_text = $_POST['post_text'];
+            $save_post_title = $db->escape($_POST['post_title']);
+            $save_post_title_clean = $db->escape(nuggie_sanitize_title($_POST['post_title']));
+            
+            $save_post_text = RenderMan::preprocess_text($save_post_text, true, true);
+            
+            if ( $post_id )
+            {
+              $sql = 'UPDATE ' . table_prefix . "blog_posts SET post_title = '$save_post_title', post_title_clean = '$save_post_title_clean', post_text = '$save_post_text', post_published = $publish WHERE post_id = $post_id;";
+            }
+            else
+            {
+              $time = time();
+              $sql = 'INSERT INTO ' . table_prefix . 'blog_posts ( post_title, post_title_clean, post_text, post_author, post_timestamp, post_published ) '
+                      . "VALUES ( '$save_post_title', '$save_post_title_clean', '$save_post_text', {$session->user_id}, $time, $publish );";
+            }
+            
+            if ( $db->sql_query($sql) )
+            {
+              echo '<div class="info-box" style="margin: 0 0 10px 0;">
+                      ' . ( $publish == '1' ? 'Your post has been published.' : 'Your post has been saved.' ) . '
+                    </div>';
+            }
+            else
+            {
+              $db->_die('Nuggie user CP running post-save query');
+            }
+            
+            if ( !$post_id )
+            {
+              $post_id = $db->insert_id();
+            }
+            
+            $post_title = $_POST['post_title'];
+            $post_text = $_POST['post_text'];
+            break;
+          case 'preview':
+            $preview_text = $_POST['post_text'];
+            $preview_text = RenderMan::preprocess_text($preview_text, true, false);
+            $preview_text = RenderMan::render($preview_text);
+            
+            /*
+             * FIXME: Use the real post renderer (when it's ready)
+             */
+            
+            echo '<div style="border: 1px solid #406080; background-color: #F0F0F0; margin: 0 0 10px 0; padding: 10px;
+                              overflow: auto; max-height: 500px; clip: rect(0px, auto, auto, 0px);">';
+            echo '<h2>Post preview</h2>';
+            echo '<p style="color: red;">FIXME: This does not use the real post-display API, which is not yet implemented. Eventually this should look just like a real post.</p>';
+            echo '<h3>' . htmlspecialchars($_POST['post_title']) . '</h3>';
+            echo $preview_text;
+            echo '</div>';
+           
+            $post_title = $_POST['post_title'];
+            $post_text = $_POST['post_text'];
+            break;
+        }
+      }
+      
+      $q = $db->sql_query('SELECT post_id, post_title FROM ' . table_prefix . "blog_posts WHERE post_published = 0 AND post_author = {$session->user_id};");
+      if ( !$q )
+        $db->_die('Nuggie user CP selecting draft posts');
+      if ( $db->numrows() > 0 )
+      {
+        echo '<div class="mdg-infobox" style="margin: 0 0 10px 0;"><b>Your drafts:</b> ';
+        $posts = array();
+        while ( $row = $db->fetchrow() )
+        {
+          $posts[] = '<a href="' . makeUrlNS('Special', "Preferences/Blog/Write/{$row['post_id']}") . '">' . htmlspecialchars($row['post_title']) . '</a>';
+        }
+        echo implode(', ', $posts);
+        echo '</div>';
+      }
+      
+      echo '<form action="' . makeUrlNS('Special', 'Preferences/Blog/Write', false, true) . '" method="post">';
+      
+      $post_text = htmlspecialchars($post_text);
+      $post_title = strtr(htmlspecialchars($post_title), array('"' => '&quot;'));
+      
+      echo '<input type="text" name="post_title" value="' . $post_title . '" style="font-size: 16pt; margin-bottom: 10px; width: 100%;' . ( $post_title == 'Post title' ? ' color: #808080;' : '' ) . '" onfocus="if ( this.value == \'Post title\' ) { this.value = \'\'; this.style.color = null; }" onblur="if ( this.value == \'\' ) { this.value = \'Post title\'; this.style.color = \'#808080\'; } else { this.style.color = null; }" />';
+      echo $template->tinymce_textarea('post_text', $post_text);
+      
+      // Buttons!
+      echo '<div style="margin-top: 10px;">';
+      echo '<button name="submit" value="save_draft">Save draft</button>&nbsp;&nbsp;';
+      echo '<button name="submit" value="preview">Show preview</button>&nbsp;&nbsp;';
+      echo '<button name="submit" value="save_publish">Publish to blog</button>&nbsp;&nbsp;';
+      echo '</div>';
+      
+      if ( $post_id )
+      {
+        echo '<input type="hidden" name="post_id" value="' . $post_id . '" />';
+      }
+      
+      echo '</form>';
+      
+      break;
+    case 'Planets':
+      echo 'module Planets';
+      break;
+    default:
+      return false;
+  }
+  return true;
+}
+
+$plugins->attachHook("userprefs_jbox", "
+    userprefs_menu_add('My blog', 'Manage blog settings', makeUrlNS('Special', 'Preferences/Blog/Settings'));
+    userprefs_menu_add('My blog', 'Manage posts', makeUrlNS('Special', 'Preferences/Blog/Posts'));
+    userprefs_menu_add('My blog', 'Write new post', makeUrlNS('Special', 'Preferences/Blog/Write'));
+    userprefs_menu_add('My blog', 'Manage my planets', makeUrlNS('Special', 'Preferences/Blog/Planets'));
+    \$userprefs_menu_links['My blog'] = makeUrlNS('Blog', \$session->username);
+  ");
+$plugins->attachHook("userprefs_body", "return nuggie_user_cp(\$section);");