Added AJAX file upload support.
authorDan Fuhry <dan@enanocms.org>
Sat, 21 Aug 2010 23:32:06 -0400
changeset 42 7c6e2e97aa08
parent 41 0944c9354e9c
child 43 7df4993be0b0
Added AJAX file upload support.
plugins/Gallery.php
plugins/gallery/browser.php
plugins/gallery/canvas.js
plugins/gallery/dropdown.css
plugins/gallery/fetcher.php
plugins/gallery/functions.php
plugins/gallery/gallery-bits.js
plugins/gallery/imagetag.php
plugins/gallery/nssetup.php
plugins/gallery/search.php
plugins/gallery/sidebar.php
plugins/gallery/tagging.js
plugins/gallery/upload.php
plugins/gallery/viewimage.php
--- a/plugins/Gallery.php	Sat Aug 21 23:25:41 2010 -0400
+++ b/plugins/Gallery.php	Sat Aug 21 23:32:06 2010 -0400
@@ -1,13 +1,13 @@
 <?php
 /**!info**
 {
-  "Plugin Name"  : "Snapr",
-  "Plugin URI"   : "http://enanocms.org/plugin/snapr",
-  "Description"  : "Provides an intuitive image gallery system with a browser, viewer for individual images, upload interface, and comment system integration.",
-  "Author"       : "Dan Fuhry",
-  "Version"      : "0.1b3",
-  "Author URI"   : "http://enanocms.org/",
-  "Version list" : ['0.1b1', '0.1b2', '0.1 beta 3', '0.1b3']
+	"Plugin Name"  : "Snapr",
+	"Plugin URI"   : "http://enanocms.org/plugin/snapr",
+	"Description"  : "Provides an intuitive image gallery system with a browser, viewer for individual images, upload interface, and comment system integration.",
+	"Author"       : "Dan Fuhry",
+	"Version"      : "0.1b4",
+	"Author URI"   : "http://enanocms.org/",
+	"Version list" : ['0.1b1', '0.1b2', '0.1 beta 3', '0.1b3', '0.1b4']
 }
 **!*/
 
@@ -17,36 +17,38 @@
 
 if ( !defined('ENANO_ATLEAST_1_1') )
 {
-  $fn = basename(__FILE__);
-  setConfig("plugin_$fn", '0');
-  die_semicritical('Snapr can\'t load on this site', '<p>This version of Snapr requires Enano 1.1.6 or later.</p>');
+	$fn = basename(__FILE__);
+	setConfig("plugin_$fn", '0');
+	die_semicritical('Snapr can\'t load on this site', '<p>This version of Snapr requires Enano 1.1.6 or later.</p>');
 }
 
 $magick_path = getConfig('imagemagick_path', '/usr/bin/convert');
 $have_gd_scale_support = function_exists('imagecreatetruecolor') && function_exists('imagejpeg') && function_exists('imagecopyresampled');
 if ( (!file_exists($magick_path) || !is_executable($magick_path)) && !$have_gd_scale_support )
 {
-  $fn = basename(__FILE__);
-  // set disabled flag with new plugin system
-  if ( defined('ENANO_ATLEAST_1_1') && defined('PLUGIN_DISABLED') )
-  {
-    $q = $db->sql_query('UPDATE ' . table_prefix . "plugins SET plugin_flags = plugin_flags | " . PLUGIN_DISABLED . " WHERE plugin_filename = 'Gallery.php';");
-    if ( !$q )
-      $db->_die();
+	$fn = basename(__FILE__);
+	// set disabled flag with new plugin system
+	if ( defined('ENANO_ATLEAST_1_1') && defined('PLUGIN_DISABLED') )
+	{
+		$q = $db->sql_query('UPDATE ' . table_prefix . "plugins SET plugin_flags = plugin_flags | " . PLUGIN_DISABLED . " WHERE plugin_filename = 'Gallery.php';");
+		if ( !$q )
+			$db->_die();
 	  
-    // kill off cache
-    global $cache;
-    $cache->purge('plugins');
-  }
-  else
-  {
-    // old plugin system
-    setConfig("plugin_$fn", '0');
-  }
-  
-  die_semicritical('Snapr can\'t load on this site', '<p>You must have ImageMagick or GD installed and working to use this plugin. The plugin has been disabled, please setup ImageMagick and then re-enable it.</p>');
+		// kill off cache
+		global $cache;
+		$cache->purge('plugins');
+	}
+	else
+	{
+		// old plugin system
+		setConfig("plugin_$fn", '0');
+	}
+	
+	die_semicritical('Snapr can\'t load on this site', '<p>You must have ImageMagick or GD installed and working to use this plugin. The plugin has been disabled, please setup ImageMagick and then re-enable it.</p>');
 }
 
+$plugins->attachHook('pgsql_set_serial_list', '$primary_keys[table_prefix."gallery"] = "img_id";');
+
 /**!install dbms="mysql"; **
 
 CREATE TABLE {{TABLE_PREFIX}}gallery(
@@ -59,6 +61,7 @@
 	img_filename varchar(255) NOT NULL,
 	img_time_upload int(12) NOT NULL DEFAULT 0,
 	img_time_mod int(12) NOT NULL DEFAULT 0,
+	img_author int(12) NOT NULL DEFAULT 1,
 	img_tags longtext,
 	PRIMARY KEY ( img_id )
 );
@@ -91,6 +94,14 @@
 /**!upgrade dbms="mysql"; from="0.1 beta 3"; to="0.1b3"; **
 **!*/
 
+/**!upgrade dbms="mysql"; from="0.1b3"; to="0.1b4"; **
+ALTER TABLE {{TABLE_PREFIX}}gallery ADD COLUMN img_author int(12) NOT NULL DEFAULT 1;
+ALTER TABLE {{TABLE_PREFIX}}gallery ADD COLUMN processed tinyint(1) NOT NULL DEFAULT 1;
+-- Set all images to authorship by the first administrator we can find
+UPDATE {{TABLE_PREFIX}}gallery SET img_author = ( SELECT user_id FROM {{TABLE_PREFIX}}users WHERE user_level = 9 ORDER BY user_id DESC LIMIT 1 ), processed = 1;
+
+**!*/
+
 require( ENANO_ROOT . '/plugins/gallery/functions.php' );
 require( ENANO_ROOT . '/plugins/gallery/nssetup.php' );
 require( ENANO_ROOT . '/plugins/gallery/viewimage.php' );
--- a/plugins/gallery/browser.php	Sat Aug 21 23:25:41 2010 -0400
+++ b/plugins/gallery/browser.php	Sat Aug 21 23:32:06 2010 -0400
@@ -27,446 +27,447 @@
  
 class SnaprFormatter
 {
-  
-  /**
-   * Main render method, called from pagination function
-   * @access private
-   */
-  
-  function render($column_crap, $row, $row_crap = false)
-  {
-    global $db, $session, $paths, $template, $plugins; // Common objects
-    
-    $out = '<li class="snapr-icon">';
-    
-    $title_safe = $row['img_title'];
-    $title_safe = htmlspecialchars($title_safe);
-    
-    if ( $row['is_folder'] == 1 )
-    {
-      // It's a folder, show the icon
-      $f_url_particle = sanitize_page_id($row['img_title']);
-      $f_url_particle = htmlspecialchars($f_url_particle);
-      $image_link = makeUrl( $paths->fullpage . '/' . $f_url_particle );
-      $image_url = scriptPath . '/plugins/gallery/folder.png';
-    }
-    else
-    {
-      // It's an image, show a thumbnail
-      $image_link = makeUrlNS('Gallery', $row['img_id']);
-      $image_url  = makeUrlNS('Special', 'GalleryFetcher/thumb/' . $row['img_id']);
-    }
-    
-    if ( isset($row['score']) )
-    {
-      $row['score'] = number_format($row['score'], 2);
-    }
-    
-    $image_url_js = addslashes($image_link);
-    $jsclick = ( $session->user_level < USER_LEVEL_ADMIN ) ? ' onclick="window.location=\'' . $image_url_js . '\'"' : '';
-    
-    $out .= '<div class="gallery_icon"' . $jsclick . '>';
-    
-    $out .= '<a class="snapr-imagelink" href="' . $image_link . '"><img alt="&lt;Thumbnail&gt;" class="gallery_thumb" src="' . $image_url . '" /></a>';
-    
-    if ( $session->user_level < USER_LEVEL_ADMIN )
-    {
-      $out .= '<span class="snapr-icon-label">' . $title_safe . ( isset($row['score']) ? "<br /><small>Relevance: {$row['score']}</small>" : '' ) . '</span>';
-    }
-    else if ( $session->user_level >= USER_LEVEL_ADMIN )
-    {
-      $out .= '<div class="menu_nojs snapr-icon-label" style="text-align: center;"><a href="#" onclick="return false;" style="width: 74px;">' . $title_safe . ( isset($row['score']) ? "<br /><small>Relevance: {$row['score']}</small>" : '' ) . '</a>';
-      
-      $url_delete = makeUrlNS('Special', 'GalleryUpload', 'rm=' . $row['img_id'], true);
-      $url_edit   = makeUrlNS('Special', 'GalleryUpload', 'edit_img=' . $row['img_id'], true);
-      
-      // Tools menu
-      $out .= '<ul style="text-align: left;">';
-      $out .= '<li><a href="' . $url_delete . '">Delete ' . ( $row['is_folder'] == 1 ? 'this folder and all contents' : 'this image' ) . '</a></li>';
-      $out .= '<li><a href="' . $url_edit   . '">Rename, move, or edit description</a></li>';
-      $out .= '</ul>';
-      $out .= '</div>';
-      $out .= '<span class="menuclear"></span>';
-    }
-    
-    $out .= '  </div>';
-    
-    $out .= '</li>';
-    
-    return $out;
-  }
-  
+	
+	/**
+ 	* Main render method, called from pagination function
+ 	* @access private
+ 	*/
+	
+	function render($column_crap, $row, $row_crap = false)
+	{
+		global $db, $session, $paths, $template, $plugins; // Common objects
+		
+		$out = '<li class="snapr-icon">';
+		
+		$title_safe = $row['img_title'];
+		$title_safe = htmlspecialchars($title_safe);
+		
+		if ( $row['is_folder'] == 1 )
+		{
+			// It's a folder, show the icon
+			$f_url_particle = sanitize_page_id($row['img_title']);
+			$f_url_particle = htmlspecialchars($f_url_particle);
+			$image_link = makeUrl( $paths->fullpage . '/' . $f_url_particle );
+			$image_url = scriptPath . '/plugins/gallery/folder.png';
+		}
+		else
+		{
+			// It's an image, show a thumbnail
+			$image_link = makeUrlNS('Gallery', $row['img_id']);
+			$image_url  = makeUrlNS('Special', 'GalleryFetcher/thumb/' . $row['img_id']);
+		}
+		
+		if ( isset($row['score']) )
+		{
+			$row['score'] = number_format($row['score'], 2);
+		}
+		
+		$image_url_js = addslashes($image_link);
+		$jsclick = ( $session->user_level < USER_LEVEL_ADMIN ) ? ' onclick="window.location=\'' . $image_url_js . '\'"' : '';
+		
+		$out .= '<div class="gallery_icon"' . $jsclick . '>';
+		
+		$out .= '<a class="snapr-imagelink" href="' . $image_link . '"><img alt="&lt;Thumbnail&gt;" class="gallery_thumb" src="' . $image_url . '" /></a>';
+		
+		if ( $session->user_level < USER_LEVEL_ADMIN )
+		{
+			$out .= '<span class="snapr-icon-label">' . $title_safe . ( isset($row['score']) ? "<br /><small>Relevance: {$row['score']}</small>" : '' ) . '</span>';
+		}
+		else if ( $session->user_level >= USER_LEVEL_ADMIN )
+		{
+			$out .= '<div class="menu_nojs snapr-icon-label" style="text-align: center;"><a href="#" onclick="return false;" style="width: 74px;">' . $title_safe . ( isset($row['score']) ? "<br /><small>Relevance: {$row['score']}</small>" : '' ) . '</a>';
+			
+			$url_delete = makeUrlNS('Special', 'GalleryUpload', 'rm=' . $row['img_id'], true);
+			$url_edit   = makeUrlNS('Special', 'GalleryUpload', 'edit_img=' . $row['img_id'], true);
+			
+			// Tools menu
+			$out .= '<ul style="text-align: left;">';
+			$out .= '<li><a href="' . $url_delete . '">Delete ' . ( $row['is_folder'] == 1 ? 'this folder and all contents' : 'this image' ) . '</a></li>';
+			$out .= '<li><a href="' . $url_edit   . '">Rename, move, or edit description</a></li>';
+			$out .= '</ul>';
+			$out .= '</div>';
+			$out .= '<span class="menuclear"></span>';
+		}
+		
+		$out .= '  </div>';
+		
+		$out .= '</li>';
+		
+		return $out;
+	}
+	
 }
 
 function page_Special_Gallery()
 {
-  global $db, $session, $paths, $template, $plugins; // Common objects
-  
-  // die('<pre>' . print_r(gallery_folder_hierarchy(), true) . '</pre>');
-  
-  $sort_column = ( isset($_GET['sort'])  && in_array($_GET['sort'],  array('img_title', 'img_time_upload', 'img_time_mod')) ) ? $_GET['sort'] : 'img_title';
-  $sort_order  = ( isset($_GET['order']) && in_array($_GET['order'], array('ASC', 'DESC')) ) ? $_GET['order'] : 'ASC';
-  
-  // Determine number of pictures per page
-  $template->load_theme();
-  
-  $where = 'WHERE folder_parent IS NULL ' . "\n  ORDER BY is_folder DESC, $sort_column $sort_order, img_title ASC";
-  $parms = $paths->getAllParams();
-  
-  $sql = "SELECT img_id, img_title, is_folder, 'NULL' AS folder_id FROM ".table_prefix."gallery $where;";
-  
-  // Breadcrumb browser
-  $breadcrumbs = array();
-  $breadcrumbs[] = '<a href="' . makeUrlNS('Special', 'Gallery') . '">Gallery index</a>';
-  
-  $breadcrumb_urlcache = '';
-  
-  // CSS for gallery browser
-  // Moved to search.php
-  //$template->add_header('<link rel="stylesheet" href="' . scriptPath . '/plugins/gallery/browser.css" type="text/css" />');
-  //$template->add_header('<link rel="stylesheet" href="' . scriptPath . '/plugins/gallery/dropdown.css" type="text/css" />');
-  
-  $header = $template->getHeader();
-  
-  if ( !empty($parms) )
-  {
-    $parms = dirtify_page_id($parms);
-    if ( strstr($parms, '/') )
-    {
-      $folders = explode('/', $parms);
-    }
-    else
-    {
-      $folders = array(0 => $parms);
-    }
-    foreach ( $folders as $i => $_crap )
-    {
-      $folder =& $folders[$i];
-      
-      $f_url = sanitize_page_id($folder);
-      $breadcrumb_urlcache .= '/' . $f_url;
-      $breadcrumb_url = makeUrlNS('Special', 'Gallery' . $breadcrumb_urlcache);
-      
-      $folder = str_replace('_', ' ', $folder);
-      
-      if ( $i == ( count($folders) - 1 ) )
-      {
-        $breadcrumbs[] = htmlspecialchars($folder);
-      }
-      else
-      {
-        $breadcrumbs[] = '<a href="' . $breadcrumb_url . '">' . htmlspecialchars($folder) . '</a>';
-      }
-    }
-    unset($folder);
-    $folders = array_reverse($folders);
-    // This is one of the best MySQL tricks on the market. We're going to reverse-travel a folder path using LEFT JOIN and the incredible power of metacoded SQL
-    $sql = 'SELECT gm.img_id, gm.img_title, gm.is_folder, g0.img_title AS folder_name, g0.img_id AS folder_id FROM '.table_prefix.'gallery AS gm' . "\n  " . 'LEFT JOIN '.table_prefix.'gallery AS g0' . "\n    " . 'ON ( gm.folder_parent = g0.img_id )';
-    $where = "\n  " . 'WHERE g0.img_title=\'' . $db->escape($folders[0]) . '\'';
-    foreach ( $folders as $i => $folder )
-    {
-      if ( $i == 0 )
-        continue;
-      $i_dec = $i - 1;
-      $folder = $db->escape($folder);
-      $sql .= "\n  LEFT JOIN ".table_prefix."gallery AS g{$i}\n    ON ( g{$i}.img_id=g{$i_dec}.folder_parent AND g{$i}.img_title='$folder' )";
-      $where .= "\n    ".'AND g'.$i.'.img_id IS NOT NULL';
-    }
-    $where .= "\n    AND g{$i}.folder_parent IS NULL";
-    $sql .= $where . "\n  ORDER BY is_folder DESC, gm.$sort_column $sort_order, gm.img_title ASC" . ';';
-  }
-  
-  $img_query = $db->sql_query($sql);
-  if ( !$img_query )
-    $db->_die('The folder ID could not be selected.');
-  
-  if ( $db->numrows() < 1 )
-  {
-    // Nothing in this folder, for one of two reasons:
-    //   1) The folder doesn't exist
-    //   2) The folder exists but doesn't have any images in it
-    
-    if ( sizeof($folders) < 1 )
-    {
-      // Nothing in the root folder
-      
-      $first_row['folder_id'] = 'NULL';
-      if ( $session->user_level >= USER_LEVEL_ADMIN && isset($_POST['create_folder']) && isset($first_row['folder_id']) )
-      {
-        if ( empty($_POST['create_folder']) )
-        {
-          $f_errors[] = 'Please enter a folder name.';
-        }
-        if ( $_POST['create_folder'] == '_id' )
-        {
-          $f_errors[] = 'The name "_id" is reserved for internal functions and cannot be used on any image or folder.';
-        }
-        if ( count($f_errors) < 1 )
-        {
-          $q = $db->sql_query('INSERT INTO '.table_prefix.'gallery(img_title, is_folder, folder_parent) VALUES(\'' . $db->escape($_POST['create_folder']) . '\', 1, ' . $first_row['folder_id'] . ');');
-          if ( !$q )
-            $db->_die();
-          redirect(makeUrl($paths->fullpage), 'Folder created', 'The folder "' . htmlspecialchars($_POST['create_folder']) . '" has been created. Redirecting to last viewed folder...', 2);
-        }
-      }
-      
-      $html = '';
-      if ( $session->user_level >= USER_LEVEL_ADMIN )
-      {
-        $html .= '<p><a href="' . makeUrlNS('Special', 'GalleryUpload') . '">Upload an image</a></p>';
-        $html .= '<div class="select-outer">Create new folder';
-        $html .= '<div class="select-inner" style="padding-top: 4px;">';
-        $html .= '<form action="' . makeUrl($paths->fullpage) . '" method="post">';
-        $html .= '<input type="text" name="create_folder" size="30" /> <input type="submit" value="Create" />';
-        $html .= '</form></div>';
-        $html .= '</div><div class="select-pad">&nbsp;</div><br />';
-      }
-      
-      die_friendly('No images', '<p>No images have been uploaded to the gallery yet.</p>' . $html);
-    }
-    
-    /*
-    $folders_old = $folders;
-    $folders = array(
-      0 => $folders_old[0]
-      );
-    $x = $folders_old;
-    unset($x[0]);
-    $folders = array_merge($folders, $x);
-    unset($x);
-    */
-    // die('<pre>' . print_r($folders, true) . '</pre>');
-    
-    // This next query will try to determine if the folder itself exists
-    $sql = 'SELECT g0.img_id, g0.img_title FROM '.table_prefix.'gallery AS g0';
-    $where = "\n  " . 'WHERE g0.img_title=\'' . $db->escape($folders[0]) . '\'';
-    foreach ( $folders as $i => $folder )
-    {
-      if ( $i == 0 )
-        continue;
-      $i_dec = $i - 1;
-      $folder = $db->escape($folder);
-      $sql .= "\n  LEFT JOIN ".table_prefix."gallery AS g{$i}\n    ON ( g{$i}.img_id=g{$i_dec}.folder_parent AND g{$i}.img_title='$folder' )";
-      $where .= "\n    ".'AND g'.$i.'.img_id IS NOT NULL';
-    }
-    $where .= "\n    AND g{$i}.folder_parent IS NULL";
-    $where .= "\n    AND g0.is_folder=1";
-    $sql .= $where . ';';
-   
-    $nameq = $db->sql_query($sql);
-    if ( !$nameq )
-      $db->_die();
-    
-    if ( $db->numrows($nameq) < 1 )
-    {
-      die_friendly('Folder not found', '<p>The folder you requested doesn\'t exist. Please check the URL and try again, or return to the <a href="' . makeUrlNS('Special', 'Gallery') . '">gallery index</a>.</p>');
-    }
-    
-    $row = $db->fetchrow($nameq);
-    
-    // Generate title
-    $title = dirtify_page_id($row['img_title']);
-    $title = str_replace('_', ' ', $title);
-    $title = htmlspecialchars($title);
-    
-    $template->tpl_strings['PAGE_NAME'] = $title;
-    
-    $first_row = $row;
-    
-    if ( $db->numrows($img_query) > 0 )
-      $db->sql_data_seek(0, $img_query);
-    
-    /* $folders = $folders_old; */
-  }
-  else if ( !empty($parms) )
-  {
-    $row = $db->fetchrow($img_query);
-    $first_row = $row;
-    
-    // Generate title
-    $title = htmlspecialchars($row['folder_name']);
-    
-    $template->tpl_strings['PAGE_NAME'] = $title;
-    
-    $db->sql_data_seek(0, $img_query);
-  }
-  else
-  {
-    $row = $db->fetchrow($img_query);
-    $first_row = $row;
-    
-    $template->tpl_strings['PAGE_NAME'] = 'Image Gallery';
-    $breadcrumbs = array('<b>Gallery index</b>');
-    
-    $db->sql_data_seek(0, $img_query);
-  }
-  
-  $f_errors = array();
-  
-  if ( $session->user_level >= USER_LEVEL_ADMIN && isset($_POST['create_folder']) )
-  {
-    if ( !isset($first_row['folder_id']) )
-    {
-      //die('FALLING<pre>' . print_r($first_row, true) . '</pre>');
-      $first_row['folder_id'] =& $first_row['img_id'];
-    }
-    if ( !isset($first_row['folder_id']) )
-    {
-      $f_errors[] = 'Internal error getting parent folder ID';
-    }
-    if ( empty($_POST['create_folder']) )
-    {
-      $f_errors[] = 'Please enter a folder name.';
-    }
-    if ( $_POST['create_folder'] == '_id' )
-    {
-      $f_errors[] = 'The name "_id" is reserved for internal functions and cannot be used on any image or folder.';
-    }
-    if ( count($f_errors) < 1 )
-    {
-      $q = $db->sql_query('INSERT INTO '.table_prefix.'gallery(img_title, is_folder, folder_parent) VALUES(\'' . $db->escape($_POST['create_folder']) . '\', 1, ' . $first_row['folder_id'] . ');');
-      if ( !$q )
-        $db->_die();
-      redirect(makeUrl($paths->fullpage), 'Folder created', 'The folder "' . htmlspecialchars($_POST['create_folder']) . '" has been created. Redirecting to last viewed folder...', 2);
-    }
-  }
-  
-  echo $header;
-  
-  if ( count($f_errors) > 0 )
-  {
-    echo '<div class="error-box">Error creating folder:<ul><li>' . implode('</li><li>', $f_errors) . '</li></ul></div>';
-  }
-  
-  // From here, this breadcrumb stuff is a piece of... sourdough French bread :-) *smacks lips*
-  echo '<div class="breadcrumbs" style="padding: 4px; margin-bottom: 7px;">';
-  // Upload image link for admins
-  if ( $session->user_level >= USER_LEVEL_ADMIN )
-  {
-    echo '<div style="float: right; font-size: smaller;">';
-    echo '<a href="' . makeUrlNS('Special', 'GalleryUpload') . '">Upload new image(s)</a>';
-    echo '</div>';
-  }
-  // The actual breadcrumbs
-  echo '<small>' . implode(' &raquo; ', $breadcrumbs) . '</small>';
-  echo '</div>';
-  
-  // "Edit all" link
-  if ( $row = $db->fetchrow($img_query) && $session->user_level >= USER_LEVEL_ADMIN )
-  {
-    $img_list = array();
-    $fol_list = array();
-    $all_list = array();
-    do
-    {
-      if ( $row === true && isset($first_row) )
-      {
-        $row = $first_row;
-      }
-        // die('<pre>' . var_dump($row) . $db->sql_backtrace() . '</pre>');
-      if ( !$row['img_id'] )
-        break;
-      $all_list[] = $row['img_id'];
-      if ( $row['is_folder'] == 1 )
-        $fol_list[] = $row['img_id'];
-      else
-        $img_list[] = $row['img_id'];
-    }
-    while ( $row = $db->fetchrow($img_query) );
-    
-    $all_list = implode(',', $all_list);
-    $fol_list = implode(',', $fol_list);
-    $img_list = implode(',', $img_list);
-    
-    if ( !empty($all_list) )
-    {
-      echo '<div style="float: right;">
-              Edit all in this folder: ';
-      if ( !empty($img_list) )
-      {
-        $edit_link = makeUrlNS('Special', 'GalleryUpload', 'edit_img=' . $img_list, true);
-        echo "<a href=\"$edit_link\">images</a> ";
-      }
-      if ( !empty($fol_list) )
-      {
-        $edit_link = makeUrlNS('Special', 'GalleryUpload', 'edit_img=' . $fol_list, true);
-        echo "<a href=\"$edit_link\">folders</a> ";
-      }
-      if ( !empty($img_list) && !empty($fol_list) )
-      {
-        $edit_link = makeUrlNS('Special', 'GalleryUpload', 'edit_img=' . $all_list, true);
-        echo "<a href=\"$edit_link\">both</a> ";
-      }
-      // " Bypass stupid jEdit bug
-      echo '</div>';
-    }
-  }
-  
-  $url_sort_name_asc  = makeUrl($paths->fullpage, 'sort=img_title&order=ASC', true);
-  $url_sort_name_desc = makeUrl($paths->fullpage, 'sort=img_title&order=DESC', true);
-  $url_sort_upl_asc   = makeUrl($paths->fullpage, 'sort=img_time_upload&order=ASC', true);
-  $url_sort_mod_asc   = makeUrl($paths->fullpage, 'sort=img_time_mod&order=ASC', true);
-  $url_sort_upl_desc  = makeUrl($paths->fullpage, 'sort=img_time_upload&order=DESC', true);
-  $url_sort_mod_desc  = makeUrl($paths->fullpage, 'sort=img_time_mod&order=DESC', true);
-  
-  // "Sort by" selector (pure CSS!)
-  echo '<div class="select-outer">
-          <span>Sort by...</span>
-          <div class="select-inner">
-            <a href="' . $url_sort_name_asc  . '">Image title (A-Z) <b>(default)</b></a>
-            <a href="' . $url_sort_name_desc . '">Image title (Z-A)</a>
-            <a href="' . $url_sort_upl_desc  . '">Time first uploaded (newest first)</a>
-            <a href="' . $url_sort_upl_asc   . '">Time first uploaded (oldest first)</a>
-            <a href="' . $url_sort_mod_desc  . '">Date of last modification (newest first)</a>
-            <a href="' . $url_sort_mod_asc   . '">Date of last modification (oldest first)</a>
-          </div>
-        </div>
-        <div class="select-pad">&nbsp;</div>';
-  
-  if ( $db->numrows($img_query) > 0 )
+	global $db, $session, $paths, $template, $plugins; // Common objects
+	
+	// die('<pre>' . print_r(gallery_folder_hierarchy(), true) . '</pre>');
+	
+	$sort_column = ( isset($_GET['sort'])  && in_array($_GET['sort'],  array('img_title', 'img_time_upload', 'img_time_mod')) ) ? $_GET['sort'] : 'img_title';
+	$sort_order  = ( isset($_GET['order']) && in_array($_GET['order'], array('ASC', 'DESC')) ) ? $_GET['order'] : 'ASC';
+	
+	// Determine number of pictures per page
+	$template->load_theme();
+	
+	$where = 'WHERE folder_parent IS NULL ' . "\n  ORDER BY is_folder DESC, $sort_column $sort_order, img_title ASC";
+	$parms = $paths->getAllParams();
+	
+	$sql = "SELECT img_id, img_title, is_folder, 'NULL' AS folder_id FROM ".table_prefix."gallery $where;";
+	
+	// Breadcrumb browser
+	$breadcrumbs = array();
+	$breadcrumbs[] = '<a href="' . makeUrlNS('Special', 'Gallery') . '">Gallery index</a>';
+	
+	$breadcrumb_urlcache = '';
+	
+	// CSS for gallery browser
+	// Moved to search.php
+	//$template->add_header('<link rel="stylesheet" href="' . scriptPath . '/plugins/gallery/browser.css" type="text/css" />');
+	//$template->add_header('<link rel="stylesheet" href="' . scriptPath . '/plugins/gallery/dropdown.css" type="text/css" />');
+	
+	$header = $template->getHeader();
+	
+	$folders = $f_errors = array();
+	if ( !empty($parms) )
+	{
+		$parms = dirtify_page_id($parms);
+		if ( strstr($parms, '/') )
+		{
+			$folders = explode('/', $parms);
+		}
+		else
+		{
+			$folders = array(0 => $parms);
+		}
+		foreach ( $folders as $i => $_crap )
+		{
+			$folder =& $folders[$i];
+			
+			$f_url = sanitize_page_id($folder);
+			$breadcrumb_urlcache .= '/' . $f_url;
+			$breadcrumb_url = makeUrlNS('Special', 'Gallery' . $breadcrumb_urlcache);
+			
+			$folder = str_replace('_', ' ', $folder);
+			
+			if ( $i == ( count($folders) - 1 ) )
+			{
+				$breadcrumbs[] = htmlspecialchars($folder);
+			}
+			else
+			{
+				$breadcrumbs[] = '<a href="' . $breadcrumb_url . '">' . htmlspecialchars($folder) . '</a>';
+			}
+		}
+		unset($folder);
+		$folders = array_reverse($folders);
+		// This is one of the best MySQL tricks on the market. We're going to reverse-travel a folder path using LEFT JOIN and the incredible power of metacoded SQL
+		$sql = 'SELECT gm.img_id, gm.img_title, gm.is_folder, g0.img_title AS folder_name, g0.img_id AS folder_id FROM '.table_prefix.'gallery AS gm' . "\n  " . 'LEFT JOIN '.table_prefix.'gallery AS g0' . "\n    " . 'ON ( gm.folder_parent = g0.img_id )';
+		$where = "\n  " . 'WHERE g0.img_title=\'' . $db->escape($folders[0]) . '\'';
+		foreach ( $folders as $i => $folder )
+		{
+			if ( $i == 0 )
+				continue;
+			$i_dec = $i - 1;
+			$folder = $db->escape($folder);
+			$sql .= "\n  LEFT JOIN ".table_prefix."gallery AS g{$i}\n    ON ( g{$i}.img_id=g{$i_dec}.folder_parent AND g{$i}.img_title='$folder' )";
+			$where .= "\n    ".'AND g'.$i.'.img_id IS NOT NULL';
+		}
+		$where .= "\n    AND g{$i}.folder_parent IS NULL";
+		$sql .= $where . "\n  ORDER BY is_folder DESC, gm.$sort_column $sort_order, gm.img_title ASC" . ';';
+	}
+	
+	$img_query = $db->sql_query($sql);
+	if ( !$img_query )
+		$db->_die('The folder ID could not be selected.');
+	
+	if ( $db->numrows() < 1 )
+	{
+		// Nothing in this folder, for one of two reasons:
+		//   1) The folder doesn't exist
+		//   2) The folder exists but doesn't have any images in it
+		
+		if ( count($folders) < 1 )
+		{
+			// Nothing in the root folder
+			
+			$first_row['folder_id'] = 'NULL';
+			if ( $session->user_level >= USER_LEVEL_ADMIN && isset($_POST['create_folder']) && isset($first_row['folder_id']) )
+			{
+				if ( empty($_POST['create_folder']) )
+				{
+					$f_errors[] = 'Please enter a folder name.';
+				}
+				if ( $_POST['create_folder'] == '_id' )
+				{
+					$f_errors[] = 'The name "_id" is reserved for internal functions and cannot be used on any image or folder.';
+				}
+				if ( count($f_errors) < 1 )
+				{
+					$q = $db->sql_query('INSERT INTO '.table_prefix.'gallery(img_title, is_folder, folder_parent, img_author) VALUES(\'' . $db->escape($_POST['create_folder']) . '\', 1, ' . $first_row['folder_id'] . ', ' . $session->user_id . ');');
+					if ( !$q )
+						$db->_die();
+					redirect(makeUrl($paths->fullpage), 'Folder created', 'The folder "' . htmlspecialchars($_POST['create_folder']) . '" has been created. Redirecting to last viewed folder...', 2);
+				}
+			}
+			
+			$html = '';
+			if ( $session->user_level >= USER_LEVEL_ADMIN )
+			{
+				$html .= '<p><a href="' . makeUrlNS('Special', 'GalleryUpload') . '">Upload an image</a></p>';
+				$html .= '<div class="select-outer">Create new folder';
+				$html .= '<div class="select-inner" style="padding-top: 4px;">';
+				$html .= '<form action="' . makeUrl($paths->fullpage) . '" method="post">';
+				$html .= '<input type="text" name="create_folder" size="30" /> <input type="submit" value="Create" />';
+				$html .= '</form></div>';
+				$html .= '</div><div class="select-pad">&nbsp;</div><br />';
+			}
+			
+			die_friendly('No images', '<p>No images have been uploaded to the gallery yet.</p>' . $html);
+		}
+		
+		/*
+		$folders_old = $folders;
+		$folders = array(
+			0 => $folders_old[0]
+			);
+		$x = $folders_old;
+		unset($x[0]);
+		$folders = array_merge($folders, $x);
+		unset($x);
+		*/
+		// die('<pre>' . print_r($folders, true) . '</pre>');
+		
+		// This next query will try to determine if the folder itself exists
+		$sql = 'SELECT g0.img_id, g0.img_title FROM '.table_prefix.'gallery AS g0';
+		$where = "\n  " . 'WHERE g0.img_title=\'' . $db->escape($folders[0]) . '\'';
+		foreach ( $folders as $i => $folder )
+		{
+			if ( $i == 0 )
+				continue;
+			$i_dec = $i - 1;
+			$folder = $db->escape($folder);
+			$sql .= "\n  LEFT JOIN ".table_prefix."gallery AS g{$i}\n    ON ( g{$i}.img_id=g{$i_dec}.folder_parent AND g{$i}.img_title='$folder' )";
+			$where .= "\n    ".'AND g'.$i.'.img_id IS NOT NULL';
+		}
+		$where .= "\n    AND g{$i}.folder_parent IS NULL";
+		$where .= "\n    AND g0.is_folder=1";
+		$sql .= $where . ';';
+ 	
+		$nameq = $db->sql_query($sql);
+		if ( !$nameq )
+			$db->_die();
+		
+		if ( $db->numrows($nameq) < 1 )
+		{
+			die_friendly('Folder not found', '<p>The folder you requested doesn\'t exist. Please check the URL and try again, or return to the <a href="' . makeUrlNS('Special', 'Gallery') . '">gallery index</a>.</p>');
+		}
+		
+		$row = $db->fetchrow($nameq);
+		
+		// Generate title
+		$title = dirtify_page_id($row['img_title']);
+		$title = str_replace('_', ' ', $title);
+		$title = htmlspecialchars($title);
+		
+		$template->tpl_strings['PAGE_NAME'] = $title;
+		
+		$first_row = $row;
+		
+		if ( $db->numrows($img_query) > 0 )
+			$db->sql_data_seek(0, $img_query);
+		
+		/* $folders = $folders_old; */
+	}
+	else if ( !empty($parms) )
+	{
+		$row = $db->fetchrow($img_query);
+		$first_row = $row;
+		
+		// Generate title
+		$title = htmlspecialchars($row['folder_name']);
+		
+		$template->tpl_strings['PAGE_NAME'] = $title;
+		
+		$db->sql_data_seek(0, $img_query);
+	}
+	else
+	{
+		$row = $db->fetchrow($img_query);
+		$first_row = $row;
+		
+		$template->tpl_strings['PAGE_NAME'] = 'Image Gallery';
+		$breadcrumbs = array('<b>Gallery index</b>');
+		
+		$db->sql_data_seek(0, $img_query);
+	}
+	
+	$f_errors = array();
+	
+	if ( $session->user_level >= USER_LEVEL_ADMIN && isset($_POST['create_folder']) )
+	{
+		if ( !isset($first_row['folder_id']) )
+		{
+			//die('FALLING<pre>' . print_r($first_row, true) . '</pre>');
+			$first_row['folder_id'] =& $first_row['img_id'];
+		}
+		if ( !isset($first_row['folder_id']) )
+		{
+			$f_errors[] = 'Internal error getting parent folder ID';
+		}
+		if ( empty($_POST['create_folder']) )
+		{
+			$f_errors[] = 'Please enter a folder name.';
+		}
+		if ( $_POST['create_folder'] == '_id' )
+		{
+			$f_errors[] = 'The name "_id" is reserved for internal functions and cannot be used on any image or folder.';
+		}
+		if ( count($f_errors) < 1 )
+		{
+			$q = $db->sql_query('INSERT INTO '.table_prefix.'gallery(img_title, is_folder, folder_parent, img_author) VALUES(\'' . $db->escape($_POST['create_folder']) . '\', 1, ' . $first_row['folder_id'] . ', ' . $session->user_id . ');');
+			if ( !$q )
+				$db->_die();
+			redirect(makeUrl($paths->fullpage), 'Folder created', 'The folder "' . htmlspecialchars($_POST['create_folder']) . '" has been created. Redirecting to last viewed folder...', 2);
+		}
+	}
+	
+	echo $header;
+	
+	if ( count($f_errors) > 0 )
+	{
+		echo '<div class="error-box">Error creating folder:<ul><li>' . implode('</li><li>', $f_errors) . '</li></ul></div>';
+	}
+	
+	// From here, this breadcrumb stuff is a piece of... sourdough French bread :-) *smacks lips*
+	echo '<div class="breadcrumbs" style="padding: 4px; margin-bottom: 7px;">';
+	// Upload image link for admins
+	if ( $session->user_level >= USER_LEVEL_ADMIN )
+	{
+		echo '<div style="float: right; font-size: smaller;">';
+		echo '<a href="' . makeUrlNS('Special', 'GalleryUpload') . '">Upload new image(s)</a>';
+		echo '</div>';
+	}
+	// The actual breadcrumbs
+	echo '<small>' . implode(' &raquo; ', $breadcrumbs) . '</small>';
+	echo '</div>';
+	
+	// "Edit all" link
+	if ( $row = $db->fetchrow($img_query) && $session->user_level >= USER_LEVEL_ADMIN )
+	{
+		$img_list = array();
+		$fol_list = array();
+		$all_list = array();
+		do
+		{
+			if ( $row === true && isset($first_row) )
+			{
+				$row = $first_row;
+			}
+				// die('<pre>' . var_dump($row) . $db->sql_backtrace() . '</pre>');
+			if ( !$row['img_id'] )
+				break;
+			$all_list[] = $row['img_id'];
+			if ( $row['is_folder'] == 1 )
+				$fol_list[] = $row['img_id'];
+			else
+				$img_list[] = $row['img_id'];
+		}
+		while ( $row = $db->fetchrow($img_query) );
+		
+		$all_list = implode(',', $all_list);
+		$fol_list = implode(',', $fol_list);
+		$img_list = implode(',', $img_list);
+		
+		if ( !empty($all_list) )
+		{
+			echo '<div style="float: right;">
+							Edit all in this folder: ';
+			if ( !empty($img_list) )
+			{
+				$edit_link = makeUrlNS('Special', 'GalleryUpload', 'edit_img=' . $img_list, true);
+				echo "<a href=\"$edit_link\">images</a> ";
+			}
+			if ( !empty($fol_list) )
+			{
+				$edit_link = makeUrlNS('Special', 'GalleryUpload', 'edit_img=' . $fol_list, true);
+				echo "<a href=\"$edit_link\">folders</a> ";
+			}
+			if ( !empty($img_list) && !empty($fol_list) )
+			{
+				$edit_link = makeUrlNS('Special', 'GalleryUpload', 'edit_img=' . $all_list, true);
+				echo "<a href=\"$edit_link\">both</a> ";
+			}
+			// " Bypass stupid jEdit bug
+			echo '</div>';
+		}
+	}
+	
+	$url_sort_name_asc  = makeUrl($paths->fullpage, 'sort=img_title&order=ASC', true);
+	$url_sort_name_desc = makeUrl($paths->fullpage, 'sort=img_title&order=DESC', true);
+	$url_sort_upl_asc   = makeUrl($paths->fullpage, 'sort=img_time_upload&order=ASC', true);
+	$url_sort_mod_asc   = makeUrl($paths->fullpage, 'sort=img_time_mod&order=ASC', true);
+	$url_sort_upl_desc  = makeUrl($paths->fullpage, 'sort=img_time_upload&order=DESC', true);
+	$url_sort_mod_desc  = makeUrl($paths->fullpage, 'sort=img_time_mod&order=DESC', true);
+	
+	// "Sort by" selector (pure CSS!)
+	echo '<div class="select-outer">
+					<span>Sort by...</span>
+					<div class="select-inner">
+						<a href="' . $url_sort_name_asc  . '">Image title (A-Z) <b>(default)</b></a>
+						<a href="' . $url_sort_name_desc . '">Image title (Z-A)</a>
+						<a href="' . $url_sort_upl_desc  . '">Time first uploaded (newest first)</a>
+						<a href="' . $url_sort_upl_asc   . '">Time first uploaded (oldest first)</a>
+						<a href="' . $url_sort_mod_desc  . '">Date of last modification (newest first)</a>
+						<a href="' . $url_sort_mod_asc   . '">Date of last modification (oldest first)</a>
+					</div>
+				</div>
+				<div class="select-pad">&nbsp;</div>';
+	
+	if ( $db->numrows($img_query) > 0 )
 	$db->sql_data_seek(0, $img_query);
-  
-  //
-  // Main fetcher
-  //
-  
-  $renderer = new SnaprFormatter();
-  $callers = array(
-    'img_id' => array($renderer, 'render')
-    );
-  
-  $renderer->icons_per_row = 5;
-  
-  $start = 0;
-  if ( isset($_GET['start']) && preg_match('/^[0-9]+$/', $_GET['start']) )
-  {
-    $start = intval($_GET['start']);
-  }
-  
-  $per_page = 25;
-  
-  $html = paginate($img_query, '{img_id}', $db->numrows($img_query), makeUrl($paths->fullpage, 'sort=' . $sort_column . '&order=' . $sort_order . '&start=%s', false), $start, $per_page, $callers, '<ul class="snapr-gallery">', '</ul><span class="menuclear"></span>');
-  if ( empty($html) )
-  {
-  	  echo '<h2 class="emptymessage">No images</h2>';
-  }
-  else
-  {
+	
+	//
+	// Main fetcher
+	//
+	
+	$renderer = new SnaprFormatter();
+	$callers = array(
+		'img_id' => array($renderer, 'render')
+		);
+	
+	$renderer->icons_per_row = 5;
+	
+	$start = 0;
+	if ( isset($_GET['start']) && preg_match('/^[0-9]+$/', $_GET['start']) )
+	{
+		$start = intval($_GET['start']);
+	}
+	
+	$per_page = 25;
+	
+	$html = paginate($img_query, '{img_id}', $db->numrows($img_query), makeUrl($paths->fullpage, 'sort=' . $sort_column . '&order=' . $sort_order . '&start=%s', false), $start, $per_page, $callers, '<ul class="snapr-gallery">', '</ul><span class="menuclear"></span>');
+	if ( empty($html) )
+	{
+		  echo '<h2 class="emptymessage">No images</h2>';
+	}
+	else
+	{
 	  echo $html;
-  }
-  
-  if ( $session->user_level >= USER_LEVEL_ADMIN )
-  {
-    echo '<div class="select-outer">Create new folder';
-    echo '<div class="select-inner" style="padding-top: 4px;">';
-    echo '<form action="' . makeUrl($paths->fullpage) . '" method="post">';
-    echo '<input type="text" name="create_folder" size="30" /> <input type="submit" value="Create" />';
-    echo '</form></div>';
-    echo '</div><div class="select-pad">&nbsp;</div><br />';
-  }
-  
-  $template->footer();
-  
+	}
+	
+	if ( $session->user_level >= USER_LEVEL_ADMIN )
+	{
+		echo '<div class="select-outer">Create new folder';
+		echo '<div class="select-inner" style="padding-top: 4px;">';
+		echo '<form action="' . makeUrl($paths->fullpage) . '" method="post">';
+		echo '<input type="text" name="create_folder" size="30" /> <input type="submit" value="Create" />';
+		echo '</form></div>';
+		echo '</div><div class="select-pad">&nbsp;</div><br />';
+	}
+	
+	$template->footer();
+	
 }
 
 ?>
--- a/plugins/gallery/canvas.js	Sat Aug 21 23:25:41 2010 -0400
+++ b/plugins/gallery/canvas.js	Sat Aug 21 23:32:06 2010 -0400
@@ -4,174 +4,174 @@
 
 function canvas_click(obj)
 {
-  var click_x = mouseX - $dynano(obj).Left();
-  var click_y = mouseY - $dynano(obj).Top() + getScrollOffset();
-  
-  if ( obj.canvas_in_draw )
-  {
-    canvas_close_draw(obj, click_x, click_y);
-  }
-  else
-  {
-    canvas_open_draw(obj, click_x, click_y);
-  }
+	var click_x = mouseX - $dynano(obj).Left();
+	var click_y = mouseY - $dynano(obj).Top() + getScrollOffset();
+	
+	if ( obj.canvas_in_draw )
+	{
+		canvas_close_draw(obj, click_x, click_y);
+	}
+	else
+	{
+		canvas_open_draw(obj, click_x, click_y);
+	}
 }
 
 function canvas_open_draw(obj, x, y)
 {
-  obj.canvas_box_obj = canvas_create_box(obj, x, y, 1, 1);
-  obj.canvas_in_draw = true;
-  obj.onclick = function(e)
-  {
-    canvas_click(this);
-    var onclose = this.getAttribute('canvas:oncomplete');
-    if ( onclose )
-    {
-      eval(onclose);
-    }
-  }
-  canvas_replace_mousemove(obj);
+	obj.canvas_box_obj = canvas_create_box(obj, x, y, 1, 1);
+	obj.canvas_in_draw = true;
+	obj.onclick = function(e)
+	{
+		canvas_click(this);
+		var onclose = this.getAttribute('canvas:oncomplete');
+		if ( onclose )
+		{
+			eval(onclose);
+		}
+	}
+	canvas_replace_mousemove(obj);
 }
 
 function canvas_replace_mousemove(obj)
 {
-  canvas_mousemove_temp = document.onmousemove;
-  canvas_mousemove_temp.box_obj = obj;
-  canvas_keyup_temp = document.onkeyup;
-  document.onmousemove = function(e)
-  {
-    canvas_mousemove_temp(e);
-    canvas_redraw_box(canvas_mousemove_temp.box_obj);
-  }
-  document.onkeyup = function(e)
-  {
-    if ( typeof(canvas_keyup_temp) == 'function' )
-      canvas_keyup_temp(e);
-    
-    if ( e.keyCode == CANVAS_KEY_ESC )
-      canvas_cancel_draw(canvas_mousemove_temp.box_obj);
-  }
+	canvas_mousemove_temp = document.onmousemove;
+	canvas_mousemove_temp.box_obj = obj;
+	canvas_keyup_temp = document.onkeyup;
+	document.onmousemove = function(e)
+	{
+		canvas_mousemove_temp(e);
+		canvas_redraw_box(canvas_mousemove_temp.box_obj);
+	}
+	document.onkeyup = function(e)
+	{
+		if ( typeof(canvas_keyup_temp) == 'function' )
+			canvas_keyup_temp(e);
+		
+		if ( e.keyCode == CANVAS_KEY_ESC )
+			canvas_cancel_draw(canvas_mousemove_temp.box_obj);
+	}
 }
 
 function canvas_restore_mousemove()
 {
-  document.onmousemove = canvas_mousemove_temp;
-  document.onkeyup = canvas_keyup_temp;
+	document.onmousemove = canvas_mousemove_temp;
+	document.onkeyup = canvas_keyup_temp;
 }
 
 function canvas_create_box(obj, x, y, width, height)
 {
-  var inner_width = width - 2;
-  var inner_height = height - 2;
-  var top = $dynano(obj).Top() + y;
-  var left = $dynano(obj).Left() + x;
-  
-  // draw outer box
-  var div_outer = document.createElement('div');
-  div_outer.className = 'canvasbox';
-  div_outer.style.border = '1px solid #000000';
-  div_outer.style.position = 'absolute';
-  div_outer.style.width = String(width) + 'px';
-  div_outer.style.height = String(height) + 'px';
-  div_outer.style.top = String(top) + 'px';
-  div_outer.style.left = String(left) + 'px';
-  
-  div_outer.rootY = y;
-  div_outer.rootX = x;
-  
-  var div_inner = document.createElement('div');
-  div_inner.style.border = '1px solid #FFFFFF';
-  if ( IE )
-  {
-    div_inner.style.width = '100%';
-    div_inner.style.height = '100%';
-  }
-  else
-  {
-    div_inner.style.width = String(inner_width) + 'px';
-    div_inner.style.height = String(inner_height) + 'px';
-  }
-  
-  div_outer.appendChild(div_inner);
-  
-  obj.appendChild(div_outer);
-  return div_outer;
+	var inner_width = width - 2;
+	var inner_height = height - 2;
+	var top = $dynano(obj).Top() + y;
+	var left = $dynano(obj).Left() + x;
+	
+	// draw outer box
+	var div_outer = document.createElement('div');
+	div_outer.className = 'canvasbox';
+	div_outer.style.border = '1px solid #000000';
+	div_outer.style.position = 'absolute';
+	div_outer.style.width = String(width) + 'px';
+	div_outer.style.height = String(height) + 'px';
+	div_outer.style.top = String(top) + 'px';
+	div_outer.style.left = String(left) + 'px';
+	
+	div_outer.rootY = y;
+	div_outer.rootX = x;
+	
+	var div_inner = document.createElement('div');
+	div_inner.style.border = '1px solid #FFFFFF';
+	if ( IE )
+	{
+		div_inner.style.width = '100%';
+		div_inner.style.height = '100%';
+	}
+	else
+	{
+		div_inner.style.width = String(inner_width) + 'px';
+		div_inner.style.height = String(inner_height) + 'px';
+	}
+	
+	div_outer.appendChild(div_inner);
+	
+	obj.appendChild(div_outer);
+	return div_outer;
 }
 
 function canvas_redraw_box(obj)
 {
-  if ( !obj.canvas_box_obj )
-    return false;
-  var rel_x = mouseX - $dynano(obj).Left();
-  var rel_y = mouseY - $dynano(obj).Top() + getScrollOffset();
-  var new_width = rel_x - obj.canvas_box_obj.rootX;
-  var new_height = rel_y - obj.canvas_box_obj.rootY;
-  var rootX = obj.canvas_box_obj.rootX;
-  var rootY = obj.canvas_box_obj.rootY;
-  // Limit dimensions to width - origin_x and height - origin_y
-  if ( new_width + rootX > $dynano(obj).Width() )
-    new_width = $dynano(obj).Width() - rootX;
-  if ( new_height + rootY > $dynano(obj).Height() )
-    new_height = $dynano(obj).Height() - rootY;
-  // If going to the top or left of the origin, avoid negative width/height by moving the box
-  if ( new_width < 1 )
-  {
-    new_width = rootX - rel_x;
-    obj.canvas_box_obj.style.left = String(mouseX + 2) + 'px';
-  }
-  if ( new_height < 1 )
-  {
-    new_height = rootY - rel_y;
-    obj.canvas_box_obj.style.top = String(mouseY + getScrollOffset() + 2) + 'px';
-  }
-  obj.canvas_box_obj.style.width = String(new_width) + 'px';
-  obj.canvas_box_obj.style.height = String(new_height) + 'px';
-  new_width = new_width - 2;
-  new_height = new_height - 2;
-  if ( IE )
-  {
-    var nw = new_width;
-    var nh = new_height;
-    obj.canvas_box_obj.firstChild.style.width = String(nw) + 'px';
-    obj.canvas_box_obj.firstChild.style.height = String(nh) + 'px';
-  }
-  else
-  {
-    obj.canvas_box_obj.firstChild.style.width = String(new_width) + 'px';
-    obj.canvas_box_obj.firstChild.style.height = String(new_height) + 'px';
-  }
+	if ( !obj.canvas_box_obj )
+		return false;
+	var rel_x = mouseX - $dynano(obj).Left();
+	var rel_y = mouseY - $dynano(obj).Top() + getScrollOffset();
+	var new_width = rel_x - obj.canvas_box_obj.rootX;
+	var new_height = rel_y - obj.canvas_box_obj.rootY;
+	var rootX = obj.canvas_box_obj.rootX;
+	var rootY = obj.canvas_box_obj.rootY;
+	// Limit dimensions to width - origin_x and height - origin_y
+	if ( new_width + rootX > $dynano(obj).Width() )
+		new_width = $dynano(obj).Width() - rootX;
+	if ( new_height + rootY > $dynano(obj).Height() )
+		new_height = $dynano(obj).Height() - rootY;
+	// If going to the top or left of the origin, avoid negative width/height by moving the box
+	if ( new_width < 1 )
+	{
+		new_width = rootX - rel_x;
+		obj.canvas_box_obj.style.left = String(mouseX + 2) + 'px';
+	}
+	if ( new_height < 1 )
+	{
+		new_height = rootY - rel_y;
+		obj.canvas_box_obj.style.top = String(mouseY + getScrollOffset() + 2) + 'px';
+	}
+	obj.canvas_box_obj.style.width = String(new_width) + 'px';
+	obj.canvas_box_obj.style.height = String(new_height) + 'px';
+	new_width = new_width - 2;
+	new_height = new_height - 2;
+	if ( IE )
+	{
+		var nw = new_width;
+		var nh = new_height;
+		obj.canvas_box_obj.firstChild.style.width = String(nw) + 'px';
+		obj.canvas_box_obj.firstChild.style.height = String(nh) + 'px';
+	}
+	else
+	{
+		obj.canvas_box_obj.firstChild.style.width = String(new_width) + 'px';
+		obj.canvas_box_obj.firstChild.style.height = String(new_height) + 'px';
+	}
 }
 
 function canvas_close_draw(obj, x, y)
 {
-  canvas_restore_mousemove();
-  obj.canvas_in_draw = false;
-  obj.canvas = {
-    top: $dynano(obj.canvas_box_obj).Top() - $dynano(obj).Top(),
-    left: $dynano(obj.canvas_box_obj).Left() - $dynano(obj).Left(),
-    width: $dynano(obj.canvas_box_obj).Width(),
-    height: $dynano(obj.canvas_box_obj).Height()
-  }
-  obj.onclick = function(e)
-  {
-    canvas_click(this);
-  }
+	canvas_restore_mousemove();
+	obj.canvas_in_draw = false;
+	obj.canvas = {
+		top: $dynano(obj.canvas_box_obj).Top() - $dynano(obj).Top(),
+		left: $dynano(obj.canvas_box_obj).Left() - $dynano(obj).Left(),
+		width: $dynano(obj.canvas_box_obj).Width(),
+		height: $dynano(obj.canvas_box_obj).Height()
+	}
+	obj.onclick = function(e)
+	{
+		canvas_click(this);
+	}
 }
 
 function canvas_cancel_draw(obj)
 {
-  canvas_restore_mousemove();
-  obj.canvas_in_draw = false;
-  obj.removeChild(obj.canvas_box_obj);
-  obj.canvas_box_obj = null;
-  obj.onclick = function(e)
-  {
-    canvas_click(this);
-  }
-  var ga = obj.getAttribute('canvas:oncancel');
-  if ( ga )
-  {
-    eval(ga);
-  }
+	canvas_restore_mousemove();
+	obj.canvas_in_draw = false;
+	obj.removeChild(obj.canvas_box_obj);
+	obj.canvas_box_obj = null;
+	obj.onclick = function(e)
+	{
+		canvas_click(this);
+	}
+	var ga = obj.getAttribute('canvas:oncancel');
+	if ( ga )
+	{
+		eval(ga);
+	}
 }
 
--- a/plugins/gallery/dropdown.css	Sat Aug 21 23:25:41 2010 -0400
+++ b/plugins/gallery/dropdown.css	Sat Aug 21 23:32:06 2010 -0400
@@ -74,3 +74,17 @@
 div.toggle div.body label:hover {
   background-color: #f3f7ff;
 }
+
+/* Uploader UI */
+
+h2.uploadgoing {
+	text-align: center;
+	border-bottom-width: 0 !important;
+	text-decoration: none;
+	margin: 20px 0;
+}
+
+p.uploadstatus {
+	text-align: center;
+	margin: 10px 0;
+}
--- a/plugins/gallery/fetcher.php	Sat Aug 21 23:25:41 2010 -0400
+++ b/plugins/gallery/fetcher.php	Sat Aug 21 23:32:06 2010 -0400
@@ -17,162 +17,168 @@
 ##
 
 $plugins->attachHook('base_classes_initted', '
-  global $paths;
-    $paths->add_page(Array(
-      \'name\'=>\'Image fetcher pagelet\',
-      \'urlname\'=>\'GalleryFetcher\',
-      \'namespace\'=>\'Special\',
-      \'special\'=>0,\'visible\'=>0,\'comments_on\'=>0,\'protected\'=>1,\'delvotes\'=>0,\'delvote_ips\'=>\'\',
-      ));
-  ');
+	global $paths;
+		$paths->add_page(Array(
+			\'name\'=>\'Image fetcher pagelet\',
+			\'urlname\'=>\'GalleryFetcher\',
+			\'namespace\'=>\'Special\',
+			\'special\'=>0,\'visible\'=>0,\'comments_on\'=>0,\'protected\'=>1,\'delvotes\'=>0,\'delvote_ips\'=>\'\',
+			));
+	');
 
 function page_Special_GalleryFetcher()
 {
-  global $db, $session, $paths, $template, $plugins; // Common objects
-  
-  // artificial race condition for debug
-  // sleep(5);
-  
-  $type = $paths->getParam(0);
-  if ( !in_array($type, array('thumb', 'preview', 'full', 'embed')) )
-  {
-    die('Hack attempt');
-  }
-  
-  $id = intval($paths->getParam(1));
-  if ( !$id )
-  {
-    die('Hack attempt');
-  }
-  
-  // Permissions object
-  $perms = $session->fetch_page_acl($id, 'Gallery');
-  
-  if ( !$perms->get_permissions('gal_full_res') && $type == 'full' )
-  {
-    $type = 'preview';
-  }
-  
-  $q = $db->sql_query('SELECT img_title, img_filename, img_time_mod, is_folder FROM '.table_prefix.'gallery WHERE img_id=' . $id . ';');
-  if ( !$q )
-    $db->_die();
-  
-  if ( $db->numrows() < 1 )
-    die('Image not found');
-  
-  $row = $db->fetchrow();
-  
-  switch ( $type )
-  {
-    case 'thumb':
-      $filename = ENANO_ROOT . '/cache/' . $row['img_filename'] . '-thumb.jpg';
-      $mimetype = 'image/jpeg';
-      $ext = "jpg";
-      break;
-    case 'preview':
-      $filename = ENANO_ROOT . '/cache/' . $row['img_filename'] . '-preview.jpg';
-      $mimetype = 'image/jpeg';
-      $ext = "jpg";
-      break;
-    case 'full':
-      $filename = ENANO_ROOT . '/files/' . $row['img_filename'];
-      $ext = get_file_extension($filename);
-      switch($ext)
-      {
-        case 'png': $mimetype = 'image/png'; break;
-        case 'gif': $mimetype = 'image/gif'; break;
-        case 'bmp': $mimetype = 'image/bmp'; break;
-        case 'jpg': case 'jpeg': $mimetype = 'image/jpeg'; break;
-        case 'tif': case 'tiff': $mimetype = 'image/tiff'; break;
-        default: $mimetype = 'application/octet-stream';
-      }
-      break;
-    case 'embed':
-      if ( !isset($_GET['width']) || !isset($_GET['height']) )
-      {
-        die('Missing width or height.');
-      }
-      $width = intval($_GET['width']);
-      $height = intval($_GET['height']);
-      if ( empty($width) || empty($height) || $width > 2048 || $height > 2048 )
-      {
-        die('Bad width or height');
-      }
-      
-      $ext = get_file_extension($row['img_filename']);
-      
-      $src_filename  = ENANO_ROOT . '/files/' . $row['img_filename'];
-      $dest_filename = ENANO_ROOT . '/cache/' . $row['img_filename'] . "-embed-$width-$height.$ext";
-      $filename =& $dest_filename;
-      
-      if ( !file_exists($dest_filename) )
-      {
-        if ( !scale_image($src_filename, $dest_filename, $width, $height, false) )
-        {
-          die('Image scaling process failed.');
-        }
-      }
-      
-      break;
-    default:
-      die('PHP...insane...');
-      break;
-  }
-  
-  // Make sure we have permission to read this image
-  if ( !$perms->get_permissions('read') )
-  {
-    $filename = ENANO_ROOT . '/plugins/gallery/denied.png';
-    $mimetype = 'image/png';
-  }
-  
-  if ( $row['is_folder'] == '1' )
-  {
-    $filename = ENANO_ROOT . '/plugins/gallery/folder.png';
-    $mimetype = 'image/png';
-  }
-  
-  if ( !file_exists($filename) )
-    die('Can\'t retrieve image file ' . $filename);
-  
-  $contents = file_get_contents($filename);
-  // expire images 30 days from now
-  $expiry = time() + ( 30 * 86400 );
-  
-  header('Content-type: '   . $mimetype);
-  header('Content-length: ' . strlen($contents));
-  header('Last-Modified: '  . date('r', $row['img_time_mod']));
-  header('Expires: ' . date('r', $expiry));
-  
-  // check for not-modified condition
-  if ( isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) )
-  {
-    $time = @strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
-    if ( ( !empty($time) && intval($row['img_time_mod']) <= $time ) || date('r', $row['img_time_mod']) === $_SERVER['HTTP_IF_MODIFIED_SINCE'] )
-    {
-      header('HTTP/1.1 304 Not Modified');
-      $db->close();
-      exit;
-    }
-  }
-  
-  if ( isset($_GET['download']) )
-  {
-    // determine an appropriate non-revealing filename
-    $filename = str_replace(' ', '_', $row['img_title']);
-    $filename = preg_replace('/([^\w\._-]+)/', '-', $filename);
-    $filename = trim($filename, '-');
-    $filename .= ".$ext";
-    header('Content-disposition: attachment; filename=' . $filename);
-  }
-  
-  echo $contents;
-  
-  gzip_output();
-  
-  $db->close();
-  exit;
-  
+	global $db, $session, $paths, $template, $plugins; // Common objects
+	
+	// artificial race condition for debug
+	// sleep(5);
+	
+	$type = $paths->getParam(0);
+	if ( !in_array($type, array('thumb', 'preview', 'full', 'embed')) )
+	{
+		die('Hack attempt');
+	}
+	
+	$id = intval($paths->getParam(1));
+	if ( !$id )
+	{
+		die('Hack attempt');
+	}
+	
+	// Permissions object
+	$perms = $session->fetch_page_acl($id, 'Gallery');
+	
+	if ( !$perms->get_permissions('gal_full_res') && $type == 'full' )
+	{
+		$type = 'preview';
+	}
+	
+	while ( true )
+	{
+		$q = $db->sql_query('SELECT img_title, img_filename, img_time_mod, is_folder, processed FROM '.table_prefix.'gallery WHERE img_id=' . $id . ';');
+		if ( !$q )
+			$db->_die();
+		
+		if ( $db->numrows() < 1 )
+			die('Image not found');
+		
+		$row = $db->fetchrow();
+		if ( $row['processed'] == 1 || $type == 'full' )
+			break;
+		sleep(1);
+	}
+	
+	switch ( $type )
+	{
+		case 'thumb':
+			$filename = ENANO_ROOT . '/cache/' . $row['img_filename'] . '-thumb.jpg';
+			$mimetype = 'image/jpeg';
+			$ext = "jpg";
+			break;
+		case 'preview':
+			$filename = ENANO_ROOT . '/cache/' . $row['img_filename'] . '-preview.jpg';
+			$mimetype = 'image/jpeg';
+			$ext = "jpg";
+			break;
+		case 'full':
+			$filename = ENANO_ROOT . '/files/' . $row['img_filename'];
+			$ext = get_file_extension($filename);
+			switch($ext)
+			{
+				case 'png': $mimetype = 'image/png'; break;
+				case 'gif': $mimetype = 'image/gif'; break;
+				case 'bmp': $mimetype = 'image/bmp'; break;
+				case 'jpg': case 'jpeg': $mimetype = 'image/jpeg'; break;
+				case 'tif': case 'tiff': $mimetype = 'image/tiff'; break;
+				default: $mimetype = 'application/octet-stream';
+			}
+			break;
+		case 'embed':
+			if ( !isset($_GET['width']) || !isset($_GET['height']) )
+			{
+				die('Missing width or height.');
+			}
+			$width = intval($_GET['width']);
+			$height = intval($_GET['height']);
+			if ( empty($width) || empty($height) || $width > 2048 || $height > 2048 )
+			{
+				die('Bad width or height');
+			}
+			
+			$ext = get_file_extension($row['img_filename']);
+			
+			$src_filename  = ENANO_ROOT . '/files/' . $row['img_filename'];
+			$dest_filename = ENANO_ROOT . '/cache/' . $row['img_filename'] . "-embed-$width-$height.$ext";
+			$filename =& $dest_filename;
+			
+			if ( !file_exists($dest_filename) )
+			{
+				if ( !scale_image($src_filename, $dest_filename, $width, $height, false) )
+				{
+					die('Image scaling process failed.');
+				}
+			}
+			
+			break;
+		default:
+			die('PHP...insane...');
+			break;
+	}
+	
+	// Make sure we have permission to read this image
+	if ( !$perms->get_permissions('read') )
+	{
+		$filename = ENANO_ROOT . '/plugins/gallery/denied.png';
+		$mimetype = 'image/png';
+	}
+	
+	if ( $row['is_folder'] == '1' )
+	{
+		$filename = ENANO_ROOT . '/plugins/gallery/folder.png';
+		$mimetype = 'image/png';
+	}
+	
+	if ( !file_exists($filename) )
+		die('Can\'t retrieve image file ' . $filename);
+	
+	$contents = file_get_contents($filename);
+	// expire images 30 days from now
+	$expiry = time() + ( 30 * 86400 );
+	
+	header('Content-type: '   . $mimetype);
+	header('Content-length: ' . strlen($contents));
+	header('Last-Modified: '  . date('r', $row['img_time_mod']));
+	header('Expires: ' . date('r', $expiry));
+	
+	// check for not-modified condition
+	if ( isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) )
+	{
+		$time = @strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
+		if ( ( !empty($time) && intval($row['img_time_mod']) <= $time ) || date('r', $row['img_time_mod']) === $_SERVER['HTTP_IF_MODIFIED_SINCE'] )
+		{
+			header('HTTP/1.1 304 Not Modified');
+			$db->close();
+			exit;
+		}
+	}
+	
+	if ( isset($_GET['download']) )
+	{
+		// determine an appropriate non-revealing filename
+		$filename = str_replace(' ', '_', $row['img_title']);
+		$filename = preg_replace('/([^\w\._-]+)/', '-', $filename);
+		$filename = trim($filename, '-');
+		$filename .= ".$ext";
+		header('Content-disposition: attachment; filename=' . $filename);
+	}
+	
+	echo $contents;
+	
+	gzip_output();
+	
+	$db->close();
+	exit;
+	
 }
 
 ?>
--- a/plugins/gallery/functions.php	Sat Aug 21 23:25:41 2010 -0400
+++ b/plugins/gallery/functions.php	Sat Aug 21 23:32:06 2010 -0400
@@ -20,14 +20,14 @@
  
 function gallery_make_filename($length = 24)
 {
-  $valid_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
-  $valid_chars = enano_str_split($valid_chars);
-  $ret = '';
-  for ( $i = 0; $i < $length; $i++ )
-  {
-    $ret .= $valid_chars[mt_rand(0, count($valid_chars)-1)];
-  }
-  return $ret;
+	$valid_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+	$valid_chars = enano_str_split($valid_chars);
+	$ret = '';
+	for ( $i = 0; $i < $length; $i++ )
+	{
+		$ret .= $valid_chars[mt_rand(0, count($valid_chars)-1)];
+	}
+	return $ret;
 }
 
 /**
@@ -38,7 +38,7 @@
 
 function get_file_extension($file)
 {
-  return substr($file, ( strrpos($file, '.') + 1 ));
+	return substr($file, ( strrpos($file, '.') + 1 ));
 }
 
 /**
@@ -49,42 +49,42 @@
 
 function gallery_imgid_to_folder($img_id)
 {
-  global $db, $session, $paths, $template, $plugins; // Common objects
-  
-  if ( !is_int($img_id) )
-    return array();
-  
-  $img_id = strval($img_id);
-  $ret = array();
-  
-  $sanity = 0;
-  $sanity_stack = array();
-  
-  while(true)
-  {
-    $sanity++;
-    $q = $db->sql_query('SELECT img_title, img_id, folder_parent FROM '.table_prefix.'gallery WHERE img_id=' . $img_id . ';');
-    if ( !$q )
-      $db->_die();
-    $row = $db->fetchrow();
-    if ( !$row )
-    {
-      break;
-    }
-    if ( $sanity > 1 )
-    {
-      $ret[] = $row['img_title'];
-    }
-    if ( !$row['folder_parent'] )
-    {
-      break;
-    }
-    if ( in_array($row['img_id'], $sanity_stack) )
-      return array('Infinite loop');
-    $sanity_stack[] = $row['img_id'];
-    $img_id = $row['folder_parent'];
-  }
-  return $ret;
+	global $db, $session, $paths, $template, $plugins; // Common objects
+	
+	if ( !is_int($img_id) )
+		return array();
+	
+	$img_id = strval($img_id);
+	$ret = array();
+	
+	$sanity = 0;
+	$sanity_stack = array();
+	
+	while(true)
+	{
+		$sanity++;
+		$q = $db->sql_query('SELECT img_title, img_id, folder_parent FROM '.table_prefix.'gallery WHERE img_id=' . $img_id . ';');
+		if ( !$q )
+			$db->_die();
+		$row = $db->fetchrow();
+		if ( !$row )
+		{
+			break;
+		}
+		if ( $sanity > 1 )
+		{
+			$ret[] = $row['img_title'];
+		}
+		if ( !$row['folder_parent'] )
+		{
+			break;
+		}
+		if ( in_array($row['img_id'], $sanity_stack) )
+			return array('Infinite loop');
+		$sanity_stack[] = $row['img_id'];
+		$img_id = $row['folder_parent'];
+	}
+	return $ret;
 }
 
 /**
@@ -94,71 +94,71 @@
 
 function gallery_folder_hierarchy()
 {
-  global $db, $session, $paths, $template, $plugins; // Common objects
-  
-  $q = $db->sql_query('SELECT img_id, img_title, folder_parent FROM '.table_prefix.'gallery WHERE is_folder=1');
-  if ( !$q )
-    $db->_die();
-  
-  if ( $db->numrows() < 1 )
-  {
-    return array('_id' => 'NULL');
-  }
-  
-  $lookup_table = array();
-  $hier = array('_id' => 'NULL');
-  $orphans = array();
-  $persist_orphans = array();
-  
-  while ( $row = $db->fetchrow() )
-  {
-    if ( !$row['folder_parent'] )
-    {
-      // root-level folder
-      $hier[ $row['img_title'] ] = array('_id' => $row['img_id']);
-      $lookup_table[$row['img_id']] =& $hier[ $row['img_title'] ];
-    }
-    else if ( $row['folder_parent'] && isset($lookup_table[$row['folder_parent']]) )
-    {
-      // child folder, parent is resolved
-      $lookup_table[ $row['folder_parent'] ][ $row['img_title'] ] = array('_id' => $row['img_id']);
-      $lookup_table[ $row['img_id'] ] =& $lookup_table[ $row['folder_parent'] ][ $row['img_title'] ];
-    }
-    else if ( $row['folder_parent'] && !isset($lookup_table[$row['folder_parent']]) )
-    {
-      // child folder, orphan as of yet
-      $orphans[] = $row;
-    }
-  }
-  
-  // Resolve orphans
-  do
-  {
-    $persist_orphans = array();
-    while ( count($orphans) > 0 )
-    {
-      $orphan =& $orphans[ ( count($orphans) - 1 ) ];
-      if ( isset($lookup_table[$orphan['folder_parent']]) )
-      {
-        $lookup_table[ $orphan['folder_parent'] ][ $orphan['img_title'] ] = array('_id' => $orphan['img_id']);
-        $lookup_table[ $orphan['img_id'] ] =& $lookup_table[ $orphan['folder_parent'] ][ $orphan['img_title'] ];
-      }
-      else
-      {
-        $persist_orphans[] = $orphans[ ( count($orphans) - 1 ) ];
-        //echo 'BUG: ' . htmlspecialchars($orphan['img_title']) . ' (' . $orphan['img_id'] . ') is an orphan folder (parent is ' . $orphan['folder_parent'] . '); placing in root<br />';
-        // $hier[ $orphan['img_title'] ] = array();
-        // $lookup_table[$orphan['img_id']] =& $hier[ $orphan['img_title'] ];
-      }
-      unset($orphan, $orphans[ ( count($orphans) - 1 ) ]);
-    }
-    $orphans = $persist_orphans;
-    //die('insanity:<pre>'.print_r($hier,true).print_r($lookup_table,true).print_r($persist_orphans,true).'</pre>');
-  }
-  while ( count($persist_orphans) > 0 );
-  
-  return $hier;
-  
+	global $db, $session, $paths, $template, $plugins; // Common objects
+	
+	$q = $db->sql_query('SELECT img_id, img_title, folder_parent FROM '.table_prefix.'gallery WHERE is_folder=1');
+	if ( !$q )
+		$db->_die();
+	
+	if ( $db->numrows() < 1 )
+	{
+		return array('_id' => 'NULL');
+	}
+	
+	$lookup_table = array();
+	$hier = array('_id' => 'NULL');
+	$orphans = array();
+	$persist_orphans = array();
+	
+	while ( $row = $db->fetchrow() )
+	{
+		if ( !$row['folder_parent'] )
+		{
+			// root-level folder
+			$hier[ $row['img_title'] ] = array('_id' => $row['img_id']);
+			$lookup_table[$row['img_id']] =& $hier[ $row['img_title'] ];
+		}
+		else if ( $row['folder_parent'] && isset($lookup_table[$row['folder_parent']]) )
+		{
+			// child folder, parent is resolved
+			$lookup_table[ $row['folder_parent'] ][ $row['img_title'] ] = array('_id' => $row['img_id']);
+			$lookup_table[ $row['img_id'] ] =& $lookup_table[ $row['folder_parent'] ][ $row['img_title'] ];
+		}
+		else if ( $row['folder_parent'] && !isset($lookup_table[$row['folder_parent']]) )
+		{
+			// child folder, orphan as of yet
+			$orphans[] = $row;
+		}
+	}
+	
+	// Resolve orphans
+	do
+	{
+		$persist_orphans = array();
+		while ( count($orphans) > 0 )
+		{
+			$orphan =& $orphans[ ( count($orphans) - 1 ) ];
+			if ( isset($lookup_table[$orphan['folder_parent']]) )
+			{
+				$lookup_table[ $orphan['folder_parent'] ][ $orphan['img_title'] ] = array('_id' => $orphan['img_id']);
+				$lookup_table[ $orphan['img_id'] ] =& $lookup_table[ $orphan['folder_parent'] ][ $orphan['img_title'] ];
+			}
+			else
+			{
+				$persist_orphans[] = $orphans[ ( count($orphans) - 1 ) ];
+				//echo 'BUG: ' . htmlspecialchars($orphan['img_title']) . ' (' . $orphan['img_id'] . ') is an orphan folder (parent is ' . $orphan['folder_parent'] . '); placing in root<br />';
+				// $hier[ $orphan['img_title'] ] = array();
+				// $lookup_table[$orphan['img_id']] =& $hier[ $orphan['img_title'] ];
+			}
+			unset($orphan, $orphans[ ( count($orphans) - 1 ) ]);
+		}
+		$orphans = $persist_orphans;
+		//die('insanity:<pre>'.print_r($hier,true).print_r($lookup_table,true).print_r($persist_orphans,true).'</pre>');
+	}
+	while ( count($persist_orphans) > 0 );
+	
+	return $hier;
+	
 }
 
 /**
@@ -170,15 +170,15 @@
 
 function gallery_hier_formfield($field_name = 'folder_id', $autosel = true)
 {
-  $hier = gallery_folder_hierarchy();
-  $img_join      = scriptPath . '/images/icons/joinbottom.gif';
-  $img_join_term = scriptPath . '/images/icons/join.gif';
-  $img_line      = scriptPath . '/images/icons/line.gif';
-  $img_empty     = scriptPath . '/images/icons/empty.gif';
-  
-  $html = _gallery_hier_form_inner($hier, '<Root>', $field_name, -1, array(), $img_join, $img_join_term, $img_line, $img_empty, $autosel);
-  
-  return $html;
+	$hier = gallery_folder_hierarchy();
+	$img_join      = scriptPath . '/images/icons/joinbottom.gif';
+	$img_join_term = scriptPath . '/images/icons/join.gif';
+	$img_line      = scriptPath . '/images/icons/line.gif';
+	$img_empty     = scriptPath . '/images/icons/empty.gif';
+	
+	$html = _gallery_hier_form_inner($hier, '<Root>', $field_name, -1, array(), $img_join, $img_join_term, $img_line, $img_empty, $autosel);
+	
+	return $html;
 }
 
 // 
@@ -190,41 +190,41 @@
 
 function _gallery_hier_form_inner($el, $name, $fname, $depth, $depth_img, $img_join, $img_join_term, $img_line, $img_empty, $sel = false)
 {
-  $html = '';
-  foreach ( $depth_img as $sw )
-    $html .= '<img alt="  " src="' . $sw . '" />';
-  
-  $html .= '<label><input ' . ( $sel ? 'checked="checked"' : '' ) . ' type="radio" name="' . $fname . '" value="' . $el['_id'] . '" /> ' . htmlspecialchars($name) . '</label><br />';
-  
-  if ( count($el) > 1 )
-  {
-    // Writing this image logic sucked.
-    $count = 0;
-    foreach ( $el as $key => $el_lower )
-    {
-      $count++;
-      if ( $key == '_id' )
-        continue;
-      $depth_mod = $depth_img;
-      $last = ( $count == count($el) );
-      
-      for ( $i = 0; $i < count($depth_mod); $i++ )
-      {
-        if ( $depth_mod[$i] == $img_join_term || $depth_mod[$i] == $img_empty )
-          $depth_mod[$i] = $img_empty;
-        else
-          $depth_mod[$i] = $img_line;
-      }
-      
-      if ( $last )
-        $depth_mod[] = $img_join_term;
-      else
-        $depth_mod[] = $img_join;
-      
-      $html .= _gallery_hier_form_inner($el_lower, $key, $fname, ( $depth + 1 ), $depth_mod, $img_join, $img_join_term, $img_line, $img_empty);
-    }
-  }
-  return $html;
+	$html = '';
+	foreach ( $depth_img as $sw )
+		$html .= '<img alt="  " src="' . $sw . '" />';
+	
+	$html .= '<label><input ' . ( $sel ? 'checked="checked"' : '' ) . ' type="radio" name="' . $fname . '" value="' . $el['_id'] . '" /> ' . htmlspecialchars($name) . '</label><br />';
+	
+	if ( count($el) > 1 )
+	{
+		// Writing this image logic sucked.
+		$count = 0;
+		foreach ( $el as $key => $el_lower )
+		{
+			$count++;
+			if ( $key == '_id' )
+				continue;
+			$depth_mod = $depth_img;
+			$last = ( $count == count($el) );
+			
+			for ( $i = 0; $i < count($depth_mod); $i++ )
+			{
+				if ( $depth_mod[$i] == $img_join_term || $depth_mod[$i] == $img_empty )
+					$depth_mod[$i] = $img_empty;
+				else
+					$depth_mod[$i] = $img_line;
+			}
+			
+			if ( $last )
+				$depth_mod[] = $img_join_term;
+			else
+				$depth_mod[] = $img_join;
+			
+			$html .= _gallery_hier_form_inner($el_lower, $key, $fname, ( $depth + 1 ), $depth_mod, $img_join, $img_join_term, $img_line, $img_empty);
+		}
+	}
+	return $html;
 }
 
 /**
@@ -234,42 +234,42 @@
 
 function gal_fetch_all_children($id)
 {
-  global $db, $session, $paths, $template, $plugins; // Common objects
-  
-  if ( !is_int($id) )
-  {
-    die('not int');
-    return false;
-  }
-  
-  $children = array();
-  
-  $q = $db->sql_query('SELECT img_id,is_folder FROM '.table_prefix.'gallery WHERE folder_parent=' . $id . ';');
-  if ( !$q )
-    $db->_die();
-  if ( $db->numrows() < 1 )
-  {
-    return $children;
-  }
-  $folders = array();
-  while ( $row = $db->fetchrow() )
-  {
-    $children[] = intval($row['img_id']);
-    if ( $row['is_folder'] == 1 )
-      $folders[] = intval($row['img_id']);
-  }
-  foreach ( $folders as $folder )
-  {
-    $grandchildren = gal_fetch_all_children($folder);
-    if ( $grandchildren === false )
-    {
-      return false;
-    }
-    $children = array_merge($children, $grandchildren);
-  }
-  
-  return $children;
-  
+	global $db, $session, $paths, $template, $plugins; // Common objects
+	
+	if ( !is_int($id) )
+	{
+		die('not int');
+		return false;
+	}
+	
+	$children = array();
+	
+	$q = $db->sql_query('SELECT img_id,is_folder FROM '.table_prefix.'gallery WHERE folder_parent=' . $id . ';');
+	if ( !$q )
+		$db->_die();
+	if ( $db->numrows() < 1 )
+	{
+		return $children;
+	}
+	$folders = array();
+	while ( $row = $db->fetchrow() )
+	{
+		$children[] = intval($row['img_id']);
+		if ( $row['is_folder'] == 1 )
+			$folders[] = intval($row['img_id']);
+	}
+	foreach ( $folders as $folder )
+	{
+		$grandchildren = gal_fetch_all_children($folder);
+		if ( $grandchildren === false )
+		{
+			return false;
+		}
+		$children = array_merge($children, $grandchildren);
+	}
+	
+	return $children;
+	
 }
 
 /**
@@ -281,37 +281,37 @@
 
 function gal_dir_recurse($dir, &$dirlist)
 {
-  $dir_handle = opendir($dir);
-  if ( !$dir_handle )
-    return false;
-  $entries = array();
-  $dirlist = array();
-  while ( true )
-  {
-    $file = readdir($dir_handle);
-    if ( !$file )
-      break;
-    if ( $file == '.' || $file == '..' )
-      continue;
-    $file = $dir . '/' . $file;
-    if ( is_dir($file) )
-    {
-      $children = gal_dir_recurse($file, $dirtemp);
-      $dirlist[] = $file;
-      $dirlist = array_merge($dirlist, $dirtemp);
-      $entries = array_merge($entries, $children);
-    }
-    else if ( is_file($file) )
-    {
-      $entries[] = $file;
-    }
-    else
-    {
-      die($file . ' is not a file or directory');
-    }
-  }
-  closedir($dir_handle);
-  return $entries;
+	$dir_handle = opendir($dir);
+	if ( !$dir_handle )
+		return false;
+	$entries = array();
+	$dirlist = array();
+	while ( true )
+	{
+		$file = readdir($dir_handle);
+		if ( !$file )
+			break;
+		if ( $file == '.' || $file == '..' )
+			continue;
+		$file = $dir . '/' . $file;
+		if ( is_dir($file) )
+		{
+			$children = gal_dir_recurse($file, $dirtemp);
+			$dirlist[] = $file;
+			$dirlist = array_merge($dirlist, $dirtemp);
+			$entries = array_merge($entries, $children);
+		}
+		else if ( is_file($file) )
+		{
+			$entries[] = $file;
+		}
+		else
+		{
+			die($file . ' is not a file or directory');
+		}
+	}
+	closedir($dir_handle);
+	return $entries;
 }
 
 /**
@@ -322,27 +322,27 @@
 
 function snapr_json_decode($data)
 {
-  if ( defined('ENANO_ATLEAST_1_1') )
-  {
-    try
-    {
-      $decoded = enano_json_decode($data);
-    }
-    catch ( Exception $e )
-    {
-      $response = array(
-        'mode' => 'error',
-        'error' => 'Exception in JSON parser.'
-      );
-      die(enano_json_encode($response));
-    }
-  }
-  else
-  {
-    $json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE);
-    $decoded = $json->decode($data);
-  }
-  return ( isset($decoded) ) ? $decoded : false;
+	if ( defined('ENANO_ATLEAST_1_1') )
+	{
+		try
+		{
+			$decoded = enano_json_decode($data);
+		}
+		catch ( Exception $e )
+		{
+			$response = array(
+				'mode' => 'error',
+				'error' => 'Exception in JSON parser.'
+			);
+			die(enano_json_encode($response));
+		}
+	}
+	else
+	{
+		$json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE);
+		$decoded = $json->decode($data);
+	}
+	return ( isset($decoded) ) ? $decoded : false;
 }
 
 /**
@@ -353,27 +353,201 @@
 
 function snapr_json_encode($data)
 {
-  if ( defined('ENANO_ATLEAST_1_1') )
-  {
-    try
-    {
-      $encoded = enano_json_encode($data);
-    }
-    catch ( Exception $e )
-    {
-      $response = array(
-        'mode' => 'error',
-        'error' => 'Exception in JSON encoder.'
-      );
-      die(enano_json_encode($response));
-    }
-  }
-  else
-  {
-    $json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE);
-    $encoded = $json->encode($data);
-  }
-  return ( isset($encoded) ) ? $encoded : false;
+	if ( defined('ENANO_ATLEAST_1_1') )
+	{
+		try
+		{
+			$encoded = enano_json_encode($data);
+		}
+		catch ( Exception $e )
+		{
+			$response = array(
+				'mode' => 'error',
+				'error' => 'Exception in JSON encoder.'
+			);
+			die(enano_json_encode($response));
+		}
+	}
+	else
+	{
+		$json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE);
+		$encoded = $json->encode($data);
+	}
+	return ( isset($encoded) ) ? $encoded : false;
+}
+
+/**
+ * Is the given file extension allowed?
+ * @param string
+ * @return bool
+ */
+
+function snapr_extension_allowed($ext)
+{
+	$allowedext = array('png', 'jpg', 'jpeg', 'tiff', 'tif', 'bmp', 'gif');
+	return in_array(strtolower($ext), $allowedext);
+}
+
+/**
+ * Process (make thumbnails for) an uploaded image.
+ * @param int image_id
+ * @return bool
+ */
+
+function snapr_process_image($image_id)
+{
+	global $db, $session, $paths, $template, $plugins; // Common objects
+	
+	$q = $db->sql_query('SELECT img_filename FROM ' . table_prefix . "gallery WHERE img_id = $image_id AND processed = 0 AND is_folder = 0;");
+	if ( !$q )
+		$db->_die();
+	if ( $db->numrows() < 1 )
+	{
+		$db->free_result();
+		return false;
+	}
+	list($filename) = $db->fetchrow_num($q);
+	$db->free_result();
+	
+	$orig_path = ENANO_ROOT . "/files/$filename";
+	$thumb = ENANO_ROOT . "/cache/$filename-thumb.jpg";
+	$preview = ENANO_ROOT . "/cache/$filename-preview.jpg";
+	
+	// create thumbnail
+	if ( !scale_image($orig_path, $thumb, 80, 80, true) )
+		return false;
+	// create preview
+	if ( !scale_image($orig_path, $preview, 640, 1000, true) )
+		return false;
+	
+	$q = $db->sql_query('UPDATE ' . table_prefix . "gallery SET processed = 1 WHERE img_id = $image_id;");
+	if ( !$q )
+		$db->_die();
+	
+	return true;
 }
 
-?>
+/**
+ * Simple function to add an image to the database. Needs only the file path and the folder to put it in.
+ * @param string Filename
+ * @param int Folder, defaults to NULL (root)
+ * @return int image ID
+ */
+
+function snapr_insert_image($path, $folder_id = NULL)
+{
+	global $db, $session, $paths, $template, $plugins; // Common objects
+	
+	$ext = get_file_extension($path);
+	$ourfilename = gallery_make_filename() . "." . strtolower($ext);
+	if ( !snapr_extension_allowed($ext) )
+		return false;
+	
+	// copy the file to the storage folder
+	if ( !rename($path, ENANO_ROOT . "/files/$ourfilename") )
+		return false;
+	
+	// insert the image into the database
+	$folder = $folder_id === NULL ? 'NULL' : strval(intval($folder_id));
+	$title = ucwords(str_replace('_', ' ', basename($path)));
+	$title = preg_replace("/\.{$ext}\$/i", '', $title);
+	$sz = serialize(array());
+	$now = time();
+	$q = $db->sql_query('INSERT INTO ' . table_prefix . "gallery(is_folder, folder_parent, img_title, print_sizes, img_filename, img_time_upload, img_time_mod, img_tags, img_author, processed) VALUES\n"
+		              . "	(0, $folder, '$title', '$sz', '$ourfilename', $now, $now, '[]', $session->user_id, 0);");
+	if ( !$q )
+		$db->_die();
+	
+	return $db->insert_id();
+}
+
+/**
+ * Process an uploaded zip file.
+ * @param string Zip file
+ * @param int Folder ID, defaults to NULL (root)
+ * @return array of image IDs
+ */
+
+function snapr_process_zip($path, $folder_id = NULL)
+{
+	error_reporting(E_ALL);
+
+	if ( !mkdir(ENANO_ROOT . '/cache/temp') )
+		return false;
+	$temp_dir = tempnam(ENANO_ROOT . '/cache/temp', 'galunz');
+	if ( file_exists($temp_dir) )
+		unlink($temp_dir);
+	@mkdir($temp_dir);
+	
+	// Extract the zip file
+	if ( class_exists('ZipArchive') )
+	{
+		$zip = new ZipArchive();
+		$op = $zip->open($file['tmp_name']);
+		if ( !$op )
+		{
+			return false;
+		}
+		$op = $zip->extractTo($temp_dir);
+		if ( !$op )
+		{
+			return false;
+		}
+	}
+	else if ( file_exists('/usr/bin/unzip') )
+	{
+		$cmd = "/usr/bin/unzip -qq -d '$temp_dir' {$path}";
+		system($cmd);
+	}
+	
+	// Any files?
+	$file_list = gal_dir_recurse($temp_dir, $dirs);
+	if ( !$file_list )
+	{
+		return false;
+	}
+	if ( count($file_list) < 1 )
+	{
+		return false;
+	}
+	
+	$dirs = array_reverse($dirs);
+	$img_files = array();
+	
+	// Loop through and add files
+	foreach ( $file_list as $file )
+	{
+		$ext = get_file_extension($file);
+		
+		if ( snapr_extension_allowed($ext) )
+		{
+			$img_files[] = $file;
+		}
+		else
+		{
+			unlink($file);
+		}
+	}
+	
+	// Main storage loop
+	$results = array();
+	foreach ( $img_files as $file )
+	{
+		$result = snapr_insert_image($file, $folder_id);
+		if ( $result !== false )
+			$results[] = $result;
+	}
+	
+	// clean up
+	foreach ( $dirs as $dir )
+	{
+		rmdir($dir);
+	}
+	
+	if ( !rmdir( $temp_dir ) )
+		return false;
+	if ( !rmdir( ENANO_ROOT . '/cache/temp' ) )
+		return false;
+		
+	return $results;
+}
--- a/plugins/gallery/gallery-bits.js	Sat Aug 21 23:25:41 2010 -0400
+++ b/plugins/gallery/gallery-bits.js	Sat Aug 21 23:32:06 2010 -0400
@@ -4,25 +4,37 @@
 
 function gal_toggle(elem, img, img_open, img_close)
 {
-  if ( !img_close || !img_open )
-  {
-    img_close = scriptPath + '/plugins/gallery/toggle-closed.png';
-    img_open  = scriptPath + '/plugins/gallery/toggle-open.png';
-  }
-  if ( elem.style.display == 'none' || !elem.style.display )
-  {
-    elem.style.display = 'block';
-    try {
-      img.src = img_open;
-    } catch(e) {};
-  }
-  else
-  {
-    elem.style.display = 'none';
-    try {
-      img.src = img_close;
-    } catch(e) {};
-  }
+	if ( !img_close || !img_open )
+	{
+		img_close = scriptPath + '/plugins/gallery/toggle-closed.png';
+		img_open  = scriptPath + '/plugins/gallery/toggle-open.png';
+	}
+	if ( elem.style.display == 'none' || !elem.style.display )
+	{
+		elem.style.display = 'block';
+		try {
+			img.src = img_open;
+		} catch(e) {};
+	}
+	else
+	{
+		elem.style.display = 'none';
+		try {
+			img.src = img_close;
+		} catch(e) {};
+	}
 }
 
+function gal_unset_radios(name)
+{
+	var radios = document.getElementsByTagName('input');
+	for ( var i = 0; i < radios.length; i++ )
+	{
+		var radio = radios[i];
+		if ( radio.name == name )
+		{
+			radio.checked = false;
+		}
+	}
+}
 
--- a/plugins/gallery/imagetag.php	Sat Aug 21 23:25:41 2010 -0400
+++ b/plugins/gallery/imagetag.php	Sat Aug 21 23:32:06 2010 -0400
@@ -17,8 +17,8 @@
 
 function snapr_process_image_tags(&$text)
 {
-  $text = snapr_image_tags_stage1($text, $taglist);
-  $text = snapr_image_tags_stage2($text, $taglist);
+	$text = snapr_image_tags_stage1($text, $taglist);
+	$text = snapr_image_tags_stage2($text, $taglist);
 }
 
 /*
@@ -34,208 +34,208 @@
 
 function snapr_image_tags_stage1($text, &$taglist)
 {
-  global $db, $session, $paths, $template, $plugins; // Common objects
-  
-  static $idcache = array();
-  
-  $s_delim = "\xFF";
-  $f_delim = "\xFF";
-  $taglist = array();
-  
-  // Wicked huh?
-  $regex = '/\[\[:' . str_replace('/', '\\/', preg_quote($paths->nslist['Gallery'])) . '([\w\s0-9_\(\)!@%\^\+\|\.-]+?)((\|thumb)|(\|([0-9]+)x([0-9]+)))?(\|left|\|right)?(\|raw|\|(.+))?\]\]/i';
-  
-  preg_match_all($regex, $text, $matches);
-  
-  foreach ( $matches[0] as $i => $match )
-  {
-    $full_tag   =& $matches[0][$i];
-    $imagename  =& $matches[1][$i];
-    $scale_type =& $matches[2][$i];
-    $width      =& $matches[5][$i];
-    $height     =& $matches[6][$i];
-    $clear      =& $matches[7][$i];
-    $caption    =& $matches[8][$i];
-    
-    // determine the image name
-    $imagename = sanitize_page_id($imagename);
-    if ( isset($idcache[$imagename]) )
-    {
-      $found_image_id = true;
-      $filename =& $idcache[$imagename];
-    }
-    else
-    {
-      $found_image_id = false;
-      // get the image ID
-      // Ech... he sent us a string... parse it and see what we get
-      if ( strstr($imagename, '/') )
-      {
-        $folders = explode('/', $imagename);
-      }
-      else
-      {
-        $folders = array($imagename);
-      }
-      foreach ( $folders as $i => $_crap )
-      {
-        $folder =& $folders[$i];
-        $folder = dirtify_page_id($folder);
-        $folder = str_replace('_', ' ', $folder);
-      }
-      unset($folder);
-      
-      $folders = array_reverse($folders);
-      // This is one of the best MySQL tricks on the market. We're going to reverse-travel a folder path using LEFT JOIN and the incredible power of metacoded SQL
-      $sql = 'SELECT g0.img_id, g0.img_title, g0.img_desc, g0.print_sizes, g0.img_time_upload, g0.img_time_mod, g0.img_filename, g0.folder_parent, g0.img_tags FROM '.table_prefix.'gallery AS g0';
-      $where = "\n  " . 'WHERE g0.img_title=\'' . $db->escape($folders[0]) . '\'';
-      foreach ( $folders as $i => $folder )
-      {
-        if ( $i == 0 )
-          continue;
-        $i_dec = $i - 1;
-        $folder = $db->escape($folder);
-        $sql .= "\n  LEFT JOIN ".table_prefix."gallery AS g{$i}\n    ON ( g{$i}.img_id=g{$i_dec}.folder_parent AND g{$i}.img_title='$folder' )";
-        $where .= "\n    ".'AND g'.$i.'.img_id IS NOT NULL';
-      }
-      $where .= "\n    AND g{$i}.folder_parent IS NULL";
-      $sql .= $where . ';';
-      
-      if ( !$db->sql_query($sql) )
-      {
-        $db->_die('The image metadata could not be loaded.');
-      }
-      
-      if ( $db->numrows() > 0 )
-      {
-        $found_image_id = true;
-        $row = $db->fetchrow();
-        $db->free_result();
-        $idcache[$imagename] = $row['img_id'];
-        $filename =& $idcache[$imagename];
-      }
-    }
-    
-    if ( !$found_image_id )
-    {
-      $text = str_replace($full_tag, '[[' . makeUrlNS('Gallery', $imagename) . ']]', $text);
-      continue;
-    }
-    
-    if ( $scale_type == '|thumb' )
-    {
-      $r_width  = 225;
-      $r_height = 225;
-      
-      $url = makeUrlNS('Special', 'GalleryFetcher/embed/' . $filename, 'width=' . $r_width . '&height=' . $r_height, true);
-    }
-    else if ( !empty($width) && !empty($height) )
-    {
-      $r_width = $width;
-      $r_height = $height;
-      
-      $url = makeUrlNS('Special', 'GalleryFetcher/embed/' . $filename, 'width=' . $r_width . '&height=' . $r_height, true);
-    }
-    else
-    {
-      $url = makeUrlNS('Special', 'GalleryFetcher/' . $filename);
-    }
-    
-    $img_tag = '<img src="' . $url . '" ';
-    
-    // if ( isset($r_width) && isset($r_height) && $scale_type != '|thumb' )
-    // {
-    //   $img_tag .= 'width="' . $r_width . '" height="' . $r_height . '" ';
-    // }
-    
-    $img_tag .= 'style="border-width: 0px; /* background-color: white; */" ';
-    
-    $code = $plugins->setHook('snapr_img_tag_parse_img');
-    foreach ( $code as $cmd )
-    {
-      eval($cmd);
-    }
-    
-    $img_tag .= '/>';
-    
-    $complete_tag = '';
-    
-    if ( !empty($scale_type) && $caption != '|raw' )
-    {
-      $complete_tag .= '<div class="thumbnail" ';
-      $clear_text = '';
-      if ( !empty($clear) )
-      {
-        $side = ( $clear == '|left' ) ? 'left' : 'right';
-        $opposite = ( $clear == '|left' ) ? 'right' : 'left';
-        $clear_text .= "float: $side; margin-$opposite: 20px; width: {$r_width}px;";
-        $complete_tag .= 'style="' . $clear_text . '" ';
-      }
-      $complete_tag .= '>';
-      
-      $complete_tag .= '<a href="' . makeUrlNS('Gallery', $filename) . '" style="display: block;">';
-      $complete_tag .= $img_tag;
-      $complete_tag .= '</a>';
-      
-      $mag_button = '<a href="' . makeUrlNS('Gallery', $filename) . '" style="display: block; float: right; clear: right; margin: 0 0 10px 10px;"><img alt="[ + ]" src="' . scriptPath . '/images/thumbnail.png" style="border-width: 0px;" /></a>';
-    
-      if ( !empty($caption) )
-      {
-        $cap = substr($caption, 1);
-        $complete_tag .= $mag_button . $cap;
-      }
-      
-      $complete_tag .= '</div>';
-    }
-    else if ( $caption == '|raw' )
-    {
-      $complete_tag .= "$img_tag";
-      $taglist[$i] = $complete_tag;
-      
-      $repl = "{$s_delim}e_img_{$i}{$f_delim}";
-      $text = str_replace($full_tag, $repl, $text);
-      continue;
-    }
-    else
-    {
-      $complete_tag .= '<a href="' . makeUrlNS('Gallery', $filename) . '" style="display: block;"';
-      $code = $plugins->setHook('snapr_img_tag_parse_link');
-      foreach ( $code as $cmd )
-      {
-        eval($cmd);
-      }
-      $complete_tag .= '>';
-      $complete_tag .= $img_tag;
-      $complete_tag .= '</a>';
-    }
-    
-    $complete_tag .= "\n\n";
-    $taglist[$i] = $complete_tag;
-    
-    $pos = strpos($text, $full_tag);
-    
-    while(true)
-    {
-      $check1 = substr($text, $pos, 3);
-      $check2 = substr($text, $pos, 1);
-      if ( $check1 == '<p>' || $pos == 0 || $check2 == "\n" )
-      {
-        // die('found at pos '.$pos);
-        break;
-      }
-      $pos--;
-    }
-    
-    $repl = "{$s_delim}e_img_{$i}{$f_delim}";
-    $text = substr($text, 0, $pos) . $repl . substr($text, $pos);
-    
-    $text = str_replace($full_tag, '', $text);
-    
-    unset($full_tag, $filename, $scale_type, $width, $height, $clear, $caption, $r_width, $r_height);
-    
-  }
-  
-  return $text;
+	global $db, $session, $paths, $template, $plugins; // Common objects
+	
+	static $idcache = array();
+	
+	$s_delim = "\xFF";
+	$f_delim = "\xFF";
+	$taglist = array();
+	
+	// Wicked huh?
+	$regex = '/\[\[:' . str_replace('/', '\\/', preg_quote($paths->nslist['Gallery'])) . '([\w\s0-9_\(\)!@%\^\+\|\.-]+?)((\|thumb)|(\|([0-9]+)x([0-9]+)))?(\|left|\|right)?(\|raw|\|(.+))?\]\]/i';
+	
+	preg_match_all($regex, $text, $matches);
+	
+	foreach ( $matches[0] as $i => $match )
+	{
+		$full_tag   =& $matches[0][$i];
+		$imagename  =& $matches[1][$i];
+		$scale_type =& $matches[2][$i];
+		$width      =& $matches[5][$i];
+		$height     =& $matches[6][$i];
+		$clear      =& $matches[7][$i];
+		$caption    =& $matches[8][$i];
+		
+		// determine the image name
+		$imagename = sanitize_page_id($imagename);
+		if ( isset($idcache[$imagename]) )
+		{
+			$found_image_id = true;
+			$filename =& $idcache[$imagename];
+		}
+		else
+		{
+			$found_image_id = false;
+			// get the image ID
+			// Ech... he sent us a string... parse it and see what we get
+			if ( strstr($imagename, '/') )
+			{
+				$folders = explode('/', $imagename);
+			}
+			else
+			{
+				$folders = array($imagename);
+			}
+			foreach ( $folders as $i => $_crap )
+			{
+				$folder =& $folders[$i];
+				$folder = dirtify_page_id($folder);
+				$folder = str_replace('_', ' ', $folder);
+			}
+			unset($folder);
+			
+			$folders = array_reverse($folders);
+			// This is one of the best MySQL tricks on the market. We're going to reverse-travel a folder path using LEFT JOIN and the incredible power of metacoded SQL
+			$sql = 'SELECT g0.img_id, g0.img_title, g0.img_desc, g0.print_sizes, g0.img_time_upload, g0.img_time_mod, g0.img_filename, g0.folder_parent, g0.img_tags FROM '.table_prefix.'gallery AS g0';
+			$where = "\n  " . 'WHERE g0.img_title=\'' . $db->escape($folders[0]) . '\'';
+			foreach ( $folders as $i => $folder )
+			{
+				if ( $i == 0 )
+					continue;
+				$i_dec = $i - 1;
+				$folder = $db->escape($folder);
+				$sql .= "\n  LEFT JOIN ".table_prefix."gallery AS g{$i}\n    ON ( g{$i}.img_id=g{$i_dec}.folder_parent AND g{$i}.img_title='$folder' )";
+				$where .= "\n    ".'AND g'.$i.'.img_id IS NOT NULL';
+			}
+			$where .= "\n    AND g{$i}.folder_parent IS NULL";
+			$sql .= $where . ';';
+			
+			if ( !$db->sql_query($sql) )
+			{
+				$db->_die('The image metadata could not be loaded.');
+			}
+			
+			if ( $db->numrows() > 0 )
+			{
+				$found_image_id = true;
+				$row = $db->fetchrow();
+				$db->free_result();
+				$idcache[$imagename] = $row['img_id'];
+				$filename =& $idcache[$imagename];
+			}
+		}
+		
+		if ( !$found_image_id )
+		{
+			$text = str_replace($full_tag, '[[' . makeUrlNS('Gallery', $imagename) . ']]', $text);
+			continue;
+		}
+		
+		if ( $scale_type == '|thumb' )
+		{
+			$r_width  = 225;
+			$r_height = 225;
+			
+			$url = makeUrlNS('Special', 'GalleryFetcher/embed/' . $filename, 'width=' . $r_width . '&height=' . $r_height, true);
+		}
+		else if ( !empty($width) && !empty($height) )
+		{
+			$r_width = $width;
+			$r_height = $height;
+			
+			$url = makeUrlNS('Special', 'GalleryFetcher/embed/' . $filename, 'width=' . $r_width . '&height=' . $r_height, true);
+		}
+		else
+		{
+			$url = makeUrlNS('Special', 'GalleryFetcher/' . $filename);
+		}
+		
+		$img_tag = '<img src="' . $url . '" ';
+		
+		// if ( isset($r_width) && isset($r_height) && $scale_type != '|thumb' )
+		// {
+		//   $img_tag .= 'width="' . $r_width . '" height="' . $r_height . '" ';
+		// }
+		
+		$img_tag .= 'style="border-width: 0px; /* background-color: white; */" ';
+		
+		$code = $plugins->setHook('snapr_img_tag_parse_img');
+		foreach ( $code as $cmd )
+		{
+			eval($cmd);
+		}
+		
+		$img_tag .= '/>';
+		
+		$complete_tag = '';
+		
+		if ( !empty($scale_type) && $caption != '|raw' )
+		{
+			$complete_tag .= '<div class="thumbnail" ';
+			$clear_text = '';
+			if ( !empty($clear) )
+			{
+				$side = ( $clear == '|left' ) ? 'left' : 'right';
+				$opposite = ( $clear == '|left' ) ? 'right' : 'left';
+				$clear_text .= "float: $side; margin-$opposite: 20px; width: {$r_width}px;";
+				$complete_tag .= 'style="' . $clear_text . '" ';
+			}
+			$complete_tag .= '>';
+			
+			$complete_tag .= '<a href="' . makeUrlNS('Gallery', $filename) . '" style="display: block;">';
+			$complete_tag .= $img_tag;
+			$complete_tag .= '</a>';
+			
+			$mag_button = '<a href="' . makeUrlNS('Gallery', $filename) . '" style="display: block; float: right; clear: right; margin: 0 0 10px 10px;"><img alt="[ + ]" src="' . scriptPath . '/images/thumbnail.png" style="border-width: 0px;" /></a>';
+		
+			if ( !empty($caption) )
+			{
+				$cap = substr($caption, 1);
+				$complete_tag .= $mag_button . $cap;
+			}
+			
+			$complete_tag .= '</div>';
+		}
+		else if ( $caption == '|raw' )
+		{
+			$complete_tag .= "$img_tag";
+			$taglist[$i] = $complete_tag;
+			
+			$repl = "{$s_delim}e_img_{$i}{$f_delim}";
+			$text = str_replace($full_tag, $repl, $text);
+			continue;
+		}
+		else
+		{
+			$complete_tag .= '<a href="' . makeUrlNS('Gallery', $filename) . '" style="display: block;"';
+			$code = $plugins->setHook('snapr_img_tag_parse_link');
+			foreach ( $code as $cmd )
+			{
+				eval($cmd);
+			}
+			$complete_tag .= '>';
+			$complete_tag .= $img_tag;
+			$complete_tag .= '</a>';
+		}
+		
+		$complete_tag .= "\n\n";
+		$taglist[$i] = $complete_tag;
+		
+		$pos = strpos($text, $full_tag);
+		
+		while(true)
+		{
+			$check1 = substr($text, $pos, 3);
+			$check2 = substr($text, $pos, 1);
+			if ( $check1 == '<p>' || $pos == 0 || $check2 == "\n" )
+			{
+				// die('found at pos '.$pos);
+				break;
+			}
+			$pos--;
+		}
+		
+		$repl = "{$s_delim}e_img_{$i}{$f_delim}";
+		$text = substr($text, 0, $pos) . $repl . substr($text, $pos);
+		
+		$text = str_replace($full_tag, '', $text);
+		
+		unset($full_tag, $filename, $scale_type, $width, $height, $clear, $caption, $r_width, $r_height);
+		
+	}
+	
+	return $text;
 }
 
 /**
@@ -246,14 +246,14 @@
  
 function snapr_image_tags_stage2($text, $taglist)
 {
-  $s_delim = "\xFF";
-  $f_delim = "\xFF";
-  foreach ( $taglist as $i => $tag )
-  {
-    $repl = "{$s_delim}e_img_{$i}{$f_delim}";
-    $text = str_replace($repl, $tag, $text);
-  }               
-  return $text;
+	$s_delim = "\xFF";
+	$f_delim = "\xFF";
+	foreach ( $taglist as $i => $tag )
+	{
+		$repl = "{$s_delim}e_img_{$i}{$f_delim}";
+		$text = str_replace($repl, $tag, $text);
+	}               
+	return $text;
 }
 
 ?>
--- a/plugins/gallery/nssetup.php	Sat Aug 21 23:25:41 2010 -0400
+++ b/plugins/gallery/nssetup.php	Sat Aug 21 23:32:06 2010 -0400
@@ -16,17 +16,17 @@
 
 function gallery_setup_namespace(&$paths)
 {
-  global $db, $session, $paths, $template, $plugins; // Common objects
-  
-  $paths->create_namespace('Gallery', 'Image:');
-  
-  $session->register_acl_type('gal_full_res', AUTH_ALLOW, 'View image at full resolution', array('read'), 'Gallery');
-  $session->register_acl_type('snapr_add_tag', AUTH_DISALLOW, 'Add image tags (separate from adding normal tags)', array('read'), 'Gallery');
-  
-  $session->acl_extend_scope('read',                   'Gallery', $paths);
-  $session->acl_extend_scope('post_comments',          'Gallery', $paths);
-  $session->acl_extend_scope('edit_comments',          'Gallery', $paths);
-  $session->acl_extend_scope('mod_comments',           'Gallery', $paths);
+	global $db, $session, $paths, $template, $plugins; // Common objects
+	
+	$paths->create_namespace('Gallery', 'Image:');
+	
+	$session->register_acl_type('gal_full_res', AUTH_ALLOW, 'View image at full resolution', array('read'), 'Gallery');
+	$session->register_acl_type('snapr_add_tag', AUTH_DISALLOW, 'Add image tags (separate from adding normal tags)', array('read'), 'Gallery');
+	
+	$session->acl_extend_scope('read',                   'Gallery', $paths);
+	$session->acl_extend_scope('post_comments',          'Gallery', $paths);
+	$session->acl_extend_scope('edit_comments',          'Gallery', $paths);
+	$session->acl_extend_scope('mod_comments',           'Gallery', $paths);
 }
 
 ?>
--- a/plugins/gallery/search.php	Sat Aug 21 23:25:41 2010 -0400
+++ b/plugins/gallery/search.php	Sat Aug 21 23:32:06 2010 -0400
@@ -20,167 +20,167 @@
 $plugins->attachHook('search_global_inner', 'snapr_search_new_api($query, $query_phrase, $scores, $page_data, $case_sensitive, $word_list);');
 
 $plugins->attachHook('compile_template', '
-  // CSS for gallery browser
-  $template->add_header(\'<link rel="stylesheet" href="' . scriptPath . '/plugins/gallery/browser.css" type="text/css" />\');
-  $template->add_header(\'<link rel="stylesheet" href="' . scriptPath . '/plugins/gallery/dropdown.css" type="text/css" />\');
-  ');
+	// CSS for gallery browser
+	$template->add_header(\'<link rel="stylesheet" href="' . scriptPath . '/plugins/gallery/browser.css" type="text/css" />\');
+	$template->add_header(\'<link rel="stylesheet" href="' . scriptPath . '/plugins/gallery/dropdown.css" type="text/css" />\');
+	');
 
 function gal_searcher($q, $offset)
 {
-  global $db, $session, $paths, $template, $plugins; // Common objects
-  if ( defined('SNAPR_SEARCH_USING_NEW_API') || version_compare(enano_version(true), '1.0.2', '>=') )
-    return false;
-  
-  $fulltext_col = 'MATCH(img_title, img_desc) AGAINST (\'' . $db->escape($q) . '\' IN BOOLEAN MODE)';
-  $sql = "SELECT img_id, img_title, img_desc, is_folder, $fulltext_col AS score, CHAR_LENGTH(img_desc) AS length FROM ".table_prefix."gallery
-              WHERE $fulltext_col > 0
-                AND ( ( is_folder=1 AND folder_parent IS NULL ) OR is_folder!=1 )
-              ORDER BY is_folder DESC, score DESC, img_title ASC;";
-  if ( !$db->sql_unbuffered_query($sql) )
-  {
-    echo $db->get_error();
-    return false;
-  }
-  echo "<h3>Image results</h3>";
-  if ( $row = $db->fetchrow() )
-  {
-    echo '<ul class="snapr-gallery">';
-    $renderer = new SnaprFormatter();
-    $fullpage = $paths->fullpage;
-    $paths->fullpage = $paths->nslist['Special'] . 'Gallery';
-    do
-    {
-      echo $renderer->render(false, $row, false);
-    }
-    while ( $row = $db->fetchrow() );
-    $paths->fullpage = $fullpage;
-    echo '</ul><span class="menuclear"></span>';
-  }
-  else
-  {
-    echo '<p>No image results.</p>';
-  }
+	global $db, $session, $paths, $template, $plugins; // Common objects
+	if ( defined('SNAPR_SEARCH_USING_NEW_API') || version_compare(enano_version(true), '1.0.2', '>=') )
+		return false;
+	
+	$fulltext_col = 'MATCH(img_title, img_desc) AGAINST (\'' . $db->escape($q) . '\' IN BOOLEAN MODE)';
+	$sql = "SELECT img_id, img_title, img_desc, is_folder, $fulltext_col AS score, CHAR_LENGTH(img_desc) AS length FROM ".table_prefix."gallery
+							WHERE $fulltext_col > 0
+								AND ( ( is_folder=1 AND folder_parent IS NULL ) OR is_folder!=1 )
+							ORDER BY is_folder DESC, score DESC, img_title ASC;";
+	if ( !$db->sql_unbuffered_query($sql) )
+	{
+		echo $db->get_error();
+		return false;
+	}
+	echo "<h3>Image results</h3>";
+	if ( $row = $db->fetchrow() )
+	{
+		echo '<ul class="snapr-gallery">';
+		$renderer = new SnaprFormatter();
+		$fullpage = $paths->fullpage;
+		$paths->fullpage = $paths->nslist['Special'] . 'Gallery';
+		do
+		{
+			echo $renderer->render(false, $row, false);
+		}
+		while ( $row = $db->fetchrow() );
+		$paths->fullpage = $fullpage;
+		echo '</ul><span class="menuclear"></span>';
+	}
+	else
+	{
+		echo '<p>No image results.</p>';
+	}
 }
 
 function snapr_search_new_api(&$query, &$query_phrase, &$scores, &$page_data, &$case_sensitive, &$word_list)
 {
-  global $db, $session, $paths, $template, $plugins; // Common objects
-  
-  if ( !defined('SNAPR_SEARCH_USING_NEW_API') )
-    define('SNAPR_SEARCH_USING_NEW_API', 1);
-  
-  // Let's do this all in one query
-  $terms = array(
-      'any' => array_merge($query['any'], $query_phrase['any']),
-      'req' => array_merge($query['req'], $query_phrase['req']),
-      'not' => $query['not']
-    );
-  $where = array('any' => array(), 'req' => array(), 'not' => array());
-  $where_any =& $where['any'];
-  $where_req =& $where['req'];
-  $where_not =& $where['not'];
-  $title_col = ( $case_sensitive ) ? 'img_title' : 'lcase(img_title)';
-  $desc_col = ( $case_sensitive ) ? 'img_desc' : 'lcase(img_desc)';
-  foreach ( $terms['any'] as $term )
-  {
-    $term = escape_string_like($term);
-    if ( !$case_sensitive )
-      $term = strtolower($term);
-    $where_any[] = "( $title_col LIKE '%{$term}%' OR $desc_col LIKE '%{$term}%' )";
-  }
-  foreach ( $terms['req'] as $term )
-  {
-    $term = escape_string_like($term);
-    if ( !$case_sensitive )
-      $term = strtolower($term);
-    $where_req[] = "( $title_col LIKE '%{$term}%' OR $desc_col LIKE '%{$term}%' )";
-  }
-  foreach ( $terms['not'] as $term )
-  {
-    $term = escape_string_like($term);
-    if ( !$case_sensitive )
-      $term = strtolower($term);
-    $where_not[] = "$title_col NOT LIKE '%{$term}%' AND $desc_col NOT LIKE '%{$term}%'";
-  }
-  if ( empty($where_any) )
-    unset($where_any, $where['any']);
-  if ( empty($where_req) )
-    unset($where_req, $where['req']);
-  if ( empty($where_not) )
-    unset($where_not, $where['not']);
-  
-  $where_any = '(' . implode(' OR ', $where_any) . '' . ( isset($where['req']) || isset($where['not']) ? ' OR 1 = 1' : '' ) . ')';
-  
-  if ( isset($where_req) )
-    $where_req = implode(' AND ', $where_req);
-  if ( isset($where_not) )
-  $where_not = implode( 'AND ', $where_not);
-  
-  $where = implode(' AND ', $where);
-  $sql = "SELECT img_id, img_title, img_desc FROM " . table_prefix . "gallery WHERE ( $where ) AND is_folder = 0;";
-  
-  if ( !($q = $db->sql_unbuffered_query($sql)) )
-  {
-    $db->_die('Error is in auto-generated SQL query in the Snapr plugin search module');
-  }
-  
-  if ( $row = $db->fetchrow() )
-  {
-    do
-    {
-      $idstring = 'ns=Gallery;pid=' . $row['img_id'];
-      foreach ( $word_list as $term )
-      {
-        if ( $case_sensitive )
-        {
-          if ( strstr($row['img_title'], $term) || strstr($row['img_desc'], $term) )
-          {
-            ( isset($scores[$idstring]) ) ? $scores[$idstring]++ : $scores[$idstring] = 1;
-          }
-        }
-        else
-        {
-          if ( strstr(strtolower($row['img_title']), strtolower($term)) || strstr(strtolower($row['img_desc']), strtolower($term)) )
-          {
-            ( isset($scores[$idstring]) ) ? $scores[$idstring]++ : $scores[$idstring] = 1;
-          }
-        }
-      }
-      // Generate text...
-      $text = highlight_and_clip_search_result(htmlspecialchars($row['img_desc']), $word_list);
-      
-      $preview_and_text = '
-        <table border="0" width="100%" cellspacing="0" cellpadding="0">
-          <tr>
-            <td valign="top">
-              ' . $text . '
-            </td>
-            <td valign="top" style="text-align: right; width: 80px; padding-left: 10px;">
-              <a href="' . makeUrlNS('Gallery', $row['img_id']) . '"><img alt="[thumbnail]" src="' . makeUrlNS('Special', "GalleryFetcher/thumb/{$row['img_id']}") . '" /></a>
-            </td>
-          </tr>
-        </table>
-      ';
-      
-      // Inject result
-      
-      if ( isset($scores[$idstring]) )
-      {
-        // echo('adding image "' . $row['img_title'] . '" to results<br />');
-        $page_data[$idstring] = array(
-          'page_name' => highlight_search_result(htmlspecialchars($row['img_title']), $word_list),
-          'page_text' => $preview_and_text,
-          'score' => $scores[$idstring],
-          'page_note' => '[Gallery image]',
-          'page_id' => strval($row['img_id']),
-          'namespace' => 'Gallery',
-          'page_length' => strlen($row['img_desc']),
-        );
-      }
-    }
-    while ( $row = $db->fetchrow() );
-    
-  }
+	global $db, $session, $paths, $template, $plugins; // Common objects
+	
+	if ( !defined('SNAPR_SEARCH_USING_NEW_API') )
+		define('SNAPR_SEARCH_USING_NEW_API', 1);
+	
+	// Let's do this all in one query
+	$terms = array(
+			'any' => array_merge($query['any'], $query_phrase['any']),
+			'req' => array_merge($query['req'], $query_phrase['req']),
+			'not' => $query['not']
+		);
+	$where = array('any' => array(), 'req' => array(), 'not' => array());
+	$where_any =& $where['any'];
+	$where_req =& $where['req'];
+	$where_not =& $where['not'];
+	$title_col = ( $case_sensitive ) ? 'img_title' : 'lcase(img_title)';
+	$desc_col = ( $case_sensitive ) ? 'img_desc' : 'lcase(img_desc)';
+	foreach ( $terms['any'] as $term )
+	{
+		$term = escape_string_like($term);
+		if ( !$case_sensitive )
+			$term = strtolower($term);
+		$where_any[] = "( $title_col LIKE '%{$term}%' OR $desc_col LIKE '%{$term}%' )";
+	}
+	foreach ( $terms['req'] as $term )
+	{
+		$term = escape_string_like($term);
+		if ( !$case_sensitive )
+			$term = strtolower($term);
+		$where_req[] = "( $title_col LIKE '%{$term}%' OR $desc_col LIKE '%{$term}%' )";
+	}
+	foreach ( $terms['not'] as $term )
+	{
+		$term = escape_string_like($term);
+		if ( !$case_sensitive )
+			$term = strtolower($term);
+		$where_not[] = "$title_col NOT LIKE '%{$term}%' AND $desc_col NOT LIKE '%{$term}%'";
+	}
+	if ( empty($where_any) )
+		unset($where_any, $where['any']);
+	if ( empty($where_req) )
+		unset($where_req, $where['req']);
+	if ( empty($where_not) )
+		unset($where_not, $where['not']);
+	
+	$where_any = '(' . implode(' OR ', $where_any) . '' . ( isset($where['req']) || isset($where['not']) ? ' OR 1 = 1' : '' ) . ')';
+	
+	if ( isset($where_req) )
+		$where_req = implode(' AND ', $where_req);
+	if ( isset($where_not) )
+	$where_not = implode( 'AND ', $where_not);
+	
+	$where = implode(' AND ', $where);
+	$sql = "SELECT img_id, img_title, img_desc FROM " . table_prefix . "gallery WHERE ( $where ) AND is_folder = 0;";
+	
+	if ( !($q = $db->sql_unbuffered_query($sql)) )
+	{
+		$db->_die('Error is in auto-generated SQL query in the Snapr plugin search module');
+	}
+	
+	if ( $row = $db->fetchrow() )
+	{
+		do
+		{
+			$idstring = 'ns=Gallery;pid=' . $row['img_id'];
+			foreach ( $word_list as $term )
+			{
+				if ( $case_sensitive )
+				{
+					if ( strstr($row['img_title'], $term) || strstr($row['img_desc'], $term) )
+					{
+						( isset($scores[$idstring]) ) ? $scores[$idstring]++ : $scores[$idstring] = 1;
+					}
+				}
+				else
+				{
+					if ( strstr(strtolower($row['img_title']), strtolower($term)) || strstr(strtolower($row['img_desc']), strtolower($term)) )
+					{
+						( isset($scores[$idstring]) ) ? $scores[$idstring]++ : $scores[$idstring] = 1;
+					}
+				}
+			}
+			// Generate text...
+			$text = highlight_and_clip_search_result(htmlspecialchars($row['img_desc']), $word_list);
+			
+			$preview_and_text = '
+				<table border="0" width="100%" cellspacing="0" cellpadding="0">
+					<tr>
+						<td valign="top">
+							' . $text . '
+						</td>
+						<td valign="top" style="text-align: right; width: 80px; padding-left: 10px;">
+							<a href="' . makeUrlNS('Gallery', $row['img_id']) . '"><img alt="[thumbnail]" src="' . makeUrlNS('Special', "GalleryFetcher/thumb/{$row['img_id']}") . '" /></a>
+						</td>
+					</tr>
+				</table>
+			';
+			
+			// Inject result
+			
+			if ( isset($scores[$idstring]) )
+			{
+				// echo('adding image "' . $row['img_title'] . '" to results<br />');
+				$page_data[$idstring] = array(
+					'page_name' => highlight_search_result(htmlspecialchars($row['img_title']), $word_list),
+					'page_text' => $preview_and_text,
+					'score' => $scores[$idstring],
+					'page_note' => '[Gallery image]',
+					'page_id' => strval($row['img_id']),
+					'namespace' => 'Gallery',
+					'page_length' => strlen($row['img_desc']),
+				);
+			}
+		}
+		while ( $row = $db->fetchrow() );
+		
+	}
 }
 
 ?>
--- a/plugins/gallery/sidebar.php	Sat Aug 21 23:25:41 2010 -0400
+++ b/plugins/gallery/sidebar.php	Sat Aug 21 23:32:06 2010 -0400
@@ -18,48 +18,48 @@
 
 function gal_sidebar_block()
 {
-  global $db, $session, $paths, $template, $plugins; // Common objects
-  
-  $q = $db->sql_query('SELECT img_id,img_title FROM '.table_prefix.'gallery WHERE is_folder=0;');
-  if ( !$q )
-    $db->_die();
-  
-  $images = array();
-  while ( $row = $db->fetchrow() )
-  {
-    $id = intval($row['img_id']);
-    $images[$id] = $row['img_title'];
-  }
-  
-  // Loop through all gallery images until we find one we can read (typically on the first try, but you never know...)
-  $my_image = false;
-  while ( count($images) > 0 )
-  {
-    $rand = array_rand($images);
-    $image = $images[$rand];
-    $acl = $session->fetch_page_acl(strval($rand), 'Gallery');
-    if ( is_object($acl) && $acl->get_permissions('read') )
-    {
-      $my_image = $image;
-      break;
-    }
-    unset($images[$rand]);
-  }
-  if ( $my_image )
-  {
-    // Generate sidebar HTML
-    $image_link = '<div style="padding: 5px; text-align: center;">
-                     <a href="' . makeUrlNS('Gallery', $rand) . '">
-                       <img alt="&lt;thumbnail&gt;" src="' . makeUrlNS('Special', 'GalleryFetcher/thumb/' . $rand) . '" style="border-width: 0; display: block; margin: 0 auto 5px auto;" />
-                       <span style="color: black;">' . htmlspecialchars($my_image) . '</span>
-                     </a>
-                   </div>';
-  }
-  else
-  {
-    $image_link = 'No images in the gallery.';
-  }
-  $template->sidebar_widget('Random image', $image_link);
+	global $db, $session, $paths, $template, $plugins; // Common objects
+	
+	$q = $db->sql_query('SELECT img_id,img_title FROM '.table_prefix.'gallery WHERE is_folder=0;');
+	if ( !$q )
+		$db->_die();
+	
+	$images = array();
+	while ( $row = $db->fetchrow() )
+	{
+		$id = intval($row['img_id']);
+		$images[$id] = $row['img_title'];
+	}
+	
+	// Loop through all gallery images until we find one we can read (typically on the first try, but you never know...)
+	$my_image = false;
+	while ( count($images) > 0 )
+	{
+		$rand = array_rand($images);
+		$image = $images[$rand];
+		$acl = $session->fetch_page_acl(strval($rand), 'Gallery');
+		if ( is_object($acl) && $acl->get_permissions('read') )
+		{
+			$my_image = $image;
+			break;
+		}
+		unset($images[$rand]);
+	}
+	if ( $my_image )
+	{
+		// Generate sidebar HTML
+		$image_link = '<div style="padding: 5px; text-align: center;">
+ 										<a href="' . makeUrlNS('Gallery', $rand) . '">
+ 											<img alt="&lt;thumbnail&gt;" src="' . makeUrlNS('Special', 'GalleryFetcher/thumb/' . $rand) . '" style="border-width: 0; display: block; margin: 0 auto 5px auto;" />
+ 											<span style="color: black;">' . htmlspecialchars($my_image) . '</span>
+ 										</a>
+ 									</div>';
+	}
+	else
+	{
+		$image_link = 'No images in the gallery.';
+	}
+	$template->sidebar_widget('Random image', $image_link);
 }
 
 $plugins->attachHook('compile_template', 'gal_sidebar_block();');
--- a/plugins/gallery/tagging.js	Sat Aug 21 23:25:41 2010 -0400
+++ b/plugins/gallery/tagging.js	Sat Aug 21 23:32:06 2010 -0400
@@ -1,368 +1,368 @@
 function snapr_add_tag()
 {
-  var image = document.getElementById('snapr_preview_img');
-  image.parentNode.onclick = function(e)
-  {
-    canvas_click(this);
-  }
-  image.parentNode.setAttribute('canvas:oncomplete', 'snapr_process_canvas_add(this);');
-  image.parentNode.setAttribute('canvas:oncancel', 'obj.onclick = null;');
+	var image = document.getElementById('snapr_preview_img');
+	image.parentNode.onclick = function(e)
+	{
+		canvas_click(this);
+	}
+	image.parentNode.setAttribute('canvas:oncomplete', 'snapr_process_canvas_add(this);');
+	image.parentNode.setAttribute('canvas:oncancel', 'obj.onclick = null;');
 }
 
 function snapr_process_canvas_add(obj, tag_data)
 {
-  obj.onclick = null;
-  var abs_x = $dynano(obj).Left() + obj.canvas.left;
-  var abs_y = $dynano(obj).Top()  + obj.canvas.top;
-  var height = obj.canvas.height + 2;
-  
-  var entry_div = document.createElement('div');
-  entry_div.className = 'snapr_tag_entry';
-  entry_div.style.position = 'absolute';
-  entry_div.style.top = String(abs_y + height) + 'px';
-  entry_div.style.left = String(abs_x)+ 'px';
-  
-  entry_div.appendChild(document.createTextNode('Enter a tag:'));
-  entry_div.appendChild(document.createElement('br'));
-  
-  var ta = document.createElement('textarea');
-  ta.rows = '7';
-  ta.cols = '30';
-  if ( typeof(tag_data) == 'string' )
-    ta.value = tag_data;
-  entry_div.appendChild(ta);
-  
-  entry_div.appendChild(document.createElement('br'));
-  
-  var a_add = document.createElement('a');
-  a_add.href = '#';
-  a_add.onclick = function()
-  {
-    var el = this.previousSibling;
-    while ( el.tagName != 'TEXTAREA' )
-      el = el.previousSibling;
-    
-    snapr_finalize_canvas_add(this.parentNode, this.parentNode.parentNode.canvas, el.value);
-    return false;
-  }
-  a_add.appendChild(document.createTextNode('Add tag'));
-  entry_div.appendChild(a_add);
-  
-  entry_div.appendChild(document.createTextNode(' | '));
-  
-  var a_cancel = document.createElement('a');
-  a_cancel.href = '#';
-  a_cancel.onclick = function()
-  {
-    snapr_finalize_canvas_cancel(this.parentNode);
-    return false;
-  }
-  a_cancel.appendChild(document.createTextNode('Cancel'));
-  entry_div.appendChild(a_cancel);
-  
-  obj.appendChild(entry_div);
-  ta.focus();
+	obj.onclick = null;
+	var abs_x = $dynano(obj).Left() + obj.canvas.left;
+	var abs_y = $dynano(obj).Top()  + obj.canvas.top;
+	var height = obj.canvas.height + 2;
+	
+	var entry_div = document.createElement('div');
+	entry_div.className = 'snapr_tag_entry';
+	entry_div.style.position = 'absolute';
+	entry_div.style.top = String(abs_y + height) + 'px';
+	entry_div.style.left = String(abs_x)+ 'px';
+	
+	entry_div.appendChild(document.createTextNode('Enter a tag:'));
+	entry_div.appendChild(document.createElement('br'));
+	
+	var ta = document.createElement('textarea');
+	ta.rows = '7';
+	ta.cols = '30';
+	if ( typeof(tag_data) == 'string' )
+		ta.value = tag_data;
+	entry_div.appendChild(ta);
+	
+	entry_div.appendChild(document.createElement('br'));
+	
+	var a_add = document.createElement('a');
+	a_add.href = '#';
+	a_add.onclick = function()
+	{
+		var el = this.previousSibling;
+		while ( el.tagName != 'TEXTAREA' )
+			el = el.previousSibling;
+		
+		snapr_finalize_canvas_add(this.parentNode, this.parentNode.parentNode.canvas, el.value);
+		return false;
+	}
+	a_add.appendChild(document.createTextNode('Add tag'));
+	entry_div.appendChild(a_add);
+	
+	entry_div.appendChild(document.createTextNode(' | '));
+	
+	var a_cancel = document.createElement('a');
+	a_cancel.href = '#';
+	a_cancel.onclick = function()
+	{
+		snapr_finalize_canvas_cancel(this.parentNode);
+		return false;
+	}
+	a_cancel.appendChild(document.createTextNode('Cancel'));
+	entry_div.appendChild(a_cancel);
+	
+	obj.appendChild(entry_div);
+	ta.focus();
 }
 
 function snapr_finalize_canvas_add(obj, canvas_data, tag)
 {
-  // add the new box
-  var id = obj.parentNode.getAttribute('snapr:imgid');
-  if ( !id )
-    return false;
-  
-  // destroy form, etc.
-  var parent = obj.parentNode;
-  parent.removeChild(parent.canvas_box_obj);
-  parent.removeChild(obj);
-  
-  var canvas_json = toJSONString(canvas_data);
-  ajaxPost(makeUrlNS('Gallery', id), 'ajax=true&act=add_tag&tag=' + ajaxEscape(tag) + '&canvas_params=' + ajaxEscape(canvas_json), snapr_process_ajax_tag_packet);
+	// add the new box
+	var id = obj.parentNode.getAttribute('snapr:imgid');
+	if ( !id )
+		return false;
+	
+	// destroy form, etc.
+	var parent = obj.parentNode;
+	parent.removeChild(parent.canvas_box_obj);
+	parent.removeChild(obj);
+	
+	var canvas_json = toJSONString(canvas_data);
+	ajaxPost(makeUrlNS('Gallery', id), 'ajax=true&act=add_tag&tag=' + ajaxEscape(tag) + '&canvas_params=' + ajaxEscape(canvas_json), snapr_process_ajax_tag_packet);
 }
 
 function snapr_finalize_canvas_cancel(obj)
 {
-  var parent = obj.parentNode;
-  parent.removeChild(parent.canvas_box_obj);
-  parent.removeChild(obj);
+	var parent = obj.parentNode;
+	parent.removeChild(parent.canvas_box_obj);
+	parent.removeChild(obj);
 }
 
 function snapr_finalize_canvas_edit_cancel(obj)
 {
-  var old_value = obj.getElementsByTagName('textarea')[0];
-  old_value = old_value.OriginalValue;
-  var canvas = obj.parentNode;
-  var canvas_data = canvas.canvas;
-  var note_id = canvas.tag_id;
-  var auth_delete = canvas.auth_delete;
-  var parent = canvas.parentNode;
-  canvas.removeChild(obj);
-  parent.removeChild(canvas);
-  // redraw the note
-  snapr_draw_note(parent, old_value, canvas_data, note_id, false, auth_delete);
+	var old_value = obj.getElementsByTagName('textarea')[0];
+	old_value = old_value.OriginalValue;
+	var canvas = obj.parentNode;
+	var canvas_data = canvas.canvas;
+	var note_id = canvas.tag_id;
+	var auth_delete = canvas.auth_delete;
+	var parent = canvas.parentNode;
+	canvas.removeChild(obj);
+	parent.removeChild(canvas);
+	// redraw the note
+	snapr_draw_note(parent, old_value, canvas_data, note_id, false, auth_delete);
 }
 
 function snapr_finalize_canvas_edit_delete(obj)
 {
-  var old_value = obj.getElementsByTagName('textarea')[0];
-  old_value = old_value.OriginalValue;
-  var canvas = obj.parentNode;
-  var canvas_data = canvas.canvas;
-  var note_id = canvas.tag_id;
-  var auth_delete = canvas.auth_delete;
-  var parent = canvas.parentNode;
-  canvas.removeChild(obj);
-  parent.removeChild(canvas);
-  // redraw the note
-  var note = snapr_draw_note(parent, old_value, canvas_data, note_id, false, auth_delete);
-  // now nuke it
-  snapr_nuke_tag(note);
+	var old_value = obj.getElementsByTagName('textarea')[0];
+	old_value = old_value.OriginalValue;
+	var canvas = obj.parentNode;
+	var canvas_data = canvas.canvas;
+	var note_id = canvas.tag_id;
+	var auth_delete = canvas.auth_delete;
+	var parent = canvas.parentNode;
+	canvas.removeChild(obj);
+	parent.removeChild(canvas);
+	// redraw the note
+	var note = snapr_draw_note(parent, old_value, canvas_data, note_id, false, auth_delete);
+	// now nuke it
+	snapr_nuke_tag(note);
 }
 
 function snapr_draw_note(obj, tag, canvas_data, note_id, initial_hide, auth_delete)
 {
-  var newbox = canvas_create_box(obj, canvas_data.left, canvas_data.top, canvas_data.width, canvas_data.height);
-  newbox.tag_id = note_id;
-  newbox.canvas = canvas_data;
-  newbox.auth_delete = auth_delete;
-  obj.onmouseover = function()
-  {
-    var boxen = this.getElementsByTagName('div');
-    for ( var i = 0; i < boxen.length; i++ )
-      if ( boxen[i].className == 'canvasbox' )
-        boxen[i].style.display = 'block';
-  }
-  obj.onmouseout = function()
-  {
-    var boxen = this.getElementsByTagName('div');
-    for ( var i = 0; i < boxen.length; i++ )
-      if ( boxen[i].className == 'canvasbox' )
-        boxen[i].style.display = 'none';
-  }
-  newbox.onmouseover = function()
-  {
-    this.style.borderColor = '#FFFF00';
-    this.firstChild.style.borderColor = '#000000';
-    snapr_display_note(this.noteObj);
-  }
-  newbox.onmouseout = function()
-  {
-    this.style.borderColor = '#000000';
-    this.firstChild.style.borderColor = '#FFFFFF';
-    snapr_hide_note(this.noteObj);
-  }
-  if ( auth_delete )
-  {
-    /*
-    var p = document.createElement('p');
-    p.style.cssFloat = 'right';
-    p.style.styleFloat = 'right';
-    p.style.fontWeight = 'bold';
-    p.style.margin = '5px';
-    var a_del = document.createElement('a');
-    a_del.style.color = '#FF0000';
-    a_del.href = '#';
-    a_del.onclick = function()
-    {
-      snapr_nuke_tag(this.parentNode.parentNode.parentNode);
-      return false;
-    }
-    a_del.appendChild(document.createTextNode('[X]'));
-    p.appendChild(a_del);
-    newbox.firstChild.appendChild(p);
-    */
-    newbox.style.cursor = 'pointer';
-    newbox.onclick = function()
-    {
-      snapr_run_tag_editor(this);
-    }
-  }
-  var abs_x = $dynano(newbox).Left();
-  var abs_y = $dynano(newbox).Top() + $dynano(newbox).Height() + 2;
-  var noteObj = document.createElement('div');
-  newbox.noteObj = noteObj;
-  noteObj.className = 'snapr_tag';
-  noteObj.style.display = 'none';
-  noteObj.style.position = 'absolute';
-  noteObj.style.top = abs_y + 'px';
-  noteObj.style.left = abs_x + 'px';
-  noteObj.style.zIndex = '100';
-  var re = new RegExp(unescape('%0A'), 'g');
-  noteObj.innerHTML = tag.replace(re, "<br />\n");
-  obj.appendChild(noteObj);
-  if ( initial_hide )
-    newbox.style.display = 'none';
-  return newbox;
+	var newbox = canvas_create_box(obj, canvas_data.left, canvas_data.top, canvas_data.width, canvas_data.height);
+	newbox.tag_id = note_id;
+	newbox.canvas = canvas_data;
+	newbox.auth_delete = auth_delete;
+	obj.onmouseover = function()
+	{
+		var boxen = this.getElementsByTagName('div');
+		for ( var i = 0; i < boxen.length; i++ )
+			if ( boxen[i].className == 'canvasbox' )
+				boxen[i].style.display = 'block';
+	}
+	obj.onmouseout = function()
+	{
+		var boxen = this.getElementsByTagName('div');
+		for ( var i = 0; i < boxen.length; i++ )
+			if ( boxen[i].className == 'canvasbox' )
+				boxen[i].style.display = 'none';
+	}
+	newbox.onmouseover = function()
+	{
+		this.style.borderColor = '#FFFF00';
+		this.firstChild.style.borderColor = '#000000';
+		snapr_display_note(this.noteObj);
+	}
+	newbox.onmouseout = function()
+	{
+		this.style.borderColor = '#000000';
+		this.firstChild.style.borderColor = '#FFFFFF';
+		snapr_hide_note(this.noteObj);
+	}
+	if ( auth_delete )
+	{
+		/*
+		var p = document.createElement('p');
+		p.style.cssFloat = 'right';
+		p.style.styleFloat = 'right';
+		p.style.fontWeight = 'bold';
+		p.style.margin = '5px';
+		var a_del = document.createElement('a');
+		a_del.style.color = '#FF0000';
+		a_del.href = '#';
+		a_del.onclick = function()
+		{
+			snapr_nuke_tag(this.parentNode.parentNode.parentNode);
+			return false;
+		}
+		a_del.appendChild(document.createTextNode('[X]'));
+		p.appendChild(a_del);
+		newbox.firstChild.appendChild(p);
+		*/
+		newbox.style.cursor = 'pointer';
+		newbox.onclick = function()
+		{
+			snapr_run_tag_editor(this);
+		}
+	}
+	var abs_x = $dynano(newbox).Left();
+	var abs_y = $dynano(newbox).Top() + $dynano(newbox).Height() + 2;
+	var noteObj = document.createElement('div');
+	newbox.noteObj = noteObj;
+	noteObj.className = 'snapr_tag';
+	noteObj.style.display = 'none';
+	noteObj.style.position = 'absolute';
+	noteObj.style.top = abs_y + 'px';
+	noteObj.style.left = abs_x + 'px';
+	noteObj.style.zIndex = '100';
+	var re = new RegExp(unescape('%0A'), 'g');
+	noteObj.innerHTML = tag.replace(re, "<br />\n");
+	obj.appendChild(noteObj);
+	if ( initial_hide )
+		newbox.style.display = 'none';
+	return newbox;
 }
 
 function snapr_display_note(note)
 {
-  //domObjChangeOpac(0, note);
-  note.style.display = 'block';
-  //domOpacity(note, 0, 100, 500);
+	//domObjChangeOpac(0, note);
+	note.style.display = 'block';
+	//domOpacity(note, 0, 100, 500);
 }
 
 function snapr_hide_note(note)
 {
-  //domOpacity(note, 100, 0, 500);
-  //setTimeout(function()
-  //  {
-      note.style.display = 'none';
-  //  }, 600);
+	//domOpacity(note, 100, 0, 500);
+	//setTimeout(function()
+	//  {
+			note.style.display = 'none';
+	//  }, 600);
 }
 
 function snapr_nuke_tag(obj)
 {
-  // add the new box
-  var parent_obj = document.getElementById('snapr_preview_img').parentNode;
-  var id = parent_obj.getAttribute('snapr:imgid');
-  if ( !id )
-    return false;
-  ajaxPost(makeUrlNS('Gallery', id), 'ajax=true&act=del_tag&tag_id=' + obj.tag_id, snapr_process_ajax_tag_packet);
+	// add the new box
+	var parent_obj = document.getElementById('snapr_preview_img').parentNode;
+	var id = parent_obj.getAttribute('snapr:imgid');
+	if ( !id )
+		return false;
+	ajaxPost(makeUrlNS('Gallery', id), 'ajax=true&act=del_tag&tag_id=' + obj.tag_id, snapr_process_ajax_tag_packet);
 }
 
 function snapr_run_tag_editor(obj)
 {
-  obj.onclick = null;
-  var abs_x = $dynano(obj).Left();
-  var abs_y = $dynano(obj).Top();
-  var height = $dynano(obj).Height() + 2;
-  
-  var value = obj.nextSibling.innerHTML;
-  var regex = new RegExp('<br>', 'g');
-  value = value.replace(regex, '');
-  obj.parentNode.removeChild(obj.nextSibling);
-  
-  var entry_div = document.createElement('div');
-  entry_div.className = 'snapr_tag_entry';
-  entry_div.style.position = 'absolute';
-  entry_div.style.top = String(height) + 'px';
-  entry_div.style.left = '0px';
-  entry_div.style.zIndex = '100';
-  
-  entry_div.appendChild(document.createTextNode('Enter a tag:'));
-  entry_div.appendChild(document.createElement('br'));
-  
-  var ta = document.createElement('textarea');
-  ta.rows = '7';
-  ta.cols = '30';
-  ta.value = value;
-  ta.style.backgroundColor = '#FFFFFF';
-  ta.style.borderWidth = '0';
-  ta.style.color = '#000000';
-  ta.OriginalValue = value;
-  entry_div.appendChild(ta);
-  
-  entry_div.appendChild(document.createElement('br'));
-  
-  var a_add = document.createElement('a');
-  a_add.href = '#';
-  a_add.onclick = function()
-  {
-    snapr_finalize_canvas_edit(this.parentNode.parentNode, this.parentNode.getElementsByTagName('textarea')[0]['value'], this.parentNode);
-    return false;
-  }
-  a_add.appendChild(document.createTextNode('Save tag'));
-  entry_div.appendChild(a_add);
-  
-  entry_div.appendChild(document.createTextNode(' | '));
-  
-  var a_cancel = document.createElement('a');
-  a_cancel.href = '#';
-  a_cancel.onclick = function()
-  {
-    snapr_finalize_canvas_edit_cancel(this.parentNode);
-    return false;
-  }
-  a_cancel.appendChild(document.createTextNode('Cancel'));
-  entry_div.appendChild(a_cancel);
-  
-  entry_div.appendChild(document.createTextNode(' | '));
-  
-  var a_del = document.createElement('a');
-  a_del.href = '#';
-  a_del.onclick = function()
-  {
-    snapr_finalize_canvas_edit_delete(this.parentNode);
-    return false;
-  }
-  a_del.style.color = '#FF0000';
-  a_del.appendChild(document.createTextNode('Delete'));
-  entry_div.appendChild(a_del);
-  
-  obj.appendChild(entry_div);
-  ta.focus();
+	obj.onclick = null;
+	var abs_x = $dynano(obj).Left();
+	var abs_y = $dynano(obj).Top();
+	var height = $dynano(obj).Height() + 2;
+	
+	var value = obj.nextSibling.innerHTML;
+	var regex = new RegExp('<br>', 'g');
+	value = value.replace(regex, '');
+	obj.parentNode.removeChild(obj.nextSibling);
+	
+	var entry_div = document.createElement('div');
+	entry_div.className = 'snapr_tag_entry';
+	entry_div.style.position = 'absolute';
+	entry_div.style.top = String(height) + 'px';
+	entry_div.style.left = '0px';
+	entry_div.style.zIndex = '100';
+	
+	entry_div.appendChild(document.createTextNode('Enter a tag:'));
+	entry_div.appendChild(document.createElement('br'));
+	
+	var ta = document.createElement('textarea');
+	ta.rows = '7';
+	ta.cols = '30';
+	ta.value = value;
+	ta.style.backgroundColor = '#FFFFFF';
+	ta.style.borderWidth = '0';
+	ta.style.color = '#000000';
+	ta.OriginalValue = value;
+	entry_div.appendChild(ta);
+	
+	entry_div.appendChild(document.createElement('br'));
+	
+	var a_add = document.createElement('a');
+	a_add.href = '#';
+	a_add.onclick = function()
+	{
+		snapr_finalize_canvas_edit(this.parentNode.parentNode, this.parentNode.getElementsByTagName('textarea')[0]['value'], this.parentNode);
+		return false;
+	}
+	a_add.appendChild(document.createTextNode('Save tag'));
+	entry_div.appendChild(a_add);
+	
+	entry_div.appendChild(document.createTextNode(' | '));
+	
+	var a_cancel = document.createElement('a');
+	a_cancel.href = '#';
+	a_cancel.onclick = function()
+	{
+		snapr_finalize_canvas_edit_cancel(this.parentNode);
+		return false;
+	}
+	a_cancel.appendChild(document.createTextNode('Cancel'));
+	entry_div.appendChild(a_cancel);
+	
+	entry_div.appendChild(document.createTextNode(' | '));
+	
+	var a_del = document.createElement('a');
+	a_del.href = '#';
+	a_del.onclick = function()
+	{
+		snapr_finalize_canvas_edit_delete(this.parentNode);
+		return false;
+	}
+	a_del.style.color = '#FF0000';
+	a_del.appendChild(document.createTextNode('Delete'));
+	entry_div.appendChild(a_del);
+	
+	obj.appendChild(entry_div);
+	ta.focus();
 }
 
 function snapr_finalize_canvas_edit(canvas, value, editor)
 {
-  var canvas_data = canvas.canvas;
-  var note_id = canvas.tag_id;
-  var parent = canvas.parentNode;
-  canvas.removeChild(editor);
-  parent.removeChild(canvas);
-  // send the edit across the 'net
-  var parent_obj = document.getElementById('snapr_preview_img').parentNode;
-  var id = parent_obj.getAttribute('snapr:imgid');
-  if ( !id )
-    return false;
-  
-  var canvas_json = toJSONString(canvas_data);
-  ajaxPost(makeUrlNS('Gallery', id), 'ajax=true&act=edit_tag&tag=' + ajaxEscape(value) + '&canvas_params=' + ajaxEscape(canvas_json) + '&tag_id=' + note_id, snapr_process_ajax_tag_packet);
+	var canvas_data = canvas.canvas;
+	var note_id = canvas.tag_id;
+	var parent = canvas.parentNode;
+	canvas.removeChild(editor);
+	parent.removeChild(canvas);
+	// send the edit across the 'net
+	var parent_obj = document.getElementById('snapr_preview_img').parentNode;
+	var id = parent_obj.getAttribute('snapr:imgid');
+	if ( !id )
+		return false;
+	
+	var canvas_json = toJSONString(canvas_data);
+	ajaxPost(makeUrlNS('Gallery', id), 'ajax=true&act=edit_tag&tag=' + ajaxEscape(value) + '&canvas_params=' + ajaxEscape(canvas_json) + '&tag_id=' + note_id, snapr_process_ajax_tag_packet);
 }
 
 function snapr_process_ajax_tag_packet(ajax)
 {
-  if ( ajax.readyState == 4 && ajax.status == 200 )
-  {
-    var response = String(ajax.responseText + '');
-    if ( response.substr(0, 1) != '[' && response.substr(0, 1) != '{' )
-    {
-      new messagebox(MB_OK|MB_ICONSTOP, 'JSON response invalid', 'Received unexpected response:<pre>' + response + '</pre>');
-      return false;
-    }
-    response = parseJSON(response);
-    if ( response.mode )
-    {
-      if ( response.mode == 'error' )
-      {
-        alert(response.error);
-        return false;
-      }
-    }
-    var parent_obj = document.getElementById('snapr_preview_img').parentNode;
-    for ( var i = 0; i < response.length; i++ )
-    {
-      var packet = response[i];
-      switch(packet.mode)
-      {
-        case 'add':
-          snapr_draw_note(parent_obj, packet.tag, packet.canvas_data, packet.note_id, packet.initial_hide, packet.auth_delete);
-          break;
-        case 'remove':
-          // Server requested to remove a tag
-          var divs = parent_obj.getElementsByTagName('div');
-          for ( var i = 0; i < divs.length; i++ )
-          {
-            var box = divs[i];
-            if ( box.className == 'canvasbox' )
-            {
-              if ( box.tag_id == packet.note_id )
-              {
-                // You. We have orders to shoot. Stand in front of wall.
-                var sibling = box.nextSibling;
-                var parent = box.parentNode;
-                // BLAM.
-                parent.removeChild(sibling);
-                parent.removeChild(box);
-                break;
-              }
-            }
-          }
-          break;
-      }
-    }
-  }
+	if ( ajax.readyState == 4 && ajax.status == 200 )
+	{
+		var response = String(ajax.responseText + '');
+		if ( response.substr(0, 1) != '[' && response.substr(0, 1) != '{' )
+		{
+			new messagebox(MB_OK|MB_ICONSTOP, 'JSON response invalid', 'Received unexpected response:<pre>' + response + '</pre>');
+			return false;
+		}
+		response = parseJSON(response);
+		if ( response.mode )
+		{
+			if ( response.mode == 'error' )
+			{
+				alert(response.error);
+				return false;
+			}
+		}
+		var parent_obj = document.getElementById('snapr_preview_img').parentNode;
+		for ( var i = 0; i < response.length; i++ )
+		{
+			var packet = response[i];
+			switch(packet.mode)
+			{
+				case 'add':
+					snapr_draw_note(parent_obj, packet.tag, packet.canvas_data, packet.note_id, packet.initial_hide, packet.auth_delete);
+					break;
+				case 'remove':
+					// Server requested to remove a tag
+					var divs = parent_obj.getElementsByTagName('div');
+					for ( var i = 0; i < divs.length; i++ )
+					{
+						var box = divs[i];
+						if ( box.className == 'canvasbox' )
+						{
+							if ( box.tag_id == packet.note_id )
+							{
+								// You. We have orders to shoot. Stand in front of wall.
+								var sibling = box.nextSibling;
+								var parent = box.parentNode;
+								// BLAM.
+								parent.removeChild(sibling);
+								parent.removeChild(box);
+								break;
+							}
+						}
+					}
+					break;
+			}
+		}
+	}
 }
 
 // Don't fire the tag init until JS init *and* image are finished.
@@ -372,22 +372,22 @@
 
 var snapr_tags_onload_real = function()
 {
-  // make sure we aren't waiting...
-  if ( snapr_lock_onload_img || snapr_lock_onload_js )
-    return false;
-  
-  // add the new box
-  var parent_obj = document.getElementById('snapr_preview_img').parentNode;
-  var id = parent_obj.getAttribute('snapr:imgid');
-  if ( !id )
-    return false;
-  ajaxPost(makeUrlNS('Gallery', id), 'ajax=true&act=get_tags', snapr_process_ajax_tag_packet);
+	// make sure we aren't waiting...
+	if ( snapr_lock_onload_img || snapr_lock_onload_js )
+		return false;
+	
+	// add the new box
+	var parent_obj = document.getElementById('snapr_preview_img').parentNode;
+	var id = parent_obj.getAttribute('snapr:imgid');
+	if ( !id )
+		return false;
+	ajaxPost(makeUrlNS('Gallery', id), 'ajax=true&act=get_tags', snapr_process_ajax_tag_packet);
 }
 
 var snapr_tags_onload = function()
 {
-  snapr_lock_onload_js = false;
-  snapr_tags_onload_real();
+	snapr_lock_onload_js = false;
+	snapr_tags_onload_real();
 }
 
 addOnloadHook(snapr_tags_onload);
--- a/plugins/gallery/upload.php	Sat Aug 21 23:25:41 2010 -0400
+++ b/plugins/gallery/upload.php	Sat Aug 21 23:32:06 2010 -0400
@@ -20,1071 +20,840 @@
 
 function page_Special_GalleryUpload()
 {
-  global $db, $session, $paths, $template, $plugins; // Common objects
-  
-  if ( $session->user_level < USER_LEVEL_ADMIN )
-  {
-    die_friendly('Access denied', '<p>You need to have administrative rights to use the gallery\'s upload features.</p>');
-  }
-  
-  $zip_support = ( class_exists('ZipArchive') || ( file_exists('/usr/bin/unzip') && is_executable('/usr/bin/unzip') ) );
-  
-  $errors = array();
-  $template->add_header('<link rel="stylesheet" type="text/css" href="' . scriptPath . '/plugins/gallery/dropdown.css" />');
-  $template->add_header('<script type="text/javascript" src="' . scriptPath . '/plugins/gallery/gallery-bits.js"></script>');
-  
-  $max_size = @ini_get('upload_max_filesize');
-  $max_size_field = '';
-  if ( $max_size )
-  {
-    if ( preg_match('/M$/i', $max_size) )
-    {
-      $max_size = intval($max_size) * 1048576;
-    }
-    else if ( preg_match('/K$/i', $max_size) )
-    {
-      $max_size = intval($max_size) * 1024;
-    }
-    else if ( preg_match('/G$/i', $max_size) )
-    {
-      $max_size = intval($max_size) * 1048576 * 1024;
-    }
-    $max_size = intval($max_size);
-    $max_size_field = "\n" . '<input type="hidden" name="MAX_FILE_SIZE" value="' . $max_size . '" />' . "\n";
-  }
-  
-  if ( isset($_GET['edit_img']) )
-  {
-    $edit_parms = $_GET['edit_img'];
-    $regex = '/^((([0-9]+),)*)?([0-9]+?)$/';
-    if ( !preg_match($regex, $edit_parms) )
-    {
-      die_friendly('Bad request', '<p>$_GET[\'edit_img\'] must be a comma-separated list of image IDs.</p>');
-    }
-    
-    $idlist = explode(',', $edit_parms);
-    $num_edit = count($idlist);
-    $idlist = "SELECT img_id,img_title,img_desc,img_filename,is_folder FROM ".table_prefix."gallery WHERE img_id=" . implode(' OR img_id=', $idlist) . ';';
-    
-    if ( !$e = $db->sql_query($idlist) )
-      $db->_die();
-    
-    $template->header();
-    
-    if ( isset($_POST['edit_do_save']) )
-    {
-      @set_time_limit(0);
-      
-      $arr_img_data = array();
-      while ( $row = $db->fetchrow($e) )
-        $arr_img_data[$row['img_id']] = $row;
-      
-      // Allow breaking out
-      switch(true):case true:
-        
-        if ( !is_array($_POST['img']) )
-        {
-          $errors[] = 'No images passed to processor.';
-          break;
-        }
-        
-        // Main updater loop
-        foreach ( $_POST['img'] as $img_id => $img_data )
-        {
-          
-          if ( !preg_match('/^[0-9]+$/', $img_id) )
-          {
-            $errors[] = 'SQL injection attempted!';
-            break 2;
-          }
-          
-          // Array of columns to update
-          $to_update = array();
-          
-          $key = 'reupload_' . $img_data['id'];
-          if ( isset($_FILES[$key]) )
-          {
-            $file =& $_FILES[ $key ];
-            if ( $file['tmp_name'] != '' )
-            {
-              // Reupload
-              $filename = ENANO_ROOT . '/files/' . $arr_img_data[ $img_data['id'] ]['img_filename'];
-              if ( !unlink($filename) )
-              {
-                $errors[] = "Could not delete $filename";
-                break 2;
-              }
-              if ( !@move_uploaded_file($file['tmp_name'], $filename) )
-              {
-                $errors[] = "Could not move uploaded file to $filename";
-                break 2;
-              }
-              
-              //
-              // Create scaled images
-              //
-              
-              // Create thumbnail image
-              $thumb_filename = ENANO_ROOT . '/cache/' . $arr_img_data[ $img_data['id'] ]['img_filename'] . '-thumb.jpg';
-              if ( !unlink($thumb_filename) )
-              {
-                $errors[] = "Could not delete $thumb_filename";
-                break 2;
-              }
-              
-              if ( !scale_image($filename, $thumb_filename, 80, 80) )
-              {
-                $errors[] = 'Couldn\'t scale image '.$i.': ImageMagick failed us';
-                break 2;
-              }
-              
-              // Create preview image
-              $preview_filename = ENANO_ROOT . '/cache/' . $arr_img_data[ $img_data['id'] ]['img_filename'] . '-preview.jpg';
-              if ( !unlink($preview_filename) )
-              {
-                $errors[] = "Could not delete $preview_filename";
-                break 2;
-              }
-              
-              if ( !scale_image($filename, $preview_filename, 640, 480) )
-              {
-                $errors[] = 'Couldn\'t scale image '.$i.': ImageMagick failed us';
-                break 2;
-              }
-              
-              $to_update['img_time_mod'] = strval(time());
-            }
-          }
-          
-          $vars = array(
-            'year' => date('Y'),
-            'month' => date('F'),
-            'day' => date('d'),
-            'time12' => date('g:i A'),
-            'time24' => date('G:i')
-          );
-          
-          // Image name/title
-          
-          $title = $template->makeParserText($img_data['title']);
-          $title->assign_vars($vars);
-          $executed = $title->run();
-          if ( $executed == '_id' )
-          {
-            $errors[] = 'You cannot name an image or folder "_id", this name is reserved for internal functions.';
-            break 2;
-          }
-          if ( $executed == '' )
-          {
-            $errors[] = 'Please enter a name for the item with unique ID ' . $img_data['id'] . '. <pre>' . print_r($_POST,true) . '</pre>';
-            break 2;
-          }
-          $to_update['img_title'] = $executed;
-          
-          // Image description
-          
-          if ( isset($img_data['desc']) )
-          {
-            $desc = $template->makeParserText($img_data['desc']);
-            $desc->assign_vars($vars);
-            $executed = $desc->run();
-            $executed = RenderMan::preprocess_text($executed, false, false);
-            $to_update['img_desc'] = $executed;
-          }
-          
-          // Folder
-          $target_folder = false;
-          
-          if ( !empty($_POST['override_folder']) )
-          {
-            if ( $_POST['override_folder'] == 'NULL' || preg_match('/^[0-9]+$/', $_POST['override_folder']) )
-            {
-              $target_folder = $_POST['override_folder'];
-            }
-          }
-          
-          if ( !empty($img_data['folder']) )
-          {
-            if ( $img_data['folder'] == 'NULL' || preg_match('/^[0-9]+$/', $img_data['folder']) )
-            {
-              $target_folder = $img_data['folder'];
-            }
-          }
-          
-          if ( $target_folder )
-          {
-            // Make sure we're not trying to move a folder to itself or a subdirectory of itself
-            
-            $children = gal_fetch_all_children(intval($img_data['id']));
-            if ( $img_data['id'] == $target_folder || in_array($target_folder, $children) )
-            {
-              $errors[] = 'You are trying to move a folder to itself, or to a subdirectory of itself, which is not allowed. If done manually (i.e. via an SQL client) this will result in infinite loops in the folder sorting code.';
-              break 2;
-            }
-            
-            $to_update['folder_parent'] = $target_folder;
-          }
-          
-          if ( count($to_update) > 0 )
-          {
-            $up_keys = array_keys($to_update);
-            $up_vals = array_values($to_update);
-            
-            $bin_cols = array('folder_parent');
-            
-            $sql = 'UPDATE ' . table_prefix.'gallery SET ';
-            
-            foreach ( $up_keys as $i => $key )
-            {
-              if ( in_array($key, $bin_cols) )
-              {
-                $sql .= $key . '=' . $up_vals[$i] . ',';
-              }
-              else
-              {
-                $sql .= $key . '=\'' . $db->escape($up_vals[$i]) . '\',';
-              }
-            }
-            
-            $sql = preg_replace('/,$/i', '', $sql) . ' WHERE img_id=' . $img_data['id'] . ';';
-            
-            if ( !$db->sql_query($sql) )
-            {
-              $db->_die();
-            }
-            
-          }
-          
-        }
-        
-        echo '<div class="info-box" style="margin-left: 0;">Your changes have been saved.</div>';
-        
-      endswitch;
-      
-      // Rerun select query to make sure information in PHP memory is up-to-date
-      if ( !$e = $db->sql_query($idlist) )
-        $db->_die();
-      
-    }
-    
-    if ( count($errors) > 0 )
-    {
-      echo '<div class="error-box" style="margin-left: 0;">
-              <b>The following errors were encountered while updating the image data:</b><br />
-              <ul>
-                <li>' . implode("</li>\n        <li>", $errors) . '</li>
-              </ul>
-            </div>';
-    }
-    
-    ?>
-    <script type="text/javascript">
-    
-      function gal_unset_radios(name)
-      {
-        var radios = document.getElementsByTagName('input');
-        for ( var i = 0; i < radios.length; i++ )
-        {
-          var radio = radios[i];
-          if ( radio.name == name )
-          {
-            radio.checked = false;
-          }
-        }
-      }
-    
-    </script>
-    <?php
-    
-    echo '<form action="' . makeUrlNS('Special', 'GalleryUpload', 'edit_img=' . $edit_parms, true) . '" method="post" enctype="multipart/form-data">';
-    
-    echo $max_size_field;
-    
-    if ( $row = $db->fetchrow($e) )
-    {
-      
-      echo '<div class="tblholder">
-              <table border="0" cellspacing="1" cellpadding="4">';
-      echo '    <tr><th class="subhead">Information</th></tr>';
-      echo '    <tr><td class="row3">
-                  As with the upload form, the following variables can be used. <b>Note that when editing images, the {id} and {autotitle} variables will be ignored.</b>';
-      ?>
-          <ul>
-            <li>{year}: The current year (<?php echo date('Y'); ?>)</li>
-            <li>{month}: The current month (<?php echo date('F'); ?>)</li>
-            <li>{day}: The day of the month (<?php echo date('d'); ?>)</li>
-            <li>{time12}: 12-hour time (<?php echo date('g:i A'); ?>)</li>
-            <li>{time24}: 24-hour time (<?php echo date('G:i'); ?>)</li>
-          </ul>
-      <?php
-      echo '        </td></tr>';
-      echo '  </table>
-            </div>';
-      
-      $i = 0;
-      do
-      {
-        $thumb_url = makeUrlNS('Special', 'GalleryFetcher/thumb/' . $row['img_id'], false, true);
-        
-        # Type: folder
-        if ( $row['is_folder'] == 1 ):
-        
-        // Image ID tracker
-        echo '<input type="hidden" name="img[' . $i . '][id]" value="' . $row['img_id'] . '" />';
-        
-        //
-        // Editor table
-        //
-        
-        $folders = gallery_imgid_to_folder(intval($row['img_id']));
-        foreach ( $folders as $j => $xxx )
-        {
-          $folder =& $folders[$j];
-          $folder = sanitize_page_id($folder);
-        }
-        $folders = array_reverse($folders);
-        $gal_href = implode('/', $folders) . ( count($folders) > 0 ? '/' : '' ) . sanitize_page_id($row['img_title']);
-        
-        echo '<div class="tblholder">
-                <table border="0" cellspacing="1" cellpadding="4">';
-        
-        echo '<tr><th colspan="2">Folder: ' . htmlspecialchars($row['img_title']) . '</th></tr>';
-        
-        // Primary key
-        echo '<tr>
-                <td class="row2">Unique ID:</td>
-                <td class="row1">' . $row['img_id'] . ' (<a href="' . makeUrlNS('Special', 'Gallery/' . $gal_href) . '">view folder contents</a>)</td>
-              </tr>';
-              
-        // Path info
-        echo '<tr>
-                <td class="row2">Parent folders:</td>
-                <td class="row1">' . /* Yeah it's dirty, but hey, it gets the job done ;-) */ ( ( $x = str_replace('&amp;raquo;', '&raquo;', htmlspecialchars(str_replace('_', ' ', implode(' &raquo; ', $folders)))) ) ? $x : '&lt;in root&gt;' ) . '</td>
-              </tr>';
-        
-        // Image name
-        
-        echo '<tr>
-                <td class="row2">Folder name:</td>
-                <td class="row1"><input type="text" style="width: 98%;" name="img[' . $i . '][title]" value="' . htmlspecialchars($row['img_title']) . '" size="43" /></td>
-              </tr>';
-              
-        // Mover widget
-        ?>
-        <tr>
-          <td class="row2">Move to folder:</td>
-          <td class="row1">
-            <div class="toggle">
-              <div class="head" onclick="gal_toggle( ( IE ? this.nextSibling : this.nextSibling.nextSibling ), this.childNodes[1]);">
-                <img alt="&gt;&gt;" src="<?php echo scriptPath; ?>/plugins/gallery/toggle-closed.png" class="toggler" />
-                Select folder
-              </div>
-              <div class="body">
-                <?php
-                  echo gallery_hier_formfield('img[' . $i . '][folder]', false);
-                ?>
-                <br />
-                <a href="#" onclick="gal_unset_radios('img[<?php echo $i; ?>][folder]'); return false;">Unselect field</a>
-              </div>
-            </div>
-          </td>
-        </tr>
-        <?php
-        
-        // Finish table
-        echo '</table>';
-        echo '</div>';
-        
-        # Type: image
-        else:
-        
-        // Image ID tracker
-        echo '<input type="hidden" name="img[' . $i . '][id]" value="' . $row['img_id'] . '" />';
-        
-        //
-        // Editor table
-        //
-        
-        echo '<div class="tblholder">
-                <table border="0" cellspacing="1" cellpadding="4">';
-        
-        echo '<tr><th colspan="2">Image: ' . htmlspecialchars($row['img_title']) . '</th></tr>';
-        
-        // Primary key
-        echo '<tr>
-                <td class="row2">Unique ID:</td>
-                <td class="row1">' . $row['img_id'] . ' (<a href="' . makeUrlNS('Gallery', $row['img_id']) . '">view image\'s page</a>)</td>
-              </tr>';
-              
-        // Thumbnail
-        
-        echo '<tr>
-                <td class="row2">Thumbnail:</td>
-                <td class="row1"><img alt="Thumbnail image" src="' . $thumb_url . '" /></td>
-              </tr>';
-        
-        // Image name
-        
-        echo '<tr>
-                <td class="row2">Image title:</td>
-                <td class="row1"><input type="text" style="width: 98%;" name="img[' . $i . '][title]" value="' . htmlspecialchars($row['img_title']) . '" size="43" /></td>
-              </tr>';
-              
-        // Image description
-        
-        echo '<tr>
-                <td class="row2">Image description:</td>
-                <td class="row1"><textarea rows="10" cols="40" style="width: 98%;" name="img[' . $i . '][desc]">' . htmlspecialchars($row['img_desc']) . '</textarea></td>
-              </tr>';
-              
-        // ACL editor trigger
-        
-        echo '<tr>
-                <td class="row2">Permissions:</td>
-                <td class="row1"><input type="button" onclick="ajaxOpenACLManager(\'' . $row['img_id'] . '\', \'Gallery\');" value="Edit permissions" /><br /><small>Only works in Firefox 1.5 or later, Safari 3.x or later, or Opera 9.0 or later.</small></td>
-              </tr>';
-              
-        // Mover widget
-        ?>
-        <tr>
-          <td class="row2">Move to folder:</td>
-          <td class="row1">
-            <div class="toggle">
-              <div class="head" onclick="gal_toggle( ( IE ? this.nextSibling : this.nextSibling.nextSibling ), this.childNodes[1]);">
-                <img alt="&gt;&gt;" src="<?php echo scriptPath; ?>/plugins/gallery/toggle-closed.png" class="toggler" />
-                Select folder
-              </div>
-              <div class="body">
-                <?php
-                  echo gallery_hier_formfield('img[' . $i . '][folder]', false);
-                ?>
-                <br />
-                <a href="#" onclick="gal_unset_radios('img[<?php echo $i; ?>][folder]'); return false;">Unselect field</a>
-              </div>
-            </div>
-          </td>
-        </tr>
-        <?php
-              
-        // File replacer
-        
-        echo '<tr>
-                <td class="row2">Upload new version:</td>
-                <td class="row1"><input type="file" name="reupload_' . $row['img_id'] . '" size="30" style="width: 98%;" /></td>
-              </tr>';
-              
-        // Finish table
-        echo '</table>';
-        echo '</div>';
-        
-        endif;
-        
-        $i++;
-      }
-      while ( $row = $db->fetchrow($e) );
-      $db->free_result();
-      
-      echo '<div class="tblholder">
-              <table border="0" cellspacing="1" cellpadding="4">';
-      // Mover widget
-      if ( $num_edit > 1 ):
-      ?>
-      <tr>
-        <td class="row2">Move all to folder:<br /><small>Other folder fields on this page can override this for individual images.</small></td>
-        <td class="row1" style="width: 70%;">
-          <div class="toggle">
-            <div class="head" onclick="gal_toggle( ( IE ? this.nextSibling : this.nextSibling.nextSibling ), this.childNodes[1]);">
-              <img alt="&gt;&gt;" src="<?php echo scriptPath; ?>/plugins/gallery/toggle-closed.png" class="toggler" />
-              Select folder
-            </div>
-            <div class="body">
-              <?php
-                echo gallery_hier_formfield('override_folder', false);
-              ?>
-              <br />
-              <a href="#" onclick="gal_unset_radios('override_folder'); return false;">Unselect field</a>
-            </div>
-          </div>
-        </td>
-      </tr>
-      <?php
-      endif;
-        
-      echo '    <tr><th class="subhead" colspan="2"><input type="submit" name="edit_do_save" value="Save changes" /></th></tr>';
-      echo '  </table>
-            </div>';
-      
-    }
-    else
-    {
-      echo '<p>No images that matched the ID list could be found.</p>';
-    }
-    
-    echo '</form>';
-    
-    $template->footer();
-    return;
-  }
-  
-  if ( isset($_GET['rm']) )
-  {
-    $warnings = array();
-    
-    if ( !preg_match('/^[0-9]+$/', $_GET['rm']) )
-      die_friendly('Bad Request', '<p>$_GET[rm] needs to be an integer.</p>');
-    
-    $rm_id = intval($_GET['rm']);
-    
-    if ( isset($_POST['confirmed']) )
-    {
-      // The user confirmed the request. Start plowing through data to decide what to delete.
-      
-      // Array of images and folder rows to delete
-      $del_imgs = array($rm_id);
-      // Array of files to delete
-      $del_files = array();
-      // Array of comment entries to delete
-      $del_comments = array();
-      
-      $all_children = gal_fetch_all_children($rm_id);
-      $del_imgs = array_merge($del_imgs, $all_children);
-      
-      $imglist = 'img_id=' . implode(' OR img_id=', $del_imgs);
-      $sql = "SELECT img_id, img_filename FROM ".table_prefix."gallery WHERE ( $imglist ) AND is_folder!=1;";
-      
-      if ( !$db->sql_query($sql) )
-      {
-        $db->_die();
-      }
-      
-      while ( $row = $db->fetchrow() )
-      {
-        $files = array(
-            ENANO_ROOT . '/files/' . $row['img_filename'],
-            ENANO_ROOT . '/cache/' . $row['img_filename'] . '-thumb.jpg',
-            ENANO_ROOT . '/cache/' . $row['img_filename'] . '-preview.jpg'
-          );
-        $del_files = array_merge($del_files, $files);
-        
-        $del_comments[] = intval($row['img_id']);
-      }
-      
-      $commentlist = 'page_id=\'' . implode('\' OR page_id=\'', $del_imgs) . '\'';
-      
-      // Main deletion cycle
-      
-      foreach ( $del_files as $file )
-      {
-        @unlink($file) or $warnings[] = 'Could not delete file ' . $file;
-      }
-      
-      if ( !$db->sql_query('DELETE FROM '.table_prefix.'gallery WHERE ' . $imglist . ';') )
-      {
-        $warnings[] = 'Main delete query failed: ' . $db->get_error();
-      }
-      
-      if ( !$db->sql_query('DELETE FROM '.table_prefix.'comments WHERE ( ' . $commentlist . ' ) AND namespace=\'Gallery\';') )
-      {
-        $warnings[] = 'Comment delete query failed: ' . $db->get_error();
-      }
-      
-      if ( count($warnings) > 0 )
-      {
-        $template->header();
-        
-        echo '<h3>Error during deletion process</h3>';
-        echo '<p>The deletion process generated some warnings which are shown below.</p>';
-        echo '<ul><li>' . implode('</li><li>', $warnings) . '</li></ul>';
-        
-        $template->footer();
-      }
-      else
-      {
-        redirect(makeUrlNS('Special', 'Gallery'), 'Deletion successful', 'The selected item has been deleted from the gallery. You will now be transferred to the gallery index.', 2);
-      }
-      
-    }
-    else
-    {
-      // Removal form
-      $template->header();
-      
-      echo '<form action="' . makeUrlNS('Special', 'GalleryUpload', 'rm=' . $rm_id, true) . '" method="post" enctype="multipart/form-data">';
-      echo $max_size_field;
-      
-      echo '<h3>Are you sure you want to delete this item?</h3>';
-      echo '<p>If you continue, this item will be permanently deleted from the gallery &ndash; no rollbacks.</p>';
-      echo '<p>If this is an image, the image files will be removed from the filesystem, and all comments associated with the image will be deleted, as well as the image\'s title, description, and location.</p>';
-      echo '<p>If this is a folder, all of its contents will be removed. Any images will be removed from the filesystem and all comments and metadata associated with images in this folder or any folders in it will be permanently deleted.</p>';
-      
-      echo '<p><input type="submit" name="confirmed" value="Continue with delete" /></p>';
-      
-      echo '</form>';
-      
-      $template->footer();
-    }
-    return;
-  }
-  
-  if ( isset($_POST['do_stage2']) )
-  {
-    // Allow breaking out of the validation in the case of an error
-    switch(true):case true:
-      
-      if ( empty($_POST['img_name']) )
-      {
-        $errors[] = 'Please enter an image name.';
-      }
-      
-      // Validate files
-      $n_files = intval($_POST['img_count']);
-      if ( $n_files < 1 )
-      {
-        $errors[] = 'Cannot get image count';
-        break;
-      }
-      
-      $files = array();
-      
-      for ( $i = 0; $i < $n_files; $i++ )
-      {
-        $key = "img_$i";
-        if ( isset($_FILES[$key]) && !empty($_FILES[$key]['name']) )
-        {
-          $files[] =& $_FILES[$key];
-        }
-      }
-      
-      if ( count($files) < 1 )
-      {
-        $errors[] = 'No files specified.';
-        break;
-      }
-      
-      $allowed = array('png', 'jpg', 'jpeg', 'tiff', 'tif', 'bmp', 'gif');
-      $is_zip = false;
-      foreach ( $files as $i => $file )
-      {
-        $ext = substr($file['name'], ( strrpos($file['name'], '.') + 1 ));
-        $ext = strtolower($ext);
-        if ( !in_array($ext, $allowed) && ( !$zip_support || ( $ext != 'zip' || $i > 0 ) ) )
-        {
-          $errors[] = htmlspecialchars($file['name']) . ' is an invalid extension (' . htmlspecialchars($ext) . ').';
-        }
-        else if ( $ext == 'zip' && $i == 0 && $zip_support )
-        {
-          $is_zip = true;
-        }
-      }
-      
-      if ( count($errors) > 0 )
-      {
-        // Send error messages
-        break;
-      }
-      
-      // Parent folder
-      $folder = $_POST['folder_id'];
-      if ( $folder != 'NULL' && !preg_match('/^[0-9]+$/', $folder) )
-      {
-        $folder = 'NULL';
-      }
-      
-      // Format title and description fields
-      $title = $template->makeParserText($_POST['img_name']);
-      $desc  = $template->makeParserText($_POST['img_desc']);
-      
-      $vars = array(
-          'year' => date('Y'),
-          'month' => date('F'),
-          'day' => date('d'),
-          'time12' => date('g:i A'),
-          'time24' => date('G:i')
-        );
-      
-      $title->assign_vars($vars);
-      $desc->assign_vars($vars);
-      
-      $idlist = array();
-      
-      // Try to disable the time limit
-      @set_time_limit(0);
-      
-      // Move uploaded files to the files/ directory
-      foreach ( $files as $i => $__trash )
-      {
-        $file =& $files[$i];
-        $ext = substr($file['name'], ( strrpos($file['name'], '.') + 1 ));
-        $ext = strtolower($ext);
-        if ( $ext == 'zip' && $is_zip && $zip_support )
-        {
-          //
-          // Time for some unzipping fun.
-          //
-          
-          error_reporting(E_ALL);
-          
-          mkdir(ENANO_ROOT . '/cache/temp') or $errors[] = 'Could not create temporary directory for extraction.';
-          if ( count($errors) > 0 )
-            break 2;
-          $temp_dir = tempnam(ENANO_ROOT . '/cache/temp', 'galunz');
-          if ( file_exists($temp_dir) )
-            unlink($temp_dir);
-          @mkdir($temp_dir);
-          
-          // Extract the zip file
-          if ( class_exists('ZipArchive') )
-          {
-            $zip = new ZipArchive();
-            $op = $zip->open($file['tmp_name']);
-            if ( !$op )
-            {
-              $errors[] = 'Could not open the zip file.';
-              break 2;
-            }
-            $op = $zip->extractTo($temp_dir);
-            if ( !$op )
-            {
-              $errors[] = 'Could not extract the zip file.';
-              break 2;
-            }
-          }
-          else if ( file_exists('/usr/bin/unzip') )
-          {
-            $cmd = "/usr/bin/unzip -qq -d $temp_dir {$file['tmp_name']}";
-            system($cmd);
-          }
-          
-          // Any files?
-          $file_list = gal_dir_recurse($temp_dir, $dirs);
-          if ( !$file_list )
-          {
-            $errors[] = 'Could not get file list for temp directory.';
-            break 2;
-          }
-          if ( count($file_list) < 1 )
-          {
-            $errors[] = 'There weren\'t any files in the uploaded zip file.';
-          }
-          
-          $dirs = array_reverse($dirs);
-          $img_files = array();
-          
-          // Loop through and add files
-          foreach ( $file_list as $file )
-          {
-            $ext = get_file_extension($file);
-            
-            if ( in_array($ext, $allowed) )
-            {
-              $img_files[] = $file;
-            }
-            else
-            {
-              unlink($file);
-            }
-          }
-          
-          // Main storage loop
-          $j = 0;
-          foreach ( $img_files as $file )
-          {
-            $ext = get_file_extension($file);
-            $stored_name = gallery_make_filename() . ".$ext";
-            $store = ENANO_ROOT . '/files/' . $stored_name;
-            if ( !rename($file, $store) )
-            {
-              $errors[] = 'Could not move file ' . $file . ' to permanent storage location ' . $store . '.';
-              break 3;
-            }
-            
-            $autotitle = ucwords(basename($file));
-            $autotitle = substr($autotitle, 0, ( strrpos($autotitle, '.') ));
-            $autotitle = str_replace('_', ' ', $autotitle);
-            
-            $title->assign_vars(array('id' => ( $j + 1 ), 'autotitle' => $autotitle));
-            $desc->assign_vars(array('id' => ( $j + 1 ), 'autotitle' => $autotitle));
-            
-            $local_t = $title->run();
-            $local_t = RenderMan::preprocess_text($local_t, true, false);
-            
-            $local_d = $desc->run();
-            $local_d = RenderMan::preprocess_text($local_d, true, false);
-            
-            $subq = '(\'' . $stored_name . '\', \'' . $db->escape($local_t) . '\', \'' . $db->escape($local_d) . '\',\'a:0:{}\', UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), ' . $folder . ', \'[]\')';
-            $sql = "INSERT INTO ".table_prefix."gallery(img_filename,img_title,img_desc,print_sizes,img_time_upload,img_time_mod,folder_parent,img_tags) VALUES{$subq};";
-            
-            if ( !$db->sql_query($sql) )
-              $db->_die();
-            
-            $idlist[] = $db->insert_id();
-            
-            //
-            // Create scaled images
-            //
-            
-            // Create thumbnail image
-            $thumb_filename = ENANO_ROOT . '/cache/' . $stored_name . '-thumb.jpg';
-            if ( file_exists($thumb_filename) )
-            {
-              if ( !unlink($thumb_filename) )
-              {
-                $errors[] = "Could not delete $thumb_filename";
-                break 2;
-              }
-            }
-            
-            if ( !scale_image($store, $thumb_filename, 80, 80) )
-            {
-              $errors[] = 'Couldn\'t scale image '.$i.': ImageMagick failed us';
-              break 2;
-            }
-            
-            // Create preview image
-            $preview_filename = ENANO_ROOT . '/cache/' . $stored_name . '-preview.jpg';
-            if ( file_exists($preview_filename) )
-            {
-              if ( !unlink($preview_filename) )
-              {
-                $errors[] = "Could not delete $preview_filename";
-                break 2;
-              }
-            }
-            
-            if ( !scale_image($store, $preview_filename, 640, 640) )
-            {
-              $errors[] = 'Couldn\'t scale image '.$i.': ImageMagick failed us';
-              break 2;
-            }
-            
-            $j++;
-          }
-          
-          // clean up
-          foreach ( $dirs as $dir )
-          {
-            rmdir($dir);
-          }
-          
-          rmdir( $temp_dir ) or $errors[] = 'Couldn\'t delete the unzip directory.';
-          rmdir( ENANO_ROOT . '/cache/temp' ) or $errors[] = 'Couldn\'t delete the temp directory.';
-          if ( count($errors) > 0 )
-            break 2;
-          
-          $idlist = implode(',', $idlist);
-          $url = makeUrlNS('Special', 'GalleryUpload', "edit_img=$idlist");
-          
-          redirect($url, 'Upload successful', 'Your images have been uploaded successfully. Please wait while you are transferred...', 2);
-          
-          break 2;
-        }
-        $file['stored_name'] = gallery_make_filename() . '.' . $ext;
-        $store = ENANO_ROOT . '/files/' . $file['stored_name'];
-        if ( !@move_uploaded_file($file['tmp_name'], $store) )
-        {
-          $errors[] = "[Internal] Couldn't move temporary file {$file['tmp_name']} to permanently stored file $store";
-          break 2;
-        }
-        
-        $autotitle = ucwords(basename($file['name']));
-        $autotitle = substr($autotitle, 0, ( strrpos($autotitle, '.') ));
-        $autotitle = str_replace('_', ' ', $autotitle);
-        
-        $title->assign_vars(array('id' => ( $i + 1 ), 'autotitle' => $autotitle));
-        $desc->assign_vars (array('id' => ( $i + 1 ), 'autotitle' => $autotitle));
-        
-        $local_t = $title->run();
-        $local_t = RenderMan::preprocess_text($local_t, true, false);
-        
-        $local_d = $desc->run();
-        $local_d = RenderMan::preprocess_text($local_d, true, false);
-        
-        $subq = '(\'' . $file['stored_name'] . '\', \'' . $db->escape($local_t) . '\', \'' . $db->escape($local_d) . '\',\'a:0:{}\', UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), ' . $folder . ', \'[]\')';
-        $sql = "INSERT INTO ".table_prefix."gallery(img_filename,img_title,img_desc,print_sizes,img_time_upload,img_time_mod,folder_parent,img_tags) VALUES{$subq};";
-        
-        if ( !$db->sql_query($sql) )
-          $db->_die();
-        
-        $idlist[] = $db->insert_id();
-        
-        // Create thumbnail image
-        $thumb_filename = ENANO_ROOT . '/cache/' . $file['stored_name'] . '-thumb.jpg';
-        $magick = getConfig('imagemagick_path');
-        $command = "$magick '{$store}' -resize ".'"'."80x80>".'"'." -quality 85 $thumb_filename";
-        
-        @system($command, $stat);
-        
-        if ( !file_exists($thumb_filename) )
-        {
-          $errors[] = 'Couldn\'t scale image '.$i.': ImageMagick failed us';
-          break 2;
-        }
-        
-        // Create preview image
-        $preview_filename = ENANO_ROOT . '/cache/' . $file['stored_name'] . '-preview.jpg';
-        $magick = getConfig('imagemagick_path');
-        $command = "$magick '{$store}' -resize ".'"'."640x640>".'"'." -quality 85 $preview_filename";
-        
-        @system($command, $stat);
-        
-        if ( !file_exists($preview_filename) )
-        {
-          $errors[] = 'Couldn\'t scale image '.$i.': ImageMagick failed us';
-          break 2;
-        }
-        
-      }
-      
-      $idlist = implode(',', $idlist);
-      $url = makeUrlNS('Special', 'GalleryUpload', "edit_img=$idlist");
-      
-      redirect($url, 'Upload successful', 'Your images have been uploaded successfully. Please wait while you are transferred...', 2);
-      
-      return;
-      
-    endswitch;
-  }
-  
-  // Smart batch-upload interface
-  $template->header();
-  
-  ?>
-  <!-- Some Javascript magic :-) -->
-  <script type="text/javascript">
-    function gal_upload_addimg()
-    {
-      var id = 0;
-      var td = document.getElementById('gal_upload_td');
-      for ( var i = 0; i < td.childNodes.length; i++ )
-      {
-        var child = td.childNodes[i];
-        if ( child.tagName == 'INPUT' && child.type == 'hidden' )
-        {
-          var file = document.createElement('input');
-          file.type = 'file';
-          file.size = '43';
-          file.name = 'img_' + id;
-          file.style.marginBottom = '3px';
-          td.insertBefore(file, child);
-          td.insertBefore(document.createElement('br'), child);
-          child.value = String(id);
-          return;
-        }
-        else if ( child.tagName == 'INPUT' && child.type == 'file' )
-        {
-          id++;
-        }
-      }
-    }
-  </script>
-  <?php
-  
-  echo '<form action="' . makeUrlNS('Special', 'GalleryUpload') . '" enctype="multipart/form-data" method="post" onsubmit="if ( window.whiteOutForm ) window.whiteOutForm(this);">';
-  echo $max_size_field;
-  if ( count($errors) > 0 )
-  {
-    echo '<div class="error-box">
-            <b>The following errors were encountered during the upload:</b><br />
-            <ul>
-              <li>' . implode("</li>\n        <li>", $errors) . '</li>
-            </ul>
-          </div>';
-  }
-  ?>
-  <div class="tblholder">
-    <table border="0" cellspacing="1" cellpadding="4">
-      <tr>
-        <th colspan="2">Upload images to gallery</th>
-      </tr>
-      <tr>
-        <td class="row2">Image name template:</td>
-        <td class="row1"><input type="text" name="img_name" size="43" style="width: 98%;" /></td>
-      </tr>
-      <tr>
-        <td class="row2">Image description template:</td>
-        <td class="row1"><textarea rows="10" cols="40" name="img_desc" style="width: 98%;"></textarea></td>
-      </tr>
-      <tr>
-        <td colspan="2" class="row3" style="font-size: smaller;">
-          <p>The name and description templates can contain the following variables:</p>
-          <ul>
-            <li>{id}: The number of the image (different for each image)</li>
-            <li>{autotitle}: Let the uploader automatically generate a title, based on the filename (david_in_the_barn.jpg will become "David in the barn"). Sometimes this process can be very dumb (mtrooper2k5.jpg will become "Mtrooper2k5").</li>
-            <li>{year}: The current year (<?php echo date('Y'); ?>)</li>
-            <li>{month}: The current month (<?php echo date('F'); ?>)</li>
-            <li>{day}: The day of the month (<?php echo date('d'); ?>)</li>
-            <li>{time12}: 12-hour time (<?php echo date('g:i A'); ?>)</li>
-            <li>{time24}: 24-hour time (<?php echo date('G:i'); ?>)</li>
-          </ul>
-          <p>Example: <input type="text" readonly="readonly" value="Photo #{id} - uploaded {month} {day}, {year} {time12}" size="50" /></p>
-        </td>
-      </tr>
-      <tr>
-        <td class="row2">
-          Image files:
-          <?php
-          if ( $zip_support )
-          {
-            ?>
-            <br />
-            <small><b>Your server has support for zip files.</b>
-                   Instead of uploading many image files, you can upload a single zip file here. Note that if you send a zip file through,
-                   it must be the first and only file or it will be ignored. Any files in the zip archive that are not supported image
-                   files will be ignored.
-                   <?php
-                     if ( $sz = ini_get('upload_max_filesize') )
-                     {
-                       echo "<b>The maximum file size is <u>{$sz}B</u>.</b>";
-                     }
-                   ?>
-                   </small>
-            <?php
-          }
-          ?>
-        </td>
-        <td class="row1" id="gal_upload_td">
-          <input type="file" name="img_0" size="43" style="margin-bottom: 3px" /><br />
-          <input type="file" name="img_1" size="43" style="margin-bottom: 3px" /><br />
-          <input type="file" name="img_2" size="43" style="margin-bottom: 3px" /><br />
-          <input type="file" name="img_3" size="43" style="margin-bottom: 3px" /><br />
-          <input type="file" name="img_4" size="43" style="margin-bottom: 3px" /><br />
-          <input type="hidden" name="img_count" value="4" />
-          <input type="button" value="+  Add image" onclick="gal_upload_addimg();" title="Add another image field" />
-        </td>
-      </tr>
-      <tr>
-        <td class="row2">Upload to folder:</td>
-        <td class="row1">
-          <div class="toggle">
-            <div class="head" onclick="gal_toggle( ( IE ? this.nextSibling : this.nextSibling.nextSibling ), this.childNodes[1]);">
-              <img alt="&gt;&gt;" src="<?php echo scriptPath; ?>/plugins/gallery/toggle-closed.png" class="toggler" />
-              Select folder
-            </div>
-            <div class="body">
-              <?php
-                echo gallery_hier_formfield();
-              ?>
-            </div>
-          </div>
-        </td>
-      </tr>
-    </table>
-    <table border="0" cellspacing="1" cellpadding="4" style="padding-top: 0;">
-      <tr>
-        <th class="subhead" style="text-align: left;">
-          <small>Please press the Upload button only once! Depending on the size of your image files and the speed of your connection, the upload may take several minutes.</small>
-        </th>
-        <th class="subhead" style="text-align: right;">
-          <input type="submit" name="do_stage2" value="Upload images" /><br />
-        </th>
-      </tr>
-    </table>
-  </div>
-  <?php
-  echo '</form>';
-  
-  $template->footer();
-  
+	global $db, $session, $paths, $template, $plugins; // Common objects
+	
+	if ( $session->user_level < USER_LEVEL_ADMIN )
+	{
+		die_friendly('Access denied', '<p>You need to have administrative rights to use the gallery\'s upload features.</p>');
+	}
+	
+	$zip_support = ( class_exists('ZipArchive') || ( file_exists('/usr/bin/unzip') && is_executable('/usr/bin/unzip') ) );
+	
+	$errors = array();
+	$template->add_header('<link rel="stylesheet" type="text/css" href="' . scriptPath . '/plugins/gallery/dropdown.css" />');
+	$template->add_header('<script type="text/javascript" src="' . scriptPath . '/plugins/gallery/gallery-bits.js"></script>');
+	
+	$max_size_field = get_max_size_field();
+	
+	//
+	// EDIT IMAGES
+	//  
+	if ( isset($_GET['edit_img']) )
+	{
+		$edit_parms = $_GET['edit_img'];
+		$regex = '/^((([0-9]+),)*)?([0-9]+?)$/';
+		if ( !preg_match($regex, $edit_parms) )
+		{
+			die_friendly('Bad request', '<p>$_GET[\'edit_img\'] must be a comma-separated list of image IDs.</p>');
+		}
+		
+		// process any uploaded images
+		// FIXME is this a bad place for this?
+		$limit = isset($_GET['ajax']) ? '' : "LIMIT 5";
+		$q = $db->sql_query('SELECT img_id FROM ' . table_prefix . "gallery WHERE is_folder = 0 AND processed = 0 $limit;");
+		if ( !$q )
+			$db->_die();
+		if ( $db->numrows() > 0 )
+		{
+			while ( $row = $db->fetchrow($q) )
+			{
+				snapr_process_image($row['img_id']);
+			}
+			$q = $db->sql_query('SELECT COUNT(img_id) FROM ' . table_prefix . "gallery WHERE is_folder = 0 AND processed = 0;");
+			if ( !$q )
+				$db->_die();
+			list($count) = $db->fetchrow_num();
+			$db->free_result();
+			if ( intval($count) > 0 )
+			redirect(makeUrlNS('Special', 'GalleryUpload', "edit_img={$_GET['edit_img']}"), "Processing images", "Processing images... $count remaining", 1);
+		}
+		
+		if ( !isset($_GET['ajax']) )
+			$template->header();
+		
+		snapr_editform($edit_parms);
+		
+		if ( !isset($_GET['ajax']) )
+			$template->footer();
+		
+		return;
+	}
+	//
+	// REMOVE IMAGES
+	// 
+	else if ( isset($_GET['rm']) )
+	{
+		$warnings = array();
+		
+		if ( !preg_match('/^[0-9]+$/', $_GET['rm']) )
+			die_friendly('Bad Request', '<p>$_GET[rm] needs to be an integer.</p>');
+		
+		$rm_id = intval($_GET['rm']);
+		
+		if ( isset($_POST['confirmed']) )
+		{
+			// The user confirmed the request. Start plowing through data to decide what to delete.
+			
+			// Array of images and folder rows to delete
+			$del_imgs = array($rm_id);
+			// Array of files to delete
+			$del_files = array();
+			// Array of comment entries to delete
+			$del_comments = array();
+			
+			$all_children = gal_fetch_all_children($rm_id);
+			$del_imgs = array_merge($del_imgs, $all_children);
+			
+			$imglist = 'img_id=' . implode(' OR img_id=', $del_imgs);
+			$sql = "SELECT img_id, img_filename FROM ".table_prefix."gallery WHERE ( $imglist ) AND is_folder!=1;";
+			
+			if ( !$db->sql_query($sql) )
+			{
+				$db->_die();
+			}
+			
+			while ( $row = $db->fetchrow() )
+			{
+				$files = array(
+						ENANO_ROOT . '/files/' . $row['img_filename'],
+						ENANO_ROOT . '/cache/' . $row['img_filename'] . '-thumb.jpg',
+						ENANO_ROOT . '/cache/' . $row['img_filename'] . '-preview.jpg'
+					);
+				$del_files = array_merge($del_files, $files);
+				
+				$del_comments[] = intval($row['img_id']);
+			}
+			
+			$commentlist = 'page_id=\'' . implode('\' OR page_id=\'', $del_imgs) . '\'';
+			
+			// Main deletion cycle
+			
+			foreach ( $del_files as $file )
+			{
+				@unlink($file) or $warnings[] = 'Could not delete file ' . $file;
+			}
+			
+			if ( !$db->sql_query('DELETE FROM '.table_prefix.'gallery WHERE ' . $imglist . ';') )
+			{
+				$warnings[] = 'Main delete query failed: ' . $db->get_error();
+			}
+			
+			if ( !$db->sql_query('DELETE FROM '.table_prefix.'comments WHERE ( ' . $commentlist . ' ) AND namespace=\'Gallery\';') )
+			{
+				$warnings[] = 'Comment delete query failed: ' . $db->get_error();
+			}
+			
+			if ( count($warnings) > 0 )
+			{
+				$template->header();
+				
+				echo '<h3>Error during deletion process</h3>';
+				echo '<p>The deletion process generated some warnings which are shown below.</p>';
+				echo '<ul><li>' . implode('</li><li>', $warnings) . '</li></ul>';
+				
+				$template->footer();
+			}
+			else
+			{
+				redirect(makeUrlNS('Special', 'Gallery'), 'Deletion successful', 'The selected item has been deleted from the gallery. You will now be transferred to the gallery index.', 2);
+			}
+			
+		}
+		else
+		{
+			// Removal form
+			$template->header();
+			
+			echo '<form action="' . makeUrlNS('Special', 'GalleryUpload', 'rm=' . $rm_id, true) . '" method="post" enctype="multipart/form-data">';
+			echo $max_size_field;
+			
+			echo '<h3>Are you sure you want to delete this item?</h3>';
+			echo '<p>If you continue, this item will be permanently deleted from the gallery &ndash; no rollbacks.</p>';
+			echo '<p>If this is an image, the image files will be removed from the filesystem, and all comments associated with the image will be deleted, as well as the image\'s title, description, and location.</p>';
+			echo '<p>If this is a folder, all of its contents will be removed. Any images will be removed from the filesystem and all comments and metadata associated with images in this folder or any folders in it will be permanently deleted.</p>';
+			
+			echo '<p><input type="submit" name="confirmed" value="Continue with delete" /></p>';
+			
+			echo '</form>';
+			
+			$template->footer();
+		}
+		return;
+	}
+	else if ( isset($_GET['ajax_proc_status']) )
+	{
+		$q = $db->sql_query("SELECT COUNT(img_id) FROM " . table_prefix . "gallery WHERE processed = 0;");
+		if ( !$q )
+			$db->_die();
+		list($count) = $db->fetchrow_num();
+		echo $count;
+		return;
+	}
+	else
+	{
+		if ( isset($_POST['do_upload']) )
+		{
+			$files =& $_FILES['files'];
+			$numfiles = count($files['name']);
+			$idlist = array();
+			$destfolder = intval($_POST['targetfolder']);
+			if ( $destfolder < 1 )
+				$destfolder = NULL;
+			for ( $i = 0; $i < $numfiles; $i++ )
+			{
+				$ext = get_file_extension($files['name'][$i]);
+				if ( snapr_extension_allowed($ext) )
+				{
+					// normal image
+					$result = snapr_insert_image($files['tmp_name'][$i], $destfolder);
+					if ( $result !== false )
+						$idlist[] = $result;
+				}
+				else if ( strtolower($ext) == 'zip' )
+				{
+					// zip file
+					$zipidlist = snapr_process_zip($files['tmp_name'][$i], $destfolder);
+					if ( $zipidlist )
+						$idlist = array_merge($idlist, $zipidlist);
+				}
+				else
+				{
+					// FIXME handle unsupported files... maybe?
+				}
+			}
+			$idlist = implode(',', $idlist);
+			echo '<div class="idlist">[' . $idlist . ']</div>';
+			//snapr_editform($idlist);
+			return;
+		}
+		
+		// Oh yes, the image uploader!
+		$template->preload_js(array('jquery', 'jquery-ui', 'upload'));
+		$template->header();
+		
+		?>
+		<form action="" method="post" enctype="multipart/form-data" id="snaprupload">
+		
+		<script type="text/javascript">
+		//<![CDATA[
+		addOnloadHook(function()
+			{
+				attachHook('snaprupload_ajaxupload_init', 'snapr_upload_init(ajaxupload);');
+			});
+		function snapr_upload_init(au)
+		{
+			au.upload_start = function()
+			{
+				$(this.form).hide();
+				$(this.statusbox).html('<h2 class="uploadgoing">Uploading pictures...</h2><div class="progress" style="margin: 15px 0;"></div><p class="uploadstatus">&nbsp;</p>');
+				$('div.progress', this.statusbox).progressbar({value: 0});
+			};
+			
+			au.status = function(state)
+			{
+				if ( !state.done && !state.cancel_upload )
+				{
+					var rawpct = state.bytes_processed / state.content_length;
+					var pct = (Math.round((rawpct) * 1000)) / 10;
+					var elapsed = state.current_time - state.start_time;
+					var rawbps = state.bytes_processed / elapsed;
+					var kbps = Math.round((rawbps) / 1024);
+					var remain_bytes = state.content_length - state.bytes_processed;
+					var remain_time = Math.round(remain_bytes / rawbps);
+					
+					$('p.uploadstatus', this.statusbox).html(pct + '% complete / ' + kbps + ' KB/s / ' + humanize_time(elapsed) + ' elapsed / ' + humanize_time(remain_time) + ' remaining');
+					$('div.progress', this.statusbox).progressbar('value', pct);
+				}
+			};
+			
+			au.upload_success = function(childbody)
+			{
+				$(this.statusbox).html('<div class="info-box"></div>' + childbody.innerHTML);
+				var idlist = parseJSON($('div.idlist', this.statusbox).text());
+				$('div.idlist', this.statusbox).remove();
+				var s = idlist.length == 1 ? '' : 's';
+				$('div.info-box', this.statusbox).html(idlist.length + ' image'+s+' were uploaded successfully. Please wait while they are processed...');
+				$(this.statusbox).append('<div class="progress" style="margin: 15px 0;"></div><p class="uploadstatus">&nbsp;</p>');
+				$('div.progress', this.statusbox).progressbar({value: 0});
+				var au = this;
+				ajaxGet(makeUrlNS('Special', 'GalleryUpload', 'edit_img=' + implode(',', idlist) + '&ajax=true'), function(ajax)
+					{
+						if ( ajax.readyState == 4 )
+						{
+							window.clearTimeout(snapr_refresh_timer);
+							$(au.statusbox).html(ajax.responseText);
+						}
+					});
+				snapr_refresh_proc(au, idlist);
+			};
+		}
+		
+		window.snapr_refresh_timer = false;
+		
+		function snapr_refresh_proc(au, idlist)
+		{
+			void(au);
+			void(idlist);
+			ajaxGet(makeUrlNS('Special', 'GalleryUpload', 'ajax_proc_status'), function(ajax)
+				{
+					if ( ajax.readyState == 4 )
+					{
+						var n = idlist.length - Number(ajax.responseText);
+						var pct = (n / idlist.length) * 100;
+						$('div.progress', au.statusbox).progressbar('value', pct);
+						$('p.uploadstatus', au.statusbox).html(n + " of " + idlist.length + " images processed");
+						if ( pct < 100 )
+							window.snapr_refresh_timer = setTimeout(function()
+								{
+									snapr_refresh_proc(au, idlist);
+								}, 1000);
+					}
+				});
+		}
+		//]]>
+		</script>
+		<?php ajax_upload_js('snaprupload'); ?>
+		
+		<div class="tblholder">
+			<table border="0" cellspacing="1" cellpadding="4">
+				<tr>
+					<th colspan="2">Upload files to the gallery</th>
+				</tr>
+				<tr>
+					<td class="row1">
+						Select files:
+					</td>
+					<td class="row1">
+						<input type="hidden" name="do_upload" value="yes" />
+						<input type="file" size="50" name="files[]" />
+						<input type="button" class="addanother" value="+" />
+					</td>
+				</tr>
+				<tr>
+					<td class="row2">
+						Upload into folder:
+					</td>
+					<td class="row2">
+					<?php echo gallery_hier_formfield('targetfolder', true); ?>
+					</td>
+				</tr>
+				<tr>
+					<td class="row3" colspan="2" style="text-align: center; line-height: 24px;">
+						<strong>Supported formats:</strong>
+						<br />
+						
+						<img alt="Checkmark" src="<?php echo cdnPath; ?>/images/check.png" style="vertical-align: middle;" /> JPEG images &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+						<img alt="Checkmark" src="<?php echo cdnPath; ?>/images/check.png" style="vertical-align: middle;" /> PNG images &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+						<img alt="Checkmark" src="<?php echo cdnPath; ?>/images/check.png" style="vertical-align: middle;" /> GIF images &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+						<?php if ( $zip_support ): ?>
+						<img alt="Checkmark" src="<?php echo cdnPath; ?>/images/check.png" style="vertical-align: middle;" /> Zip archives
+						<?php else: ?>
+						<img alt="X mark" src="<?php echo cdnPath; ?>/images/checkbad.png" style="vertical-align: middle;" /> Zip archives
+						<?php endif; ?><br />
+						<small>Maximum file size: <strong><?php echo ini_get('upload_max_filesize'); ?></strong></small>
+						<?php echo $max_size_field; ?>
+					</td>
+				</tr>
+				<tr>
+					<th colspan="2" class="subhead">
+						<input type="submit" value="Upload" />
+					</th>
+				</tr>
+			</table>
+		</div>
+		</form>
+		<script type="text/javascript">
+		// <![CDATA[
+		addOnloadHook(function()
+			{
+				$('input.addanother').click(function()
+					{
+						$(this).before('<br />');
+						var inp = document.createElement('input');
+						$(inp).attr('type', 'file').attr('size', '50').attr('name', 'files[]');
+						this.parentNode.insertBefore(inp, this);
+						$(this).before(' ');
+						return false;
+					});
+			});
+		// ]]>
+		</script>
+		<?php
+	}
+	
+	
+	$template->footer();
+	
+}
+
+function snapr_editform($edit_parms)
+{
+	global $db, $session, $paths, $template, $plugins; // Common objects
+	
+	$max_size_field = get_max_size_field();
+	$errors = array();
+	$idlist = explode(',', $edit_parms);
+	$num_edit = count($idlist);
+	$idlist = "SELECT img_id,img_title,img_desc,img_filename,is_folder FROM ".table_prefix."gallery WHERE img_id=" . implode(' OR img_id=', $idlist) . ';';
+	
+	if ( !$e = $db->sql_query($idlist) )
+		$db->_die();
+	
+	if ( isset($_POST['edit_do_save']) )
+	{
+		@set_time_limit(0);
+		
+		$arr_img_data = array();
+		while ( $row = $db->fetchrow($e) )
+			$arr_img_data[$row['img_id']] = $row;
+		
+		// Allow breaking out
+		switch(true):case true:
+			
+			if ( !is_array($_POST['img']) )
+			{
+				$errors[] = 'No images passed to processor.';
+				break;
+			}
+			
+			// Main updater loop
+			foreach ( $_POST['img'] as $img_id => $img_data )
+			{
+				
+				if ( !preg_match('/^[0-9]+$/', $img_id) )
+				{
+					$errors[] = 'SQL injection attempted!';
+					break 2;
+				}
+				
+				// Array of columns to update
+				$to_update = array();
+				
+				$key = 'reupload_' . $img_data['id'];
+				if ( isset($_FILES[$key]) )
+				{
+					$file =& $_FILES[ $key ];
+					if ( $file['tmp_name'] != '' )
+					{
+						// Reupload
+						$filename = ENANO_ROOT . '/files/' . $arr_img_data[ $img_data['id'] ]['img_filename'];
+						if ( !unlink($filename) )
+						{
+							$errors[] = "Could not delete $filename";
+							break 2;
+						}
+						if ( !@move_uploaded_file($file['tmp_name'], $filename) )
+						{
+							$errors[] = "Could not move uploaded file to $filename";
+							break 2;
+						}
+						
+						//
+						// Create scaled images
+						//
+						
+						// Create thumbnail image
+						$thumb_filename = ENANO_ROOT . '/cache/' . $arr_img_data[ $img_data['id'] ]['img_filename'] . '-thumb.jpg';
+						if ( !unlink($thumb_filename) )
+						{
+							$errors[] = "Could not delete $thumb_filename";
+							break 2;
+						}
+						
+						if ( !scale_image($filename, $thumb_filename, 80, 80) )
+						{
+							$errors[] = 'Couldn\'t scale image '.$i.': ImageMagick failed us';
+							break 2;
+						}
+						
+						// Create preview image
+						$preview_filename = ENANO_ROOT . '/cache/' . $arr_img_data[ $img_data['id'] ]['img_filename'] . '-preview.jpg';
+						if ( !unlink($preview_filename) )
+						{
+							$errors[] = "Could not delete $preview_filename";
+							break 2;
+						}
+						
+						if ( !scale_image($filename, $preview_filename, 640, 480) )
+						{
+							$errors[] = 'Couldn\'t scale image '.$i.': ImageMagick failed us';
+							break 2;
+						}
+						
+						$to_update['img_time_mod'] = strval(time());
+					}
+				}
+				
+				$vars = array(
+					'year' => date('Y'),
+					'month' => date('F'),
+					'day' => date('d'),
+					'time12' => date('g:i A'),
+					'time24' => date('G:i')
+				);
+				
+				// Image name/title
+				
+				$title = $template->makeParserText($img_data['title']);
+				$title->assign_vars($vars);
+				$executed = $title->run();
+				if ( $executed == '_id' )
+				{
+					$errors[] = 'You cannot name an image or folder "_id", this name is reserved for internal functions.';
+					break 2;
+				}
+				if ( $executed == '' )
+				{
+					$errors[] = 'Please enter a name for the item with unique ID ' . $img_data['id'] . '. <pre>' . print_r($_POST,true) . '</pre>';
+					break 2;
+				}
+				$to_update['img_title'] = $executed;
+				
+				// Image description
+				
+				if ( isset($img_data['desc']) )
+				{
+					$desc = $template->makeParserText($img_data['desc']);
+					$desc->assign_vars($vars);
+					$executed = $desc->run();
+					$executed = RenderMan::preprocess_text($executed, false, false);
+					$to_update['img_desc'] = $executed;
+				}
+				
+				// Folder
+				$target_folder = false;
+				
+				if ( !empty($_POST['override_folder']) )
+				{
+					if ( $_POST['override_folder'] == 'NULL' || preg_match('/^[0-9]+$/', $_POST['override_folder']) )
+					{
+						$target_folder = $_POST['override_folder'];
+					}
+				}
+				
+				if ( !empty($img_data['folder']) )
+				{
+					if ( $img_data['folder'] == 'NULL' || preg_match('/^[0-9]+$/', $img_data['folder']) )
+					{
+						$target_folder = $img_data['folder'];
+					}
+				}
+				
+				if ( $target_folder )
+				{
+					// Make sure we're not trying to move a folder to itself or a subdirectory of itself
+					
+					$children = gal_fetch_all_children(intval($img_data['id']));
+					if ( $img_data['id'] == $target_folder || in_array($target_folder, $children) )
+					{
+						$errors[] = 'You are trying to move a folder to itself, or to a subdirectory of itself, which is not allowed. If done manually (i.e. via an SQL client) this will result in infinite loops in the folder sorting code.';
+						break 2;
+					}
+					
+					$to_update['folder_parent'] = $target_folder;
+				}
+				
+				if ( count($to_update) > 0 )
+				{
+					$up_keys = array_keys($to_update);
+					$up_vals = array_values($to_update);
+					
+					$bin_cols = array('folder_parent');
+					
+					$sql = 'UPDATE ' . table_prefix.'gallery SET ';
+					
+					foreach ( $up_keys as $i => $key )
+					{
+						if ( in_array($key, $bin_cols) )
+						{
+							$sql .= $key . '=' . $up_vals[$i] . ',';
+						}
+						else
+						{
+							$sql .= $key . '=\'' . $db->escape($up_vals[$i]) . '\',';
+						}
+					}
+					
+					$sql = preg_replace('/,$/i', '', $sql) . ' WHERE img_id=' . $img_data['id'] . ';';
+					
+					if ( !$db->sql_query($sql) )
+					{
+						$db->_die();
+					}
+					
+				}
+				
+			}
+			
+			echo '<div class="info-box" style="margin-left: 0;">Your changes have been saved.</div>';
+			
+		endswitch;
+		
+		// Rerun select query to make sure information in PHP memory is up-to-date
+		if ( !$e = $db->sql_query($idlist) )
+			$db->_die();
+		
+	}
+	
+	if ( count($errors) > 0 )
+	{
+		echo '<div class="error-box" style="margin-left: 0;">
+						<b>The following errors were encountered while updating the image data:</b><br />
+						<ul>
+							<li>' . implode("</li>\n        <li>", $errors) . '</li>
+						</ul>
+					</div>';
+	}
+	
+	echo '<form action="' . makeUrlNS('Special', 'GalleryUpload', 'edit_img=' . $edit_parms, true) . '" method="post" enctype="multipart/form-data">';
+	
+	echo $max_size_field;
+	
+	if ( $row = $db->fetchrow($e) )
+	{
+		
+		echo '<div class="tblholder">
+						<table border="0" cellspacing="1" cellpadding="4">';
+		echo '    <tr><th class="subhead">Information</th></tr>';
+		echo '    <tr><td class="row3">
+								As with the upload form, the following variables can be used. <b>Note that when editing images, the {id} and {autotitle} variables will be ignored.</b>';
+		?>
+				<ul>
+					<li>{year}: The current year (<?php echo date('Y'); ?>)</li>
+					<li>{month}: The current month (<?php echo date('F'); ?>)</li>
+					<li>{day}: The day of the month (<?php echo date('d'); ?>)</li>
+					<li>{time12}: 12-hour time (<?php echo date('g:i A'); ?>)</li>
+					<li>{time24}: 24-hour time (<?php echo date('G:i'); ?>)</li>
+				</ul>
+		<?php
+		echo '        </td></tr>';
+		echo '  </table>
+					</div>';
+		
+		$i = 0;
+		do
+		{
+			$thumb_url = makeUrlNS('Special', 'GalleryFetcher/thumb/' . $row['img_id'], false, true);
+			
+			# Type: folder
+			if ( $row['is_folder'] == 1 ):
+			
+			// Image ID tracker
+			echo '<input type="hidden" name="img[' . $i . '][id]" value="' . $row['img_id'] . '" />';
+			
+			//
+			// Editor table
+			//
+			
+			$folders = gallery_imgid_to_folder(intval($row['img_id']));
+			foreach ( $folders as $j => $xxx )
+			{
+				$folder =& $folders[$j];
+				$folder = sanitize_page_id($folder);
+			}
+			$folders = array_reverse($folders);
+			$gal_href = implode('/', $folders) . ( count($folders) > 0 ? '/' : '' ) . sanitize_page_id($row['img_title']);
+			
+			echo '<div class="tblholder">
+							<table border="0" cellspacing="1" cellpadding="4">';
+			
+			echo '<tr><th colspan="2">Folder: ' . htmlspecialchars($row['img_title']) . '</th></tr>';
+			
+			// Primary key
+			echo '<tr>
+							<td class="row2">Unique ID:</td>
+							<td class="row1">' . $row['img_id'] . ' (<a href="' . makeUrlNS('Special', 'Gallery/' . $gal_href) . '">view folder contents</a>)</td>
+						</tr>';
+						
+			// Path info
+			echo '<tr>
+							<td class="row2">Parent folders:</td>
+							<td class="row1">' . /* Yeah it's dirty, but hey, it gets the job done ;-) */ ( ( $x = str_replace('&amp;raquo;', '&raquo;', htmlspecialchars(str_replace('_', ' ', implode(' &raquo; ', $folders)))) ) ? $x : '&lt;in root&gt;' ) . '</td>
+						</tr>';
+			
+			// Image name
+			
+			echo '<tr>
+							<td class="row2">Folder name:</td>
+							<td class="row1"><input type="text" style="width: 98%;" name="img[' . $i . '][title]" value="' . htmlspecialchars($row['img_title']) . '" size="43" /></td>
+						</tr>';
+						
+			// Mover widget
+			?>
+			<tr>
+				<td class="row2">Move to folder:</td>
+				<td class="row1">
+					<div class="toggle">
+						<div class="head" onclick="gal_toggle( ( IE ? this.nextSibling : this.nextSibling.nextSibling ), this.childNodes[1]);">
+							<img alt="&gt;&gt;" src="<?php echo scriptPath; ?>/plugins/gallery/toggle-closed.png" class="toggler" />
+							Select folder
+						</div>
+						<div class="body">
+							<?php
+								echo gallery_hier_formfield('img[' . $i . '][folder]', false);
+							?>
+							<br />
+							<a href="#" onclick="gal_unset_radios('img[<?php echo $i; ?>][folder]'); return false;">Unselect field</a>
+						</div>
+					</div>
+				</td>
+			</tr>
+			<?php
+			
+			// Finish table
+			echo '</table>';
+			echo '</div>';
+			
+			# Type: image
+			else:
+			
+			// Image ID tracker
+			echo '<input type="hidden" name="img[' . $i . '][id]" value="' . $row['img_id'] . '" />';
+			
+			//
+			// Editor table
+			//
+			
+			echo '<div class="tblholder">
+							<table border="0" cellspacing="1" cellpadding="4">';
+			
+			echo '<tr><th colspan="2">Image: ' . htmlspecialchars($row['img_title']) . '</th></tr>';
+			
+			// Primary key
+			echo '<tr>
+							<td class="row2">Unique ID:</td>
+							<td class="row1">' . $row['img_id'] . ' (<a href="' . makeUrlNS('Gallery', $row['img_id']) . '">view image\'s page</a>)</td>
+						</tr>';
+						
+			// Thumbnail
+			
+			echo '<tr>
+							<td class="row2">Thumbnail:</td>
+							<td class="row1"><img alt="Thumbnail image" src="' . $thumb_url . '" /></td>
+						</tr>';
+			
+			// Image name
+			
+			echo '<tr>
+							<td class="row2">Image title:</td>
+							<td class="row1"><input type="text" style="width: 98%;" name="img[' . $i . '][title]" value="' . htmlspecialchars($row['img_title']) . '" size="43" /></td>
+						</tr>';
+						
+			// Image description
+			
+			echo '<tr>
+							<td class="row2">Image description:</td>
+							<td class="row1"><textarea rows="10" cols="40" style="width: 98%;" name="img[' . $i . '][desc]">' . htmlspecialchars($row['img_desc']) . '</textarea></td>
+						</tr>';
+						
+			// ACL editor trigger
+			
+			echo '<tr>
+							<td class="row2">Permissions:</td>
+							<td class="row1"><input type="button" onclick="ajaxOpenACLManager(\'' . $row['img_id'] . '\', \'Gallery\');" value="Edit permissions" /><br /><small>Only works in Firefox 1.5 or later, Safari 3.x or later, or Opera 9.0 or later.</small></td>
+						</tr>';
+						
+			// Mover widget
+			?>
+			<tr>
+				<td class="row2">Move to folder:</td>
+				<td class="row1">
+					<div class="toggle">
+						<div class="head" onclick="gal_toggle( ( IE ? this.nextSibling : this.nextSibling.nextSibling ), this.childNodes[1]);">
+							<img alt="&gt;&gt;" src="<?php echo scriptPath; ?>/plugins/gallery/toggle-closed.png" class="toggler" />
+							Select folder
+						</div>
+						<div class="body">
+							<?php
+								echo gallery_hier_formfield('img[' . $i . '][folder]', false);
+							?>
+							<br />
+							<a href="#" onclick="gal_unset_radios('img[<?php echo $i; ?>][folder]'); return false;">Unselect field</a>
+						</div>
+					</div>
+				</td>
+			</tr>
+			<?php
+						
+			// File replacer
+			
+			echo '<tr>
+							<td class="row2">Upload new version:</td>
+							<td class="row1"><input type="file" name="reupload_' . $row['img_id'] . '" size="30" style="width: 98%;" /></td>
+						</tr>';
+						
+			// Finish table
+			echo '</table>';
+			echo '</div>';
+			
+			endif;
+			
+			$i++;
+		}
+		while ( $row = $db->fetchrow($e) );
+		$db->free_result();
+		
+		echo '<div class="tblholder">
+						<table border="0" cellspacing="1" cellpadding="4">';
+		// Mover widget
+		if ( $num_edit > 1 ):
+		?>
+		<tr>
+			<td class="row2">Move all to folder:<br /><small>Other folder fields on this page can override this for individual images.</small></td>
+			<td class="row1" style="width: 70%;">
+				<div class="toggle">
+					<div class="head" onclick="gal_toggle( ( IE ? this.nextSibling : this.nextSibling.nextSibling ), this.childNodes[1]);">
+						<img alt="&gt;&gt;" src="<?php echo scriptPath; ?>/plugins/gallery/toggle-closed.png" class="toggler" />
+						Select folder
+					</div>
+					<div class="body">
+						<?php
+							echo gallery_hier_formfield('override_folder', false);
+						?>
+						<br />
+						<a href="#" onclick="gal_unset_radios('override_folder'); return false;">Unselect field</a>
+					</div>
+				</div>
+			</td>
+		</tr>
+		<?php
+		endif;
+			
+		echo '    <tr><th class="subhead" colspan="2"><input type="submit" name="edit_do_save" value="Save changes" /></th></tr>';
+		echo '  </table>
+					</div>';
+		
+	}
+	else
+	{
+		echo '<p>No images that matched the ID list could be found.</p>';
+	}
+	
+	echo '</form>';
+}
+
+function get_max_size_field()
+{
+	$max_size = @ini_get('upload_max_filesize');
+	$max_size_field = '';
+	if ( $max_size )
+	{
+		if ( preg_match('/M$/i', $max_size) )
+		{
+			$max_size = intval($max_size) * 1048576;
+		}
+		else if ( preg_match('/K$/i', $max_size) )
+		{
+			$max_size = intval($max_size) * 1024;
+		}
+		else if ( preg_match('/G$/i', $max_size) )
+		{
+			$max_size = intval($max_size) * 1048576 * 1024;
+		}
+		$max_size = intval($max_size);
+		$max_size_field = "\n" . '<input type="hidden" name="MAX_FILE_SIZE" value="' . $max_size . '" />' . "\n";
+	}
+	return $max_size_field;
 }
 
 ?>
--- a/plugins/gallery/viewimage.php	Sat Aug 21 23:25:41 2010 -0400
+++ b/plugins/gallery/viewimage.php	Sat Aug 21 23:32:06 2010 -0400
@@ -21,351 +21,354 @@
 
 function gallery_namespace_handler(&$page)
 {
-  global $db, $session, $paths, $template, $plugins; // Common objects
-  
-  if ( $page->namespace != 'Gallery' )
-    return false;
-  
-  if ( $page->page_id == 'Root' )
-  {
-    page_Special_Gallery();
-    return true;
-  }
-  
-  $row =& $page->image_info;
-  
-  $db->free_result();
-  
-  $img_id = $row['img_id'];
-  
-  if ( !$row['folder_parent'] )
-    $row['folder_parent'] = ' IS NULL';
-  else
-    $row['folder_parent'] = '=' . $row['folder_parent'];
-  
-  // Fetch image parent properties
-  $q = $db->sql_query('SELECT img_id, img_title FROM '.table_prefix.'gallery WHERE folder_parent' . $row['folder_parent'] . ' AND is_folder!=1 ORDER BY img_title ASC;');
-  if ( !$q )
-    $db->_die();
-  
-  $folder_total = $db->numrows();
-  $folder_this = 0;
-  $prev = false;
-  $next = false;
-  $next_title = '';
-  $prev_title = '';
-  
-  $i = 0;
-  
-  while ( $r = $db->fetchrow() )
-  {
-    $i++;
-    if ( $i == $folder_total && $r['img_id'] == $img_id )
-    {
-      $folder_this = $i;
-      $next = false;
-    }
-    else if ( $i < $folder_total && $r['img_id'] == $img_id )
-    {
-      $folder_this = $i;
-      $next = true;
-    }
-    else
-    {
-      if ( $next )
-      {
-        $next = $r['img_id'];
-        $next_title = $r['img_title'];
-        break;
-      }
-      $prev = $r['img_id'];
-      $prev_title = $r['img_title'];
-    }
-  }
-  
-  if ( $next )
-  {
-    $next_sanitized = sanitize_page_id($next_title);
-    $next_url = ( isset($hier) ) ? makeUrlNS('Gallery', $hier . $next_sanitized ) : makeUrlNS('Gallery', $next);
-  }
-  if ( $prev )
-  {
-    $prev_sanitized = sanitize_page_id($prev_title);
-    $prev_url = ( isset($hier) ) ? makeUrlNS('Gallery', $hier . $prev_sanitized ) : makeUrlNS('Gallery', $prev);
-  }
-  
-  $db->free_result();
-  
-  $perms = $session->fetch_page_acl(strval($img_id), 'Gallery');
-  
-  if ( isset($_POST['ajax']) && @$_POST['ajax'] === 'true' && isset($_POST['act']) )
-  {
-    $mode =& $_POST['act'];
-    $response = array();
-    switch($mode)
-    {
-      case 'add_tag':
-        if ( !$perms->get_permissions('snapr_add_tag') )
-        {
-          die(snapr_json_encode(array(
-              'mode' => 'error',
-              'error' => 'You don\'t have permission to add tags.'
-            )));
-        }
-        if ( empty($row['img_tags']) )
-        {
-          $row['img_tags'] = '[]';
-        }
-        $row['img_tags'] = snapr_json_decode($row['img_tags']);
-        
-        $canvas_data = snapr_json_decode($_POST['canvas_params']);
-        $tag_data = array(
-            'tag' => sanitize_html($_POST['tag']),
-            'canvas_data' => $canvas_data
-          );
-        $row['img_tags'][] = $tag_data;
-        $tag_data['note_id'] = count($row['img_tags']) - 1;
-        $tag_data['mode'] = 'add';
-        $tag_data['initial_hide'] = false;
-        $tag_data['auth_delete'] = true;
-        
-        $row['img_tags'] = snapr_json_encode($row['img_tags']);
-        $row['img_tags'] = $db->escape($row['img_tags']);
-        $q = $db->sql_query('UPDATE ' . table_prefix . "gallery SET img_tags = '{$row['img_tags']}' WHERE img_id = $img_id;");
-        if ( !$q )
-          $db->die_json();
-        
-        $response[] = $tag_data;
-        break;
-      case 'del_tag':
-        if ( !$perms->get_permissions('snapr_add_tag') )
-        {
-          die(snapr_json_encode(array(
-              'mode' => 'error',
-              'error' => 'You don\'t have permission to add tags.'
-            )));
-        }
-        if ( empty($row['img_tags']) )
-        {
-          $row['img_tags'] = '[]';
-        }
-        $row['img_tags'] = snapr_json_decode($row['img_tags']);
-        
-        $tag_id = intval(@$_POST['tag_id']);
-        if ( isset($row['img_tags'][$tag_id]) )
-          unset($row['img_tags'][$tag_id]);
-        
-        $row['img_tags'] = snapr_json_encode($row['img_tags']);
-        $row['img_tags'] = $db->escape($row['img_tags']);
-        $q = $db->sql_query('UPDATE ' . table_prefix . "gallery SET img_tags = '{$row['img_tags']}' WHERE img_id = $img_id;");
-        if ( !$q )
-          $db->die_json();
-        
-        $response[] = array(
-            'mode' => 'remove',
-            'note_id' => $tag_id
-          );
-        break;
-      case 'edit_tag':
-        if ( !$perms->get_permissions('snapr_add_tag') )
-        {
-          die(snapr_json_encode(array(
-              'mode' => 'error',
-              'error' => 'You don\'t have permission to edit tags.'
-            )));
-        }
-        if ( empty($row['img_tags']) )
-        {
-          $row['img_tags'] = '[]';
-        }
-        $row['img_tags'] = snapr_json_decode($row['img_tags']);
-        
-        $tag_id = intval(@$_POST['tag_id']);
-        if ( isset($row['img_tags'][$tag_id]) )
-        {
-          $row['img_tags'][$tag_id]['tag'] = sanitize_html($_POST['tag']);
-          // copy it
-          $tag_return = $row['img_tags'][$tag_id];
-          unset($tag);
-        }
-        else
-        {
-          die(snapr_json_encode(array(
-              'mode' => 'error',
-              'error' => 'That tag doesn\'t exist.'
-            )));
-        }
-        
-        $row['img_tags'] = snapr_json_encode($row['img_tags']);
-        $row['img_tags'] = $db->escape($row['img_tags']);
-        $q = $db->sql_query('UPDATE ' . table_prefix . "gallery SET img_tags = '{$row['img_tags']}' WHERE img_id = $img_id;");
-        if ( !$q )
-          $db->die_json();
-        
-        $tag_return['mode'] = 'add';
-        $tag_return['canvas_data'] = snapr_json_decode($_POST['canvas_params']);
-        $tag_return['auth_delete'] = $perms->get_permissions('snapr_add_tag');
-        $tag_return['initial_hide'] = false;
-        $tag_return['note_id'] = $tag_id;
-        $response = array($tag_return);
-        
-        break;
-      case 'get_tags':
-        if ( empty($row['img_tags']) )
-        {
-          $row['img_tags'] = '[]';
-        }
-        $response = snapr_json_decode($row['img_tags']);
-        foreach ( $response as $key => $_ )
-        {
-          unset($_);
-          $tag = $response[$key];
-          unset($response[$key]);
-          $tag['note_id'] = intval($key);
-          $tag['mode'] = 'add';
-          $tag['initial_hide'] = true;
-          $tag['auth_delete'] = $perms->get_permissions('snapr_add_tag');
-          $response[intval($key)] = $tag;
-        }
-        $response = array_values($response);
-        unset($tag);
-        break;
-    }
-    $encoded = snapr_json_encode($response);
-    header('Content-type: text/plain');
-    echo $encoded;
-    return true;
-  }
-  
-  $have_notes = ( empty($row['img_tags']) ) ? false : ( count(snapr_json_decode($row['img_tags'])) > 0 );
-  
-  $template->add_header('<script type="text/javascript" src="' . scriptPath . '/plugins/gallery/canvas.js"></script>');
-  $template->add_header('<script type="text/javascript" src="' . scriptPath . '/plugins/gallery/tagging.js"></script>');
-  
-  $template->tpl_strings['PAGE_NAME'] = 'Gallery image: ' . htmlspecialchars($row['img_title']);
-  if ( is_object(@$GLOBALS['output']) )
-  {
-    global $output;
-    $output->set_title('Gallery image: ' . $row['img_title']);
-  }
-  else if ( method_exists($template, 'assign_vars') )
-  {
-    $template->assign_vars(array(
-        'PAGE_NAME' => 'Gallery image: ' . htmlspecialchars($row['img_title'])
-      ));
-  }
-  $title_spacey = strtolower(htmlspecialchars($row['img_title']));
-  
-  $template->header();
-  
-  $img_id = intval($img_id);
-  $bc_folders = gallery_imgid_to_folder($img_id);
-  $bc_folders = array_reverse($bc_folders);
-  $bc_url = '';
-  $breadcrumbs = array();
-  $breadcrumbs[] = '<a href="' . makeUrlNS('Special', 'Gallery') . '">Gallery index</a>';
-  
-  foreach ( $bc_folders as $folder )
-  {
-    $bc_url .= '/' . dirtify_page_id($folder);
-    $breadcrumbs[] = '<a href="' . makeUrlNS('Special', 'Gallery' . $bc_url, false, true) . '">' . htmlspecialchars($folder) . '</a>';
-  }
-  
-  $breadcrumbs[] = htmlspecialchars($row['img_title']);
-  
-  // From here, this breadcrumb stuff is a piece of... sourdough French bread :-) *smacks lips*
-  echo '<div class="breadcrumbs" style="padding: 4px; margin-bottom: 7px;">';
-  // The actual breadcrumbs
-  echo '<small>' . implode(' &raquo; ', $breadcrumbs) . '</small>';
-  echo '</div>';
-  
-  echo '<div style="text-align: center; margin: 10px auto; border: 1px solid #DDDDDD; padding: 7px 10px; display: table;">';
-  $img_url  = makeUrlNS('Special', 'GalleryFetcher/preview/' . $img_id);
-  $img_href = makeUrlNS('Special', 'GalleryFetcher/full/' . $img_id);
-  
-  // The actual image
-  $iehack = ( strstr(@$_SERVER['HTTP_USER_AGENT'], 'MSIE') ) ? ' style="width: 1px;"' : '';
-  echo '<div snapr:imgid="' . $img_id . '"' . $iehack . '><img onload="snapr_lock_onload_img = false; snapr_tags_onload_real();" alt="Image preview (640px max width)" src="' . $img_url . '" id="snapr_preview_img" style="border-width: 0; margin-bottom: 5px; display: block;" /></div>';
-  
-  echo '<table border="0" width="100%"><tr><td style="text-align: left; width: 24px;">';
-  
-  // Prev button
-  if ( $prev )
-    echo '<a href="' . $prev_url . '"><img style="border-width: 0px;" alt="&lt; Previous" src="' . scriptPath . '/plugins/gallery/prev.gif" /></a>';
-  //echo '</td><td style="text-align: left;">';
-  // if ( $prev )
-  //   echo '<a href="' . $prev_url . '">previous image</a>';
-  
-  echo '</td><td style="text-align: center; letter-spacing: 5px;">';
-  
-  // Image title
-  echo $title_spacey;
-  
-  echo '</td><td style="text-align: right; width: 24px;">';
-  
-  // Next button
-  if ( $next )
-  //  echo '<a href="' . $next_url . '">next image</a>';
-  //echo '</td><td style="text-align: right;">';
-  if ( $next )
-    echo '<a href="' . $next_url . '"><img style="border-width: 0px;" alt="&lt; Previous" src="' . scriptPath . '/plugins/gallery/next.gif" /></a>';
-  
-  echo '</td></tr>';
-  echo '<tr><td colspan="3">' . "image $folder_this of $folder_total" . '</td></tr>';
-  if ( $perms->get_permissions('gal_full_res') || $have_notes )
-  {
-    echo '<tr><td colspan="3"><small>';
-    
-    if ( $perms->get_permissions('gal_full_res') )
-      echo "<a href=\"$img_href\" onclick=\"window.open(this.href, '', 'toolbar=no,address=no,menus=no,status=no,scrollbars=yes'); return false;\">View in original resolution</a>";
-    
-    if ( $perms->get_permissions('gal_full_res') && $have_notes )
-      echo ' :: ';
-    
-    if ( $have_notes )
-      echo 'Mouse over photo to view tags';
-    
-    echo '</small></td></tr>';
-  }
-  echo '</table>';
-  echo '</div>';
-  
-  if ( $session->user_level >= USER_LEVEL_ADMIN || $perms->get_permissions('snapr_add_tag') )
-  {
-    echo '<div style="float: right;">';
-    if ( $session->user_level >= USER_LEVEL_ADMIN )
-      echo '[ <a href="' . makeUrlNS('Special', 'GalleryUpload', 'edit_img=' . $img_id, true) . '">edit image</a> ] ';
-    if ( $perms->get_permissions('snapr_add_tag') )
-      echo '[ <a href="#" onclick="snapr_add_tag(); return false;"><img alt=" " src="' . scriptPath . '/plugins/gallery/tag-image.gif" style="border-width: 0;" /> add a tag</a> ] ';
-    echo '</div>';
-  }
-  
-  if ( !empty($row['img_desc']) )
-  {
-    echo '<h2>Image description</h2>';
-    
-    $desc = RenderMan::render($row['img_desc']);
-    echo $desc;
-  }
-  
-  echo '<div class="tblholder" style="font-size: smaller; display: table;' . ( empty($row['img_desc']) ? '' : 'margin: 0 auto;' ) . '">
-          <table border="0" cellspacing="1" cellpadding="3">';
-  
-  // By the time I got to this point, it was 1:32AM (I was on vacation) and my 5-hour playlist on my iPod had been around about 3 times today.
-  // So I'm glad this is like the last thing on the list tonight.
-  
-  $ext = get_file_extension($row['img_filename']);
-  $ext = strtoupper($ext);
-  
-  echo '<tr><th colspan="2">Image details</th></tr>';
-  echo '<tr><td class="row2">Uploaded:</td><td class="row1">' . date('F d, Y h:i a', $row['img_time_upload']) . '</td></tr>';
-  echo '<tr><td class="row2">Last modified:</td><td class="row1">' . date('F d, Y h:i a', $row['img_time_mod']) . '</td></tr>';
-  echo '<tr><td class="row2">Original format:</td><td class="row1">' . $ext . '</td></tr>';
-  echo '<tr><td class="row3" colspan="2" style="text-align: center;"><a href="' . makeUrlNS('Special', 'GalleryFetcher/full/' . $img_id, 'download', 'true') . '">Download image</a></td></tr>';
-          
-  echo '</table></div>';
-  
-  $template->footer();
+	global $db, $session, $paths, $template, $plugins; // Common objects
+	
+	if ( $page->namespace != 'Gallery' )
+		return false;
+	
+	if ( $page->page_id == 'Root' )
+	{
+		page_Special_Gallery();
+		return true;
+	}
+	
+	$row =& $page->image_info;
+	
+	$db->free_result();
+	
+	$img_id = $row['img_id'];
+	
+	if ( !$row['folder_parent'] )
+		$row['folder_parent'] = ' IS NULL';
+	else
+		$row['folder_parent'] = '=' . $row['folder_parent'];
+	
+	// Fetch image parent properties
+	$q = $db->sql_query('SELECT img_id, img_title FROM '.table_prefix.'gallery WHERE folder_parent' . $row['folder_parent'] . ' AND is_folder!=1 ORDER BY img_title ASC;');
+	if ( !$q )
+		$db->_die();
+	
+	$folder_total = $db->numrows();
+	$folder_this = 0;
+	$prev = false;
+	$next = false;
+	$next_title = '';
+	$prev_title = '';
+	
+	$i = 0;
+	
+	while ( $r = $db->fetchrow() )
+	{
+		$i++;
+		if ( $i == $folder_total && $r['img_id'] == $img_id )
+		{
+			$folder_this = $i;
+			$next = false;
+		}
+		else if ( $i < $folder_total && $r['img_id'] == $img_id )
+		{
+			$folder_this = $i;
+			$next = true;
+		}
+		else
+		{
+			if ( $next )
+			{
+				$next = $r['img_id'];
+				$next_title = $r['img_title'];
+				break;
+			}
+			$prev = $r['img_id'];
+			$prev_title = $r['img_title'];
+		}
+	}
+	
+	if ( $next )
+	{
+		$next_sanitized = sanitize_page_id($next_title);
+		$next_url = ( isset($hier) ) ? makeUrlNS('Gallery', $hier . $next_sanitized ) : makeUrlNS('Gallery', $next);
+	}
+	if ( $prev )
+	{
+		$prev_sanitized = sanitize_page_id($prev_title);
+		$prev_url = ( isset($hier) ) ? makeUrlNS('Gallery', $hier . $prev_sanitized ) : makeUrlNS('Gallery', $prev);
+	}
+	
+	$db->free_result();
+	
+	$perms = $session->fetch_page_acl(strval($img_id), 'Gallery');
+	
+	if ( isset($_POST['ajax']) && @$_POST['ajax'] === 'true' && isset($_POST['act']) )
+	{
+		$mode =& $_POST['act'];
+		$response = array();
+		switch($mode)
+		{
+			case 'add_tag':
+				if ( !$perms->get_permissions('snapr_add_tag') )
+				{
+					die(snapr_json_encode(array(
+							'mode' => 'error',
+							'error' => 'You don\'t have permission to add tags.'
+						)));
+				}
+				if ( empty($row['img_tags']) )
+				{
+					$row['img_tags'] = '[]';
+				}
+				$row['img_tags'] = snapr_json_decode($row['img_tags']);
+				
+				$canvas_data = snapr_json_decode($_POST['canvas_params']);
+				$tag_data = array(
+						'tag' => sanitize_html($_POST['tag']),
+						'canvas_data' => $canvas_data
+					);
+				$row['img_tags'][] = $tag_data;
+				$tag_data['note_id'] = count($row['img_tags']) - 1;
+				$tag_data['mode'] = 'add';
+				$tag_data['initial_hide'] = false;
+				$tag_data['auth_delete'] = true;
+				
+				$row['img_tags'] = snapr_json_encode($row['img_tags']);
+				$row['img_tags'] = $db->escape($row['img_tags']);
+				$q = $db->sql_query('UPDATE ' . table_prefix . "gallery SET img_tags = '{$row['img_tags']}' WHERE img_id = $img_id;");
+				if ( !$q )
+					$db->die_json();
+				
+				$response[] = $tag_data;
+				break;
+			case 'del_tag':
+				if ( !$perms->get_permissions('snapr_add_tag') )
+				{
+					die(snapr_json_encode(array(
+							'mode' => 'error',
+							'error' => 'You don\'t have permission to add tags.'
+						)));
+				}
+				if ( empty($row['img_tags']) )
+				{
+					$row['img_tags'] = '[]';
+				}
+				$row['img_tags'] = snapr_json_decode($row['img_tags']);
+				
+				$tag_id = intval(@$_POST['tag_id']);
+				if ( isset($row['img_tags'][$tag_id]) )
+					unset($row['img_tags'][$tag_id]);
+				
+				$row['img_tags'] = snapr_json_encode($row['img_tags']);
+				$row['img_tags'] = $db->escape($row['img_tags']);
+				$q = $db->sql_query('UPDATE ' . table_prefix . "gallery SET img_tags = '{$row['img_tags']}' WHERE img_id = $img_id;");
+				if ( !$q )
+					$db->die_json();
+				
+				$response[] = array(
+						'mode' => 'remove',
+						'note_id' => $tag_id
+					);
+				break;
+			case 'edit_tag':
+				if ( !$perms->get_permissions('snapr_add_tag') )
+				{
+					die(snapr_json_encode(array(
+							'mode' => 'error',
+							'error' => 'You don\'t have permission to edit tags.'
+						)));
+				}
+				if ( empty($row['img_tags']) )
+				{
+					$row['img_tags'] = '[]';
+				}
+				$row['img_tags'] = snapr_json_decode($row['img_tags']);
+				
+				$tag_id = intval(@$_POST['tag_id']);
+				if ( isset($row['img_tags'][$tag_id]) )
+				{
+					$row['img_tags'][$tag_id]['tag'] = sanitize_html($_POST['tag']);
+					// copy it
+					$tag_return = $row['img_tags'][$tag_id];
+					unset($tag);
+				}
+				else
+				{
+					die(snapr_json_encode(array(
+							'mode' => 'error',
+							'error' => 'That tag doesn\'t exist.'
+						)));
+				}
+				
+				$row['img_tags'] = snapr_json_encode($row['img_tags']);
+				$row['img_tags'] = $db->escape($row['img_tags']);
+				$q = $db->sql_query('UPDATE ' . table_prefix . "gallery SET img_tags = '{$row['img_tags']}' WHERE img_id = $img_id;");
+				if ( !$q )
+					$db->die_json();
+				
+				$tag_return['mode'] = 'add';
+				$tag_return['canvas_data'] = snapr_json_decode($_POST['canvas_params']);
+				$tag_return['auth_delete'] = $perms->get_permissions('snapr_add_tag');
+				$tag_return['initial_hide'] = false;
+				$tag_return['note_id'] = $tag_id;
+				$response = array($tag_return);
+				
+				break;
+			case 'get_tags':
+				if ( empty($row['img_tags']) )
+				{
+					$row['img_tags'] = '[]';
+				}
+				$response = snapr_json_decode($row['img_tags']);
+				foreach ( $response as $key => $_ )
+				{
+					unset($_);
+					$tag = $response[$key];
+					unset($response[$key]);
+					$tag['note_id'] = intval($key);
+					$tag['mode'] = 'add';
+					$tag['initial_hide'] = true;
+					$tag['auth_delete'] = $perms->get_permissions('snapr_add_tag');
+					$response[intval($key)] = $tag;
+				}
+				$response = array_values($response);
+				unset($tag);
+				break;
+		}
+		$encoded = snapr_json_encode($response);
+		header('Content-type: text/plain');
+		echo $encoded;
+		return true;
+	}
+	
+	$have_notes = ( empty($row['img_tags']) ) ? false : ( count(snapr_json_decode($row['img_tags'])) > 0 );
+	
+	$template->add_header('<script type="text/javascript" src="' . scriptPath . '/plugins/gallery/canvas.js"></script>');
+	$template->add_header('<script type="text/javascript" src="' . scriptPath . '/plugins/gallery/tagging.js"></script>');
+	
+	$template->tpl_strings['PAGE_NAME'] = 'Gallery image: ' . htmlspecialchars($row['img_title']);
+	if ( is_object(@$GLOBALS['output']) )
+	{
+		global $output;
+		$output->set_title('Gallery image: ' . $row['img_title']);
+	}
+	else if ( method_exists($template, 'assign_vars') )
+	{
+		$template->assign_vars(array(
+				'PAGE_NAME' => 'Gallery image: ' . htmlspecialchars($row['img_title'])
+			));
+	}
+	$title_spacey = strtolower(htmlspecialchars($row['img_title']));
+	
+	$template->header();
+	
+	$img_id = intval($img_id);
+	$bc_folders = gallery_imgid_to_folder($img_id);
+	$bc_folders = array_reverse($bc_folders);
+	$bc_url = '';
+	$breadcrumbs = array();
+	$breadcrumbs[] = '<a href="' . makeUrlNS('Special', 'Gallery') . '">Gallery index</a>';
+	
+	foreach ( $bc_folders as $folder )
+	{
+		$bc_url .= '/' . dirtify_page_id($folder);
+		$breadcrumbs[] = '<a href="' . makeUrlNS('Special', 'Gallery' . $bc_url, false, true) . '">' . htmlspecialchars($folder) . '</a>';
+	}
+	
+	$breadcrumbs[] = htmlspecialchars($row['img_title']);
+	
+	// From here, this breadcrumb stuff is a piece of... sourdough French bread :-) *smacks lips*
+	echo '<div class="breadcrumbs" style="padding: 4px; margin-bottom: 7px;">';
+	// The actual breadcrumbs
+	echo '<small>' . implode(' &raquo; ', $breadcrumbs) . '</small>';
+	echo '</div>';
+	
+	echo '<div style="text-align: center; margin: 10px auto; border: 1px solid #DDDDDD; padding: 7px 10px; display: table;">';
+	$img_url  = makeUrlNS('Special', 'GalleryFetcher/preview/' . $img_id);
+	$img_href = makeUrlNS('Special', 'GalleryFetcher/full/' . $img_id);
+	
+	// The actual image
+	$iehack = ( strstr(@$_SERVER['HTTP_USER_AGENT'], 'MSIE') ) ? ' style="width: 1px;"' : '';
+	echo '<div snapr:imgid="' . $img_id . '"' . $iehack . '><img onload="snapr_lock_onload_img = false; snapr_tags_onload_real();" alt="Image preview (640px max width)" src="' . $img_url . '" id="snapr_preview_img" style="border-width: 0; margin-bottom: 5px; display: block;" /></div>';
+	
+	echo '<table border="0" width="100%"><tr><td style="text-align: left; width: 24px;">';
+	
+	// Prev button
+	if ( $prev )
+		echo '<a href="' . $prev_url . '"><img style="border-width: 0px;" alt="&lt; Previous" src="' . scriptPath . '/plugins/gallery/prev.gif" /></a>';
+	//echo '</td><td style="text-align: left;">';
+	// if ( $prev )
+	//   echo '<a href="' . $prev_url . '">previous image</a>';
+	
+	echo '</td><td style="text-align: center; letter-spacing: 5px;">';
+	
+	// Image title
+	echo $title_spacey;
+	
+	echo '</td><td style="text-align: right; width: 24px;">';
+	
+	// Next button
+	if ( $next )
+	//  echo '<a href="' . $next_url . '">next image</a>';
+	//echo '</td><td style="text-align: right;">';
+	if ( $next )
+		echo '<a href="' . $next_url . '"><img style="border-width: 0px;" alt="&lt; Previous" src="' . scriptPath . '/plugins/gallery/next.gif" /></a>';
+	
+	echo '</td></tr>';
+	echo '<tr><td colspan="3">' . "image $folder_this of $folder_total" . '</td></tr>';
+	if ( $perms->get_permissions('gal_full_res') || $have_notes )
+	{
+		echo '<tr><td colspan="3"><small>';
+		
+		if ( $perms->get_permissions('gal_full_res') )
+			echo "<a href=\"$img_href\" onclick=\"window.open(this.href, '', 'toolbar=no,address=no,menus=no,status=no,scrollbars=yes'); return false;\">View in original resolution</a>";
+		
+		if ( $perms->get_permissions('gal_full_res') && $have_notes )
+			echo ' :: ';
+		
+		if ( $have_notes )
+			echo 'Mouse over photo to view tags';
+		
+		echo '</small></td></tr>';
+	}
+	echo '</table>';
+	echo '</div>';
+	
+	if ( $session->user_level >= USER_LEVEL_ADMIN || $perms->get_permissions('snapr_add_tag') )
+	{
+		echo '<div style="float: right;">';
+		if ( $session->user_level >= USER_LEVEL_ADMIN )
+			echo '[ <a href="' . makeUrlNS('Special', 'GalleryUpload', 'edit_img=' . $img_id, true) . '">edit image</a> ] ';
+		if ( $perms->get_permissions('snapr_add_tag') )
+			echo '[ <a href="#" onclick="snapr_add_tag(); return false;"><img alt=" " src="' . scriptPath . '/plugins/gallery/tag-image.gif" style="border-width: 0;" /> add a tag</a> ] ';
+		echo '</div>';
+	}
+	
+	if ( !empty($row['img_desc']) )
+	{
+		echo '<h2>Image description</h2>';
+		
+		$desc = RenderMan::render($row['img_desc']);
+		echo $desc;
+	}
+	
+	echo '<div class="tblholder" style="font-size: smaller; display: table;' . ( empty($row['img_desc']) ? '' : 'margin: 0 auto;' ) . '">
+					<table border="0" cellspacing="1" cellpadding="3">';
+	
+	// By the time I got to this point, it was 1:32AM (I was on vacation) and my 5-hour playlist on my iPod had been around about 3 times today.
+	// So I'm glad this is like the last thing on the list tonight.
+	
+	$ext = get_file_extension($row['img_filename']);
+	$ext = strtoupper($ext);
+	
+	$user_link = '';
+	
+	echo '<tr><th colspan="2">Image details</th></tr>';
+	echo '<tr><td class="row2">Uploaded:</td><td class="row1">' . date('F d, Y h:i a', $row['img_time_upload']) . '</td></tr>';
+	echo '<tr><td class="row2">Last modified:</td><td class="row1">' . date('F d, Y h:i a', $row['img_time_mod']) . '</td></tr>';
+	echo '<tr><td class="row2">Original format:</td><td class="row1">' . $ext . '</td></tr>';
+	echo '<tr><td class="row2">Author:</td><td class="row1">' . $user_link . '</td></tr>';
+	echo '<tr><td class="row3" colspan="2" style="text-align: center;"><a href="' . makeUrlNS('Special', 'GalleryFetcher/full/' . $img_id, 'download', 'true') . '">Download image</a></td></tr>';
+					
+	echo '</table></div>';
+	
+	$template->footer();
 }
 
 /**
@@ -374,153 +377,153 @@
 
 class Namespace_Gallery extends Namespace_Default
 {
-  public $image_info;
-  
-  function __construct($page_id, $namespace, $revision_id = 0)
-  {
-    global $db, $session, $paths, $template, $plugins; // Common objects
-      
-    $this->page_id = sanitize_page_id($page_id);
-    $this->namespace = $namespace;
-    $this->revision_id = intval($revision_id);
-    
-    // only do this if calling from the (very heavily feature filled) abstract
-    // this will still be called if you're using your own handler but not replacing the constructor
-    if ( __CLASS__ == 'Namespace_Gallery' )
-    {
-      $this->exists = false;
-      // NOTE! These should already be WELL sanitized before we reach this stage.
-      
-      if ( preg_match('/^[0-9]+$/', $this->page_id) )
-      {
-        $img_id = intval($this->page_id);
-        if ( !$img_id )
-        {
-          $this->exists = false;
-          return;
-        }
-        $q = $db->sql_query('SELECT img_id, img_title, img_desc, print_sizes, img_time_upload, img_time_mod, img_filename, folder_parent, img_tags FROM '.table_prefix.'gallery WHERE img_id=' . $img_id . ';');
-        if ( !$q )
-          $db->_die();
-      }
-      else
-      {
-        // Ech... he sent us a string... parse it and see what we get
-        if ( strstr($this->page_id, '/') )
-        {
-          $folders = explode('/', $this->page_id);
-        }
-        else
-        {
-          $folders = array($this->page_id);
-        }
-        foreach ( $folders as $i => $_crap )
-        {
-          $folder =& $folders[$i];
-          $folder = dirtify_page_id($folder);
-          $folder = str_replace('_', ' ', $folder);
-        }
-        unset($folder);
-        
-        $folders = array_reverse($folders);
-        // This is one of the best MySQL tricks on the market. We're going to reverse-travel a folder path using LEFT JOIN and the incredible power of metacoded SQL
-        $sql = 'SELECT g0.img_id, g0.img_title, g0.img_desc, g0.print_sizes, g0.img_time_upload, g0.img_time_mod, g0.img_filename, g0.folder_parent, g0.img_tags FROM '.table_prefix.'gallery AS g0';
-        $where = "\n  " . 'WHERE g0.img_title=\'' . $db->escape($folders[0]) . '\'';
-        foreach ( $folders as $i => $folder )
-        {
-          if ( $i == 0 )
-            continue;
-          $i_dec = $i - 1;
-          $folder = $db->escape($folder);
-          $sql .= "\n  LEFT JOIN ".table_prefix."gallery AS g{$i}\n    ON ( g{$i}.img_id=g{$i_dec}.folder_parent AND g{$i}.img_title='$folder' )";
-          $where .= "\n    ".'AND g'.$i.'.img_id IS NOT NULL';
-        }
-        $where .= "\n    AND g{$i}.folder_parent IS NULL";
-        $sql .= $where . ';';
-        
-        if ( !$db->sql_query($sql) )
-        {
-          $db->_die('The image metadata could not be loaded.');
-        }
-        
-        // Now that the folder data is no longer needed, we can fool around with it a little
-        $folders = $this->page_id;
-        if ( !strstr($folders, '/') )
-        {
-          $hier = '/';
-        }
-        else
-        {
-          $hier = preg_replace('/\/([^\/]+)$/', '/', $folders);
-          $hier = sanitize_page_id($hier);
-        }
-        
-      }
-      if ( $db->numrows() < 1 )
-      {
-        // Image not found
-        $this->exists = false;
-        $this->cdata = array(
-            'urlname' => $this->page_id,
-            'namespace' => $this->namespace,
-            'name' => 'Image not found',
-            'special' => 0,
-            'visible' => 0,
-            'comments_on' => 0,
-            'protected' => 0,
-            'delvotes' => 0,
-            'delvote_ips' => '',
-            'wiki_mode' => 0,
-            'page_exists' => false,
-            'page_format' => getConfig('default_page_format', 'wikitext')
-          );
-      }
-      else
-      {
-        $this->image_info = $db->fetchrow();
-        $this->exists = true;
-        $this->cdata = array(
-            'urlname' => $this->page_id,
-            'namespace' => $this->namespace,
-            'name' => $this->image_info['img_title'],
-            'special' => 1,
-            'visible' => 1,
-            'comments_on' => 1,
-            'protected' => 0,
-            'delvotes' => 0,
-            'delvote_ips' => '',
-            'wiki_mode' => 0,
-            'page_exists' => true,
-            'page_format' => getConfig('default_page_format', 'wikitext')
-          );
-      }
-      $this->title =& $this->cdata['name'];
-      $this->cdata = Namespace_Default::bake_cdata($this->cdata);
-      
-      $db->free_result();
-    }
-  }
-  
-  function send()
-  {
-    if ( $this->exists )
-    {
-      gallery_namespace_handler($this);
-    }
-    else
-    {
-      global $output;
-      $output->header();
-      $this->error_404();
-      $output->footer();
-    }
-  }
-  
-  function error_404()
-  {
-    echo '<h3>There is no image in the gallery with this ID.</h3>';
-    echo '<p>You have requested an image that couldn\'t be looked up. Please check the URL and try again, or visit the <a href="' . makeUrlNS('Special', 'Gallery') . '">Gallery index</a>.</p>';
-  }
+	public $image_info;
+	
+	function __construct($page_id, $namespace, $revision_id = 0)
+	{
+		global $db, $session, $paths, $template, $plugins; // Common objects
+			
+		$this->page_id = sanitize_page_id($page_id);
+		$this->namespace = $namespace;
+		$this->revision_id = intval($revision_id);
+		
+		// only do this if calling from the (very heavily feature filled) abstract
+		// this will still be called if you're using your own handler but not replacing the constructor
+		if ( __CLASS__ == 'Namespace_Gallery' )
+		{
+			$this->exists = false;
+			// NOTE! These should already be WELL sanitized before we reach this stage.
+			
+			if ( preg_match('/^[0-9]+$/', $this->page_id) )
+			{
+				$img_id = intval($this->page_id);
+				if ( !$img_id )
+				{
+					$this->exists = false;
+					return;
+				}
+				$q = $db->sql_query('SELECT img_id, img_title, img_desc, print_sizes, img_time_upload, img_time_mod, img_filename, folder_parent, img_tags FROM '.table_prefix.'gallery WHERE img_id=' . $img_id . ';');
+				if ( !$q )
+					$db->_die();
+			}
+			else
+			{
+				// Ech... he sent us a string... parse it and see what we get
+				if ( strstr($this->page_id, '/') )
+				{
+					$folders = explode('/', $this->page_id);
+				}
+				else
+				{
+					$folders = array($this->page_id);
+				}
+				foreach ( $folders as $i => $_crap )
+				{
+					$folder =& $folders[$i];
+					$folder = dirtify_page_id($folder);
+					$folder = str_replace('_', ' ', $folder);
+				}
+				unset($folder);
+				
+				$folders = array_reverse($folders);
+				// This is one of the best MySQL tricks on the market. We're going to reverse-travel a folder path using LEFT JOIN and the incredible power of metacoded SQL
+				$sql = 'SELECT g0.img_id, g0.img_title, g0.img_desc, g0.print_sizes, g0.img_time_upload, g0.img_time_mod, g0.img_filename, g0.folder_parent, g0.img_tags FROM '.table_prefix.'gallery AS g0';
+				$where = "\n  " . 'WHERE g0.img_title=\'' . $db->escape($folders[0]) . '\'';
+				foreach ( $folders as $i => $folder )
+				{
+					if ( $i == 0 )
+						continue;
+					$i_dec = $i - 1;
+					$folder = $db->escape($folder);
+					$sql .= "\n  LEFT JOIN ".table_prefix."gallery AS g{$i}\n    ON ( g{$i}.img_id=g{$i_dec}.folder_parent AND g{$i}.img_title='$folder' )";
+					$where .= "\n    ".'AND g'.$i.'.img_id IS NOT NULL';
+				}
+				$where .= "\n    AND g{$i}.folder_parent IS NULL";
+				$sql .= $where . ';';
+				
+				if ( !$db->sql_query($sql) )
+				{
+					$db->_die('The image metadata could not be loaded.');
+				}
+				
+				// Now that the folder data is no longer needed, we can fool around with it a little
+				$folders = $this->page_id;
+				if ( !strstr($folders, '/') )
+				{
+					$hier = '/';
+				}
+				else
+				{
+					$hier = preg_replace('/\/([^\/]+)$/', '/', $folders);
+					$hier = sanitize_page_id($hier);
+				}
+				
+			}
+			if ( $db->numrows() < 1 )
+			{
+				// Image not found
+				$this->exists = false;
+				$this->cdata = array(
+						'urlname' => $this->page_id,
+						'namespace' => $this->namespace,
+						'name' => 'Image not found',
+						'special' => 0,
+						'visible' => 0,
+						'comments_on' => 0,
+						'protected' => 0,
+						'delvotes' => 0,
+						'delvote_ips' => '',
+						'wiki_mode' => 0,
+						'page_exists' => false,
+						'page_format' => getConfig('default_page_format', 'wikitext')
+					);
+			}
+			else
+			{
+				$this->image_info = $db->fetchrow();
+				$this->exists = true;
+				$this->cdata = array(
+						'urlname' => $this->page_id,
+						'namespace' => $this->namespace,
+						'name' => $this->image_info['img_title'],
+						'special' => 1,
+						'visible' => 1,
+						'comments_on' => 1,
+						'protected' => 0,
+						'delvotes' => 0,
+						'delvote_ips' => '',
+						'wiki_mode' => 0,
+						'page_exists' => true,
+						'page_format' => getConfig('default_page_format', 'wikitext')
+					);
+			}
+			$this->title =& $this->cdata['name'];
+			$this->cdata = Namespace_Default::bake_cdata($this->cdata);
+			
+			$db->free_result();
+		}
+	}
+	
+	function send()
+	{
+		if ( $this->exists )
+		{
+			gallery_namespace_handler($this);
+		}
+		else
+		{
+			global $output;
+			$output->header();
+			$this->error_404();
+			$output->footer();
+		}
+	}
+	
+	function error_404()
+	{
+		echo '<h3>There is no image in the gallery with this ID.</h3>';
+		echo '<p>You have requested an image that couldn\'t be looked up. Please check the URL and try again, or visit the <a href="' . makeUrlNS('Special', 'Gallery') . '">Gallery index</a>.</p>';
+	}
 }
 
 ?>