Initial population; browser, viewer, uploader, and security are working
authorDan
Wed, 25 Jul 2007 16:36:26 -0400
changeset 0 7caf561c50ee
child 1 94af07c8f2f3
Initial population; browser, viewer, uploader, and security are working
README
TODO
plugins/Gallery.php
plugins/gallery/.upload.php.marks
plugins/gallery/browser.css
plugins/gallery/browser.php
plugins/gallery/chevron-prelight.gif
plugins/gallery/chevron.gif
plugins/gallery/denied.png
plugins/gallery/dropdown.css
plugins/gallery/fetcher.php
plugins/gallery/folder.png
plugins/gallery/functions.php
plugins/gallery/gallery-bits.js
plugins/gallery/next-prelight.gif
plugins/gallery/next.gif
plugins/gallery/nssetup.php
plugins/gallery/prev-prelight.gif
plugins/gallery/prev.gif
plugins/gallery/toggle-closed.png
plugins/gallery/toggle-head-bg.gif
plugins/gallery/toggle-open.png
plugins/gallery/upload.php
plugins/gallery/viewimage.php
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README	Wed Jul 25 16:36:26 2007 -0400
@@ -0,0 +1,45 @@
+Snapr - Image gallery in a snap!
+Another fine plugin for Enano CMS
+--------------------------------------------------
+
+Welcome and thanks for downloading Snapr, the Enano plugin designed to make
+turning your site into a photo or image gallery a snap.
+
+Copyright
+--------------------------------------------------
+
+Snapr is Free Software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either Version 2 of the License, or (at your option) any later
+version.
+
+Snapr is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranties of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for details.
+
+You should have received a copy of the GNU General Public License along with
+this package; if not, please consult the Enano CMS website at
+<http://enanocms.org/>, or view a copy of the GNU General Public license with
+the GPL file included with the official Enano distribution files.
+
+Installing Snapr
+--------------------------------------------------
+
+To install the Snapr plugin on your site, just extract the zip or tarball file
+into your Enano root folder. If you need to upload Snapr manually, upload the
+file plugins/Gallery.php and the entire folder plugins/gallery/ to the
+plugins/ folder inside your Enano root.
+
+To enable Snapr plugin, go to the Administration panel, expand General, click
+Manage Plugins, and click Enable in the row showing the Snapr plugin.
+
+Obtaining support
+--------------------------------------------------
+
+Support for Snapr is available via the same support channels as Enano, as it
+is developed and maintained by the official Enano team. For support options,
+both free and commercial, please see <http://enanocms.org/Contact_us>.
+
+Have fun!
+
+-- The Enano team
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/TODO	Wed Jul 25 16:36:26 2007 -0400
@@ -0,0 +1,8 @@
+Snapr - Image gallery in a snap!
+Another fine plugin for Enano CMS
+
+TODO:
+--------------------------------------------------
+
+[ ] Figure out a PageProcessor hack to make ACLs work for folders as well as images
+[ ] Possibly rename Special:GalleryUpload to something more accurate, as it's become a one-for-everything administration center for the gallery
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/Gallery.php	Wed Jul 25 16:36:26 2007 -0400
@@ -0,0 +1,50 @@
+<?php
+/*
+Plugin Name: Snapr
+Plugin URI: http://enanocms.org/Enano.Img_Gallery
+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.1 beta 1
+Author URI: http://enanocms.org/
+*/
+
+global $db, $session, $paths, $template, $plugins; // Common objects
+
+define('GALLERY_VERSION', '0.1b1');
+
+$magick_path = getConfig('imagemagick_path');
+if ( !file_exists($magick_path) || !is_executable($magick_path) )
+{
+  $fn = basename(__FILE__);
+  setConfig("plugin_$fn", '0');
+  die('Snapr: You must have ImageMagick installed and working to use this plugin. The plugin has been disabled, please setup ImageMagick and then re-enable it.');
+}
+
+if ( !getConfig('gallery_version') )
+{
+  $q = $db->sql_query('CREATE TABLE '.table_prefix.'gallery(
+                        img_id int(12) NOT NULL auto_increment,
+                        is_folder tinyint(1) NOT NULL DEFAULT 0,
+                        folder_parent int(12) DEFAULT NULL,
+                        img_title varchar(255) NOT NULL DEFAULT \'\',
+                        img_desc longtext NOT NULL DEFAULT \'\',
+                        print_sizes longtext NOT NULL DEFAULT \'\',
+                        img_filename varchar(255) NOT NULL,
+                        img_time_upload int(12) NOT NULL DEFAULT 0,
+                        img_time_mod int(12) NOT NULL DEFAULT 0,
+                        PRIMARY KEY ( img_id )
+                      );');
+  if ( !$q )
+    $db->_die();
+  
+  setConfig('gallery_version', GALLERY_VERSION);
+}
+
+require( ENANO_ROOT . '/plugins/gallery/functions.php' );
+require( ENANO_ROOT . '/plugins/gallery/nssetup.php' );
+require( ENANO_ROOT . '/plugins/gallery/viewimage.php' );
+require( ENANO_ROOT . '/plugins/gallery/browser.php' );
+require( ENANO_ROOT . '/plugins/gallery/upload.php' );
+require( ENANO_ROOT . '/plugins/gallery/fetcher.php' );
+
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/gallery/.upload.php.marks	Wed Jul 25 16:36:26 2007 -0400
@@ -0,0 +1,4 @@
+!u;8818;8818
+!e;12994;12994
+!z;22686;22686
+!f;35274;35274
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/gallery/browser.css	Wed Jul 25 16:36:26 2007 -0400
@@ -0,0 +1,24 @@
+/*
+ * CSS for gallery browser
+ */
+
+div.gallery_icon {
+  border-width: 1px;
+  border-style: solid;
+  border-color: #FFFFFF;
+  cursor: pointer;
+  text-align: center;
+  display: table;
+  margin: 0 auto;
+  padding: 4px;
+}
+
+div.gallery_icon:hover {
+  border-color: #CCCCCC;
+}
+
+div.gallery_icon img.gallery_thumb {
+  display: block;
+  border-width: 0;
+  margin: 0 auto 6px auto;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/gallery/browser.php	Wed Jul 25 16:36:26 2007 -0400
@@ -0,0 +1,455 @@
+<?php
+
+/*
+ * Snapr
+ * Version 0.1 beta 1
+ * Copyright (C) 2007 Dan Fuhry
+ *
+ * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ */
+
+##
+## BROWSER INTERFACE
+##
+
+$plugins->attachHook('base_classes_initted', '
+  global $paths;
+    $paths->add_page(Array(
+      \'name\'=>\'Image gallery\',
+      \'urlname\'=>\'Gallery\',
+      \'namespace\'=>\'Special\',
+      \'special\'=>0,\'visible\'=>1,\'comments_on\'=>0,\'protected\'=>1,\'delvotes\'=>0,\'delvote_ips\'=>\'\',
+      ));
+  ');
+
+/**
+ * Class to handle building the HTML for gallery pages. Called by the pagination function.
+ * @package Enano
+ * @subpackage Snapr
+ * @access private
+ */
+ 
+class SnaprFormatter
+{
+  
+  /**
+   * Counter for how many cells we've printed out in this row.
+   * @var int
+   */
+  
+  var $cell_count = 0;
+  
+  /**
+   * Icons to print per row.
+   * @var int
+   */
+  
+  var $icons_per_row = 5;
+  
+  /**
+   * Main render method, called from pagination function
+   * @access private
+   */
+  
+  function render($column_crap, $row, $row_crap)
+  {
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    
+    $out = '';
+    
+    if ( $this->cell_count == $this->icons_per_row )
+    {
+      $out .= '</tr><tr>';
+      $this->cell_count = 0;
+    }
+    $this->cell_count++;
+    
+    $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']);
+    }
+    
+    $image_url_js = addslashes($image_link);
+    $jsclick = ( $session->user_level < USER_LEVEL_ADMIN ) ? ' onclick="window.location=\'' . $image_url_js . '\'"' : '';
+    
+    $out .= '<td style="text-align: center;">
+            <div class="gallery_icon"' . $jsclick . '>';
+    
+    $out .= '<a href="' . $image_link . '"><img alt="&lt;Thumbnail&gt;" class="gallery_thumb" src="' . $image_url . '" /></a>';
+    
+    if ( $session->user_level < USER_LEVEL_ADMIN )
+    {
+      $out .= $title_safe;
+    }
+    else if ( $session->user_level >= USER_LEVEL_ADMIN )
+    {
+      $out .= '<div class="menu_nojs" style="text-align: center;"><a href="#" onclick="return false;" style="margin: 0 auto;">' . $title_safe . '</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 .= '</td>';
+    
+    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();
+  
+  global $theme;
+  $fn = ENANO_ROOT . '/themes/' . $template->theme . '/theme.cfg';
+  require( $fn );
+  if ( isset($theme['snapr_gallery_rows']) )
+  {
+    $rows_in_browser = intval($theme['snapr_gallery_rows']);
+    if ( empty($rows_in_browser) )
+    {
+      $rows_in_browser = 5;
+    }
+  }
+  else
+  {
+    $rows_in_browser = 5;
+  }
+  
+  $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 FROM ".table_prefix."gallery $where;";
+  
+  // Breadcrumb browser
+  $breadcrumbs = array();
+  $breadcrumbs[] = '<a href="' . makeUrlNS('Special', 'Gallery') . '">Gallery index</a>';
+  
+  $breadcrumb_urlcache = '';
+  
+  // 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" />');
+  
+  $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
+    
+    /*
+    $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;
+    
+    $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']) && 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);
+    }
+  }
+  
+  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="tblholder" 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 '<b><a href="' . makeUrlNS('Special', 'GalleryUpload') . '">Upload new image(s)</a></b>';
+    echo '</div>';
+  }
+  // The actual breadcrumbs
+  echo '<b><small>' . implode(' &raquo; ', $breadcrumbs) . '</small></b>';
+  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>';
+  
+  $db->sql_data_seek(0, $img_query);
+  
+  //
+  // Main fetcher
+  //
+  
+  $renderer = new SnaprFormatter();
+  $callers = array(
+    'img_id' => array($renderer, 'render')
+    );
+  
+  $renderer->icons_per_row = $rows_in_browser;
+  
+  $start = 0;
+  if ( isset($_GET['start']) && preg_match('/^[0-9]+$/', $_GET['start']) )
+  {
+    $start = intval($_GET['start']);
+  }
+  
+  $per_page = $rows_in_browser * 5;
+  
+  $html = paginate($img_query, '{img_id}', $db->numrows($img_query), makeUrl($paths->fullpage, 'start=%s', true), $start, $per_page, $callers, '<table border="0" cellspacing="8"><tr>', '</tr></table>');
+  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();
+  
+}
+
+?>
Binary file plugins/gallery/chevron-prelight.gif has changed
Binary file plugins/gallery/chevron.gif has changed
Binary file plugins/gallery/denied.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/gallery/dropdown.css	Wed Jul 25 16:36:26 2007 -0400
@@ -0,0 +1,76 @@
+div.select-outer {
+  border-width: 1px;
+  border-style: solid;
+  border-color: #FFFFFF;
+  padding: 3px 21px 3px 3px !important;
+  cursor: pointer;
+  display: table;
+  background-image: url(chevron.gif);
+  background-position: 95% 8px;
+  background-repeat: no-repeat;
+  background-color: #FFFFFF;
+  position: absolute;
+  font-family: arial, sans-serif;
+  font-size: 8pt;
+  margin: 4px 0 0 0 !important;
+  text-align: left;
+}
+div.select-outer:hover {
+  border-color: #CCCCCC;
+  background-image: url(chevron-prelight.gif);
+}
+div.select-inner {
+  display: none;
+  background-color: #FFFFFF;
+  margin-right: -18px;
+}
+div.select-outer:hover div.select-inner {
+  display: block;
+}
+div.select-inner a {
+  display: block;
+  padding: 2px;
+  color: #000000;
+}
+div.select-inner a:hover {
+  display: block;
+  color: #0000FF;
+  background-color: #F0F0FF;
+}
+
+div.toggle {
+  border: 1px solid #2c458a;
+  background-color: #F0FDFF;
+  padding: 1px;
+  margin: 10px 0;
+}
+
+div.toggle div.head {
+  line-height: 22px;
+  background-image: url(toggle-head-bg.gif);
+  padding: 2px 7px 0 7px;
+  margin: 0;
+  color: #FFFFFF;
+  cursor: pointer;
+}
+
+div.toggle div.head img.toggler {
+  float: right;
+  margin: 3px;
+  display: block;
+}
+
+div.toggle div.body {
+  background-color: #e3e7f3;
+  border: 1px solid #849ad6;
+  padding: 10px;
+  margin: 1px 0 0 0;
+  display: none;
+  max-height: 300px;
+  clip: rect(0px,auto,auto,0px);
+  overflow: auto;
+}
+
+div.toggle div.body label:hover {
+  background-color: #f3f7ff;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/gallery/fetcher.php	Wed Jul 25 16:36:26 2007 -0400
@@ -0,0 +1,116 @@
+<?php
+
+/*
+ * Snapr
+ * Version 0.1 beta 1
+ * Copyright (C) 2007 Dan Fuhry
+ *
+ * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ */
+
+##
+## IMAGE FILE FETCHER
+##
+
+$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\'=>\'\',
+      ));
+  ');
+
+function page_Special_GalleryFetcher()
+{
+  global $db, $session, $paths, $template, $plugins; // Common objects
+  
+  $type = $paths->getParam(0);
+  if ( !in_array($type, array('thumb', 'preview', 'full')) )
+  {
+    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_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';
+      break;
+    case 'preview':
+      $filename = ENANO_ROOT . '/cache/' . $row['img_filename'] . '-preview.jpg';
+      $mimetype = 'image/jpeg';
+      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;
+    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);
+  
+  header('Content-type: '   . $mimetype);
+  header('Content-length: ' . strlen($contents));
+  header('Last-Modified: '  . date('r', $row['img_time_mod']));
+  
+  echo $contents;
+  
+}
+
+?>
Binary file plugins/gallery/folder.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/gallery/functions.php	Wed Jul 25 16:36:26 2007 -0400
@@ -0,0 +1,317 @@
+<?php
+
+/*
+ * Snapr
+ * Version 0.1 beta 1
+ * Copyright (C) 2007 Dan Fuhry
+ *
+ * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ */
+
+/**
+ * Generates a random filename for Snapr images.
+ * @param int $length Optional - length of filename
+ * @return string
+ */
+ 
+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;
+}
+
+/**
+ * Returns the extension of a file.
+ * @param string file
+ * @return string
+ */
+
+function get_file_extension($file)
+{
+  return substr($file, ( strrpos($file, '.') + 1 ));
+}
+
+/**
+ * For a given image ID, return the folder hierarchy.
+ * @param int The image ID
+ * @return array
+ */
+
+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;
+}
+
+/**
+ * Generates a hierarchy of Gallery folders.
+ * @return array
+ */
+
+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();
+  }
+  
+  $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;
+  
+}
+
+/**
+ * Generates HTML for a folder selector.
+ * @param string The form field name, defaults to folder_id.
+ * @param bool Whether to auto-select the root or not. Defaults to true.
+ * @return string
+ */
+
+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;
+}
+
+// 
+
+/**
+ * Inner loop for form field generator (needs to call itself recursively)
+ * @access private
+ */
+
+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;
+}
+
+/**
+ * Returns an array containing the IDs of all of the given folder ID's children. Recursive function.
+ * @param int ID of folder
+ */
+
+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;
+  
+}
+
+/**
+ * Lists all normal files within a given directory. Recursive function. Can also return the list of directories in the second parameter by reference.
+ * @param string Directory to search
+ * @param array Variable in which to store 
+ * @return array Not multi-depth
+ */
+
+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;
+}
+
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/gallery/gallery-bits.js	Wed Jul 25 16:36:26 2007 -0400
@@ -0,0 +1,24 @@
+/*
+ * Misc functions for Enano.Img Gallery.
+ */
+
+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 == 'block' )
+  {
+    elem.style.display = 'none';
+    img.src = img_close;
+  }
+  else
+  {
+    elem.style.display = 'block';
+    img.src = img_open;
+  }
+}
+
+
Binary file plugins/gallery/next-prelight.gif has changed
Binary file plugins/gallery/next.gif has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/gallery/nssetup.php	Wed Jul 25 16:36:26 2007 -0400
@@ -0,0 +1,31 @@
+<?php
+
+/*
+ * Snapr
+ * Version 0.1 beta 1
+ * Copyright (C) 2007 Dan Fuhry
+ *
+ * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ */
+
+$plugins->attachHook('acl_rule_init', 'gallery_setup_namespace($this);');
+
+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->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);
+}
+
+?>
Binary file plugins/gallery/prev-prelight.gif has changed
Binary file plugins/gallery/prev.gif has changed
Binary file plugins/gallery/toggle-closed.png has changed
Binary file plugins/gallery/toggle-head-bg.gif has changed
Binary file plugins/gallery/toggle-open.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/gallery/upload.php	Wed Jul 25 16:36:26 2007 -0400
@@ -0,0 +1,1063 @@
+<?php
+
+/*
+ * Snapr
+ * Version 0.1 beta 1
+ * Copyright (C) 2007 Dan Fuhry
+ *
+ * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ */
+
+##
+## UPLOAD INTERFACE
+##
+
+$plugins->attachHook('base_classes_initted', '
+  global $paths;
+    $paths->add_page(Array(
+      \'name\'=>\'Image gallery upload\',
+      \'urlname\'=>\'GalleryUpload\',
+      \'namespace\'=>\'Special\',
+      \'special\'=>0,\'visible\'=>1,\'comments_on\'=>0,\'protected\'=>1,\'delvotes\'=>0,\'delvote_ips\'=>\'\',
+      ));
+  ');
+
+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>');
+  
+  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;
+              }
+              $magick = getConfig('imagemagick_path');
+              $command = "$magick '{$filename}' -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/' . $arr_img_data[ $img_data['id'] ]['img_filename'] . '-preview.jpg';
+              if ( !unlink($preview_filename) )
+              {
+                $errors[] = "Could not delete $preview_filename";
+                break 2;
+              }
+              $magick = getConfig('imagemagick_path');
+              $command = "$magick '{$filename}' -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;
+              }
+              $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 )
+          {
+            $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">';
+    
+    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) . '/' . 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(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(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(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 folder override 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 '<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.
+          //
+          
+          // for debugging only
+          system('rm -fr ' . ENANO_ROOT . '/cache/temp');
+          
+          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 = capitalize_first_letter(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) VALUES{$subq};";
+            
+            if ( !$db->sql_query($sql) )
+              $db->_die();
+            
+            $idlist[] = $db->insert_id();
+            
+            // Create thumbnail image
+            $thumb_filename = ENANO_ROOT . '/cache/' . $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/' . $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;
+            }
+            
+            $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 = capitalize_first_letter(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) 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">';
+  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(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();
+  
+}
+
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/gallery/viewimage.php	Wed Jul 25 16:36:26 2007 -0400
@@ -0,0 +1,270 @@
+<?php
+
+/*
+ * Snapr
+ * Version 0.1 beta 1
+ * Copyright (C) 2007 Dan Fuhry
+ *
+ * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ */
+
+##
+## GALLERY NAMESPACE HANDLER
+##
+
+$plugins->attachHook('page_not_found', 'gallery_namespace_handler($this);');
+
+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;
+  }
+  
+  if ( preg_match('/^[0-9]+$/', $page->page_id) )
+  {
+    $img_id = intval($page->page_id);
+    if ( !$img_id )
+      return false;
+    $q = $db->sql_query('SELECT img_id, img_title, img_desc, print_sizes, img_time_upload, img_time_mod, folder_parent 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($page->page_id, '/') )
+    {
+      $folders = explode('/', $page->page_id);
+    }
+    else
+    {
+      $folders = array($page->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.folder_parent 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 = $page->page_id;
+    if ( !strstr($folders, '/') )
+    {
+      $hier = '/';
+    }
+    else
+    {
+      $hier = preg_replace('/\/([^\/]+)$/', '/', $folders);
+      $hier = sanitize_page_id($hier);
+    }
+    
+  }
+  if ( $db->numrows() < 1 )
+  {
+    // Image not found - show custom error message
+    $template->header();
+    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>';
+    $template->footer();
+    return false;
+  }
+  $row = $db->fetchrow();
+  
+  $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();
+  
+  $template->tpl_strings['PAGE_NAME'] = 'Gallery image: ' . htmlspecialchars($row['img_title']);
+  $title_spacey = strtolower(htmlspecialchars($row['img_title']));
+  
+  $perms = $session->fetch_page_acl(strval($img_id), 'Gallery');
+  
+  $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="tblholder" style="padding: 4px; margin-bottom: 7px;">';
+  // The actual breadcrumbs
+  echo '<b><small>' . implode(' &raquo; ', $breadcrumbs) . '</small></b>';
+  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);
+  
+  if ( $perms->get_permissions('gal_full_res') )
+  {
+    echo '<a href="' . $img_href . '" title="Click to view this image at full resolution, right click to save image" onclick="window.open(this.href, \'\', \'toolbar=no,address=no,menus=no,status=no\'); return false;">';
+  }
+  
+  echo '<img alt="Image preview (640px max width)" src="' . $img_url . '" style="border-width: 0; margin-bottom: 5px; display: block;" />';
+  
+  if ( $perms->get_permissions('gal_full_res') )
+  {
+    echo '</a>';
+  }
+  
+  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>';
+  echo '</table>';
+  echo '</div>';
+  
+  if ( $session->user_level >= USER_LEVEL_ADMIN )
+  {
+    echo '<div style="float: right;">[ <a href="' . makeUrlNS('Special', 'GalleryUpload', 'edit_img=' . $img_id, true) . '">edit image</a> ]</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.
+  
+  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 '</table></div>';
+  
+  $template->footer();
+    
+}
+
+?>