changeset 0:ac688606ec4b

Initial import of code.
author Matti Hamalainen <ccr@tnsp.org>
date Wed, 13 May 2015 07:27:40 +0300
parents
children a07447c02b13
files basic.css example.cfg mgallery.inc.php mgallery.php mgtool.php
diffstat 5 files changed, 1741 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/basic.css	Wed May 13 07:27:40 2015 +0300
@@ -0,0 +1,205 @@
+body {
+	color: #ded;
+	background: #121;
+	font-family: Verdana, Arial, helvetica, sans-serif;
+	font-size: 10pt;
+}
+
+a {
+	color: white;
+}
+
+a:hover {
+	text-decoration: none;
+	color: yellow;
+	text-shadow: 1px 1px 2px #000;
+}
+
+
+#contents {
+	background: #565;
+	border-radius: 0.5em;
+	padding: 0.5em;
+}
+
+#contents h1 {
+	font-size: 24pt;
+	font-family: helvetica;
+	margin-top: 0;
+	margin-bottom: 0.1em;
+	background: #343;
+	padding: 5pt;
+	color: white;
+	text-shadow: 2px 2px 2px #000;
+
+	border-radius: 8px;
+}
+
+h2,h3 {
+	padding: 5pt;
+	border-radius: 8px;
+	background: #343;
+	color: white;
+	text-shadow: 2px 2px 2px #000;
+}
+
+
+div.pageInfoFooter {
+	margin-top: 0.5em;
+	padding: 0.25em;
+	text-align: right;
+	font-size: 0.75em;
+	text-shadow: 1px 1px 1px black;
+	color: white;
+}
+
+
+/*
+ * Albums
+ */
+div.albumHeaderText {
+	padding: 0.5em;
+	font-size: 1.25em;
+}
+
+table {
+	width: 100%;
+}
+
+td {
+	width: 15%;
+	padding: 0.5em;
+	text-align: center;
+}
+
+table.albumTable {
+	background: #454;
+	border-radius: 0.5em;
+	border-top: 1px solid #343;
+	border-left: 1px solid #343;
+	border-bottom: 1px solid #787;
+	border-right: 1px solid #787;
+}
+
+table.imageTable td {
+}
+
+
+table a, table a:visited  {
+	text-decoration: none;
+	color: white;
+	outline: 0;
+}
+
+table a:hover, table a:active, table a:focus {
+	text-shadow: 1px 1px 2px #000;
+	text-decoration: underline;
+	color: #f00;
+}
+
+
+
+/*
+ *
+ */
+div.imageBox, div.imageFilename {
+	text-align: center;
+}
+
+div.imageBox img, div.imageCtrl img {
+	border: 0.2em solid black;
+	border-radius: 0.3em;
+	box-shadow: 4px 4px 4px black;
+}
+
+div.imageCtrl {
+	width: 15%;
+}
+
+div.imageBox a:hover img, div.imageBox a:active img, div.imageBox a:focus img,
+div.imageCtrl a:hover img, div.imageCtrl a:active img, div.imageCtrl a:focus img {
+	border: 0.2em solid white;
+}
+
+a:hover img.albumIcon, a:active img.albumIcon, a:focus img.albumIcon {
+	box-shadow: 0px 0px 4px black;
+	background: black;
+	transition: 0.15s;
+}
+
+div.imageCBox {
+	text-align: center;
+}
+
+div.imageCtrl, div.imageCBox div.imageBox {
+	display: inline-block;
+	vertical-align: middle;
+}
+
+div.imageCtrl.next {
+	left: 0;
+}
+
+div.imageCaption {
+	text-align: center;
+	font-size: 1.5em;
+	text-shadow: 1px 1px 2px black;
+	padding: 0.5em;
+}
+
+td div.imageCaption {
+	text-align: center;
+	font-size: 1em;
+	text-shadow: 1px 1px 2px black;
+	padding: 0.5em;
+}
+
+
+div.infoBox {
+	text-align: center;
+	margin: 0.5em;
+	padding: 0.5em;
+	border: 0.2em solid white;
+	border-radius: 0.5em;
+}
+
+span.infoDateTime {
+	display: block;
+	color: white;
+	font-weight: bold;
+	text-shadow: 1px 1px 2px black;
+}
+
+/*
+ *
+ */
+div.naviControls {
+	font-size: 1.5em;
+	text-align: center;
+	margin: 0.5em;
+}
+
+span.naviBreadCrumbItem {
+	color: red;
+}
+
+span.naviBreadCrumbSep:before {
+	content: " → ";
+	color: #0f0;
+	font-weight: bold;
+	font-size: 1.5em;
+}
+
+div.naviBreadCrumbs {
+	text-shadow: 1px 1px 1px black;
+	padding: 0.5em;
+	padding-top: 0;
+	border-radius: 0.3em;
+	font-size: 1.2em;
+
+	background: #454;
+	border-top: 1px solid #343;
+	border-left: 1px solid #343;
+	border-bottom: 1px solid #787;
+	border-right: 1px solid #787;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/example.cfg	Wed May 13 07:27:40 2015 +0300
@@ -0,0 +1,47 @@
+;;;
+;;; MGallery example configuration
+;;;
+
+;;; Gallery title, etc.
+title_prefix         = "My Own Gallery"
+title_sep            = " - "
+page_info            = "<b>MGallery v0.1</b> &copy; Copyright 2015 Tecnic Software productions (TNSP)"
+
+
+;;;
+;;; CSS / layout style
+;;;
+css_select           = yes
+css                  = "/mgallery/basic.css"
+album_icon           = "/mgallery/album_sm.png"
+
+
+;;;
+;;; Important paths and urls
+;;;
+base_path            = "/absolute/path/to/your/gallery"
+base_url             = "/base/url/"
+
+
+;image_url            = "/gallery/"
+;mgallery_php         = "mgallery.php"
+;tn_path              = "tn/"
+;format_exts          = "\.jpg|\.png|\.gif|\.jpeg"
+
+;captions_file        = "captions.txt"
+;header_file          = "header.txt"
+;info_file            = "gallery.info"
+;cache_file           = ".gallery.cache"
+;clean_urls           = no
+
+
+;;;
+;;; Thumbnail/medium size image settings
+;;;
+;med_suffix           = ".med"
+;tn_width             = 140
+;tn_height            = 100
+;tn_quality           = 80
+;med_width            = 800
+;med_height           = 600
+;med_quality          = 95
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mgallery.inc.php	Wed May 13 07:27:40 2015 +0300
@@ -0,0 +1,301 @@
+<?php
+//
+// Yet Another Image Gallery
+// (C) Copyright 2015 Tecnic Software productions (TNSP)
+//
+
+$mgProgVersion = "v0.4";
+$mgProgInfo = "Programmed by Matti 'ccr' Hamalainen";
+$mgProgEmail = "<ccr@tnsp.org>";
+$mgProgCopyright = "2015 Tecnic Software productions (TNSP)";
+
+
+//
+// Constants for different value types
+//
+define("MG_STR", 1);
+define("MG_INT", 2);
+define("MG_DVA", 3);
+define("MG_BOOL", 4);
+
+
+define("yes", 1);
+define("no", 0);
+
+
+//
+// Navigation control defines
+//
+define("GNAV_TOP"        , 0x01);
+define("GNAV_BOTTOM"     , 0x02);
+define("GNAV_IMG"        , 0x04);
+define("GNAV_TEXT"       , 0x08);
+define("GNAV_SIDE_IMG"   , 0x10);
+define("GNAV_JAVASCRIPT" , 0x20);
+define("GNAV_BREADCRUMBS", 0x40);
+
+
+function mgPathName($path)
+{
+  $tmp = mgCleanPathArray(TRUE, 0, func_num_args(), func_get_args());
+  if (count($tmp) > 0)
+    return implode("/", array_splice($tmp, 0, -1))."/";
+  else
+    return $path;
+}
+
+//
+// Configuration settings and their default values
+//
+$mgDefaults = array(
+  "base_path"        => array(MG_STR, mgPathName(mgRealPath($_SERVER["SCRIPT_FILENAME"]))),
+  "base_url"         => array(MG_STR, mgPathName($_SERVER["PHP_SELF"])),
+  "image_url"        => array(MG_STR, mgPathName($_SERVER["PHP_SELF"])),
+  "mgallery_php"     => array(MG_STR, "mgallery.php"),
+  "format_exts"      => array(MG_STR, "\.jpg|\.png|\.gif|\.jpeg"),
+  "captions_file"    => array(MG_STR, "captions.txt"),
+  "header_file"      => array(MG_STR, "header.txt"),
+  "info_file"        => array(MG_STR, "gallery.info"),
+  "cache_file"       => array(MG_STR, ".mgallery.cache"),
+
+  "clean_urls"       => array(MG_BOOL, FALSE),
+
+  "title_prefix"     => array(MG_STR, ""),
+  "title_sep"        => array(MG_STR, " - "),
+  "page_info"        => array(MG_STR, "<b>MGallery ".$mgProgVersion."</b> &copy; Copyright ".$mgProgCopyright),
+  "css_select"       => array(MG_BOOL, FALSE),
+  "css"              => array(MG_STR, NULL),
+  "album_icon"       => array(MG_STR, "album_sm.png"),
+
+  "image_navigation" => array(MG_INT, GNAV_TOP | GNAV_BOTTOM | GNAV_SIDE_IMG | GNAV_TEXT | GNAV_JAVASCRIPT | GNAV_BREADCRUMBS),
+  "album_navigation" => array(MG_INT, GNAV_BREADCRUMBS),
+
+  "album_row_limit"  => array(MG_INT, 5),
+  "album_page_limit" => array(MG_INT, 100),
+
+  "med_suffix"       => array(MG_STR, ".med"),
+  "tn_path"          => array(MG_STR, "tn/"),
+
+  "tn_width"         => array(MG_INT, 140),  // In pixels, applies as bounding box for w/h
+  "tn_height"        => array(MG_INT, 100),
+  "tn_quality"       => array(MG_INT, 80), // JPEG quality percent
+
+  "med_width"        => array(MG_INT, 800),
+  "med_height"       => array(MG_INT, 600),
+  "med_quality"      => array(MG_INT, 95),
+);
+
+
+function mgDebug($msg)
+{
+//  echo "MGAL[debug]: ".$msg;
+}
+
+
+function mgFatal($msg)
+{
+  die("MGAL[fatal]: ".$msg);
+}
+
+
+function mgError($msg)
+{
+  echo "MGAL[error]: ".$msg;
+  return FALSE;
+}
+
+
+function mgCArg($index, $clip = FALSE)
+{
+  global $argc, $argv;
+  if ($index < $argc)
+  {
+    $str = $argv[$index];
+    return ($clip !== FALSE) ? substr($str, 0, $clip) : $str;
+  }
+  else
+    return FALSE;
+}
+
+
+function mgCArgLC($index, $clip = FALSE)
+{
+  global $argc, $argv;
+  if ($index < $argc)
+  {
+    $str = strtolower($argv[$index]);
+    return ($clip !== FALSE) ? substr($str, 0, $clip) : $str;
+  }
+  else
+    return FALSE;
+}
+
+
+function mgGetSetting($key, $default = NULL)
+{
+  global $mgSettings, $mgDefaults;
+
+  if (!array_key_exists($key, $mgDefaults))
+    mgFatal("Setting '".$key."' does not exist.\n");
+
+  if (array_key_exists($key, $mgSettings))
+    $val = $mgSettings[$key];
+  else
+    $val = $mgDefaults[$key][1];
+
+  if (!isset($val) || $val === NULL)
+  {
+    if ($default !== NULL)
+      $val = $default;
+    else
+      mgFatal("Setting '".$key."' is not set, but is required to be configured.\n");
+  }
+
+  return $val;
+}
+
+
+function mgGetPath($path, $key)
+{
+  $val = mgGetSetting($key);
+  return ($val !== FALSE) ? $path."/".$val : FALSE;
+}
+
+
+function mgReadSettings($filename = "mgallery.cfg")
+{
+  global $mgSettings, $mgDefaults;
+
+  $spaths = array();
+  $spaths[] = getcwd()."/";
+  if (($tmp = getenv("HOME")) !== FALSE && strlen($tmp) > 0)
+  {
+    $spaths[] = $tmp."/.config/mgallery/";
+    $spaths[] = $tmp."/.";
+  }
+  else
+  {
+    $data = posix_getpwuid(posix_getuid());
+    if ($data !== FALSE && isset($data["dir"]))
+    {
+      $tmp = $data["dir"];
+      $spaths[] = $tmp."/.config/mgallery/";
+      $spaths[] = $tmp."/.";
+    }
+  }
+  $spaths[] = dirname(__FILE__)."/";
+
+  foreach (array_unique($spaths) as $path)
+  {
+    $file = $path.$filename;
+    mgDebug("Checking '".$file."' for configuration ..\n");
+    if (file_exists($file) &&
+      ($mgSettings = parse_ini_file($file, FALSE)) !== FALSE)
+    {
+      mgDebug("Found '".$file."' config.\n");
+
+      // Validate settings
+      $ok = TRUE;
+      foreach ($mgSettings as $setting => $val)
+      {
+        if (!array_key_exists($setting, $mgDefaults))
+        {
+          mgError("Setting '".$setting."' does not exist.\n");
+          $ok = FALSE;
+        }
+      }
+      return $ok;
+    }
+  }
+
+  $mgSettings = array();
+  return FALSE;
+}
+
+
+function mgRealPath($path)
+{
+  return realpath($path);
+}
+
+
+function mgCleanPathArray($refs, $start, $argc, $argv)
+{
+  $path = array();
+  $first = TRUE;
+  for ($n = $start; $n < $argc; $n++)
+  {
+    foreach (explode("/", $argv[$n]) as $piece)
+    {
+      switch ($piece)
+      {
+        case ".":
+        case "":
+          if ($first)
+            $path[] = $piece;
+          break;
+
+        case "..":
+          if ($refs && count($path) > 0)
+            array_pop($path);
+          break;
+
+        default:
+          $path[] = $piece;
+          break;
+      }
+      $first = FALSE;
+    }
+  }
+  return $path;
+}
+
+
+function mgCleanPath($refs)
+{
+  return implode("/", mgCleanPathArray($refs, 1, func_num_args(), func_get_args()));
+}
+
+
+function mgGetArr($data, $skeys, $sfmt1 = "%1", $sfmt2 = "", $func = NULL)
+{
+  if (!is_array($skeys))
+    $skeys = array($skeys);
+
+  foreach ($skeys as $skey)
+  if (!array_key_exists($skey, $data))
+    return $sfmt2;
+
+  $str = $sfmt1;
+  for ($i = 1; $i <= sizeof($skeys); $i++)
+  {
+    $val = $data[$skeys[$i - 1]];
+
+    if (is_callable($func))
+      $val = call_user_func($func, $val);
+
+    $str = str_replace("%".$i, $val, $str);
+  }
+
+  return $str;
+}
+
+
+function mgGetVal($skeys, $sfmt1 = "%1", $sfmt2 = "", $func = NULL)
+{
+}
+
+
+function mgGetDValStr($type, $val)
+{
+  switch ($type)
+  {
+    case MG_STR  : return "\"".$val."\"";
+    case MG_BOOL : return $val ? "yes" : "no";
+    case MG_INT  :
+    default      : return (string) $val;
+  }
+}
+
+
+?>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mgallery.php	Wed May 13 07:27:40 2015 +0300
@@ -0,0 +1,408 @@
+<?php
+//
+// Yet Another Image Gallery
+// (C) Copyright 2015 Tecnic Software productions (TNSP)
+//
+require "msitegen.inc.php";
+require "mgallery.inc.php";
+
+
+//
+// Various utility functions
+//
+function mgGetImageURL()
+{
+  global $galImageURL, $galPath;
+  return $galImageURL.$galPath."/".implode("", func_get_args());
+}
+
+
+function mgGetURL($path, $image, $entities = TRUE)
+{
+  global $galBaseURL, $galCleanURLS;
+  $amp = $entities ? "&amp;" : "&";
+
+  if ($galCleanURLS)
+  {
+    return $galBaseURL.$path."/".($image !== FALSE ? $image : "");
+  }
+  else
+  {
+    return
+      $galBaseURL.mgGetSetting("mgallery_php")."?path=".
+      $path.($image !== FALSE ? $amp."image=".$image : "");
+  }
+}
+
+
+function mgGetNaviActive(&$galIndex, $index, $delta, &$res, &$url, $entities)
+{
+  global $galPath;
+  $res = $index + $delta;
+  if ($res >= 0 && $res <= sizeof($galIndex) - 1)
+  {
+    $url = mgGetURL($galPath, $galIndex[$res], $entities);
+    return TRUE;
+  }
+  else
+    return FALSE;
+}
+
+
+function mgGetNaviControlImage(&$galIndex, $index, $class, $url)
+{
+  global $galTNPath;
+
+  $img = "<div class=\"imageCtrl ".$class."\">";
+
+  if ($url !== FALSE)
+  {
+    $img .=
+    "<a href=\"".$url."\"><img src=\"".
+    mgGetImageURL($galTNPath, $galIndex[$index]).
+    "\" alt=\"".$galIndex[$index]."\" /></a>";
+  }
+
+  return $img."</div>\n";
+}
+
+
+function mgGetNaviControlImageBox(&$galIndex, $index, $class, $delta)
+{
+  if (!mgGetNaviActive($galIndex, $index, $delta, $res, $url, TRUE))
+    $url = FALSE;
+
+  return mgGetNaviControlImage($galIndex, $res, $class, $url);
+}
+
+
+function mgGetControl($str, $class, &$galIndex, $index, $delta, $naviFlags)
+{
+  $active = mgGetNaviActive($galIndex, $index, $delta, $res, $url, TRUE);
+  if ($active && ($naviFlags & GNAV_IMG))
+    $img = mgGetNaviControlImage($galIndex, $res, $class, $url);
+  else
+    $img = "";
+
+  if ($naviFlags & GNAV_TEXT)
+    $str = "<span class=\"naviControl ".$class."\">[".($active ? "<a href=\"".$url."\">".$str."</a>" : $str)."]</span>";
+  else
+    $str = "";
+
+  if ($delta < 0)
+    return $img.$str;
+  else
+    return $str.$img;
+}
+
+
+function mgGetNaviControls(&$galIndex, $index, $naviFlags)
+{
+  global $galPath;
+
+  return
+    "<div class=\"naviControls\">".
+    mgGetControl("&lt;&lt;", "prev", $galIndex, $index, -1, $naviFlags).
+    "[<a href=\"".mgGetURL($galPath, FALSE)."\">^^</a>]".
+    mgGetControl("&gt;&gt;", "next", $galIndex, $index,  1, $naviFlags).
+    "</div>\n";
+}
+
+
+function mgPrintTable($class, &$galEntries, &$galIndex, $start, $limit)
+{
+  global $galAlbumIcon, $galPath, $galTNPath;
+
+  $galCount = count($galIndex);
+  if ($start >= $galCount)
+    return $start;
+
+  $end = ($limit === FALSE) ? $galCount : $start + $limit;
+  if ($end > $galCount) $end = $galCount;
+
+  $rowLimit = mgGetSetting("album_row_limit");
+  $n = 0;
+
+  echo "<table class=\"".$class."\">\n";
+  for ($index = $start; $index < $end; $index++)
+  {
+    $filename = &$galIndex[$index];
+    $data = &$galEntries[$filename];
+
+    if ($n == 0) echo " <tr>\n";
+
+    echo
+      "  <td id=\"cd".$data["base"]."\">\n";
+
+    if ($data["type"] == 0)
+    {
+      echo
+      "<div class=\"imageBox\"><a href=\"".mgGetURL($galPath, $filename)."\">".
+      "<img src=\"".mgGetImageURL($galTNPath, $filename)."\" alt=\"".
+      chentities($filename)."\"></a>".
+      "</div>".
+      mgGetArr($data, "caption", "<div class=\"imageCaption\">%1</div>", "", "chentities");
+/*
+      if ($mode == "")
+      {
+      echo
+        "  <select class=\"dropdown\" id=\"dd".$data["base"]."\" name=\"dd".$data["base"].
+        "\" onchange=\"galPhotoDataChanged('".$data["base"]."');\">\n";
+
+      foreach ($picChoices as $name => $value)
+      {
+        echo "   <option value=\"$value\"".($value == $data["id"] ? " selected=\"selected\"" : "").">".chentities($name)."</option>\n";
+      }
+      echo
+        "  </select>\n";
+      }
+*/
+    }
+    else
+    {
+      echo
+      " <a href=\"".mgGetURL(mgCleanPath(TRUE, $galPath, $data["base"]), FALSE)."\">".
+      "<img class=\"albumIcon\" src=\"".$galAlbumIcon."\" alt=\"".chentities($data["caption"])."\" />\n".
+      "<div class=\"albumTitle\">".chentities($data["caption"])."</div></a>\n";
+    }
+
+    echo
+      "  </td>\n";
+
+    if (++$n >= $rowLimit)
+    {
+      echo " </tr>\n";
+      $n = 0;
+    }
+  }
+  if ($n > 0)
+  {
+    while ($n++ < $rowLimit)
+      echo "  <td></td>\n";
+    echo " </tr>\n";
+  }
+  echo "</table>\n";
+  return $index;
+}
+
+
+function mgTimeStr($str)
+{
+  $tmp = date_create_from_format("Y:m:d H:i:s", $str);
+  return date_format($tmp, "d M Y (H:i)");
+}
+
+
+function mgPrintPageInfoFooter()
+{
+  if (($str = mgGetSetting("page_info")) !== FALSE)
+    echo "<div class=\"pageInfoFooter\">".$str."</div>";
+}
+
+
+function mgPrintBreadCrumbs($galData)
+{
+  $res = array();
+  if ($galData["caption"])
+    $res[] = chentities($galData["caption"]);
+
+  $tmp = $galData;
+  while (isset($tmp["parent"]))
+  {
+    $pdata = $tmp["parent"];
+    $res[] = "<a href=\"".mgGetURL($pdata["path"], FALSE)."\">".chentities($pdata["caption"])."</a>";
+    $tmp = $tmp["parent"];
+  }
+
+  if (count($res) > 1)
+  {
+    $res = array_map(function ($a) { return "<span class=\"naviBreadCrumbItem\">".$a."</span>"; }, $res);
+    echo
+      "<div class=\"naviBreadCrumbs\">".
+      implode("<span class=\"naviBreadCrumbSep\"></span>", array_reverse($res)).
+      "</div>\n";
+  }
+}
+
+
+//
+// Get gallery settings
+//
+mgReadSettings();
+
+$pageCSS = mgGetSetting("css");
+$pageCSSSelect = mgGetSetting("css_select");
+$galBasePath = mgGetSetting("base_path");
+$galBaseURL = mgGetSetting("base_url");
+$galImageURL = mgGetSetting("image_url", mgGetSetting("base_url"));
+
+$galAlbumIcon = mgGetSetting("album_icon");
+$galCleanURLS = mgGetSetting("clean_urls");
+$galTNPath = mgGetSetting("tn_path");
+$galMedSuffix = mgGetSetting("med_suffix");
+$galTitlePrefix = mgGetSetting("title_prefix");
+$galTitleSep = mgGetSetting("title_sep");
+
+$galMode = stGetRequestItem("mode", "view", TRUE);
+$galPath = stGetRequestItem("path", ".", TRUE);
+$galPageIndex = intval(stGetRequestItem("index", 0, TRUE));
+$galImage = stGetRequestItem("image", FALSE, TRUE);
+
+if (is_string($galImage))
+  $galImage = basename($galImage);
+
+
+//
+// Attempt to read the data cache file
+//
+$filename = mgGetPath(mgCleanPath(TRUE, $galBasePath, $galPath), "cache_file");
+$filename2 = mgGetPath(mgCleanPath(FALSE, $galBasePath, $galPath), "cache_file");
+if ($filename == $filename2 && file_exists($filename) && ($fp = @fopen($filename, "rb")) !== FALSE)
+{
+  if (flock($fp, LOCK_SH))
+  {
+    require($filename);
+    flock($fp, LOCK_UN);
+  }
+  fclose($fp);
+}
+
+
+// If no data available, show an error page
+if (!isset($galData) || !isset($galEntries) ||
+    !isset($galAlbumsIndex) || !isset($galImagesIndex))
+{
+  cmPrintPageHeader(mgGetVal(array("title_prefix", "title_sep"), "%1%2")."ERROR!");
+
+  echo
+    "<h1>Gallery error</h1>\n".
+    "<p>Gallery path <b>".chentities($galPath)."</b> does not exist or is invalid.</p>\n";
+
+  //echo "<p>".$filename."</p><p>".$filename2."</p>";
+
+  mgPrintPageInfoFooter();
+  cmPrintPageFooter(TRUE);
+  exit;
+}
+
+
+//
+// Print page header, etc.
+//
+if (($index = array_search($galImage, $galImagesIndex)) !== FALSE)
+{
+  //
+  // Single image mode
+  //
+  $naviFlags = mgGetSetting("image_navigation");
+  $data = $galEntries[$galImage];
+
+  $pageTitle = $galTitlePrefix.$galTitleSep.$galData["caption"]." - ".$galImage;
+  cmPrintPageHeader($pageTitle);
+  echo "<h1>".chentities($pageTitle)."</h1>\n";
+
+  if ($naviFlags & GNAV_BREADCRUMBS)
+    mgPrintBreadCrumbs($galData);
+
+  if ($naviFlags & GNAV_TOP)
+    echo mgGetNaviControls($galImagesIndex, $index, $naviFlags);
+
+  echo
+  "<div class=\"imageCBox\">\n".
+  mgGetNaviControlImageBox($galImagesIndex, $index, "prev", -1).
+  "<div class=\"imageBox\">\n".
+  "<a target=\"_blank\" href=\"".$galImageURL.$galPath."/".$galImage."\">".
+  "<img src=\"".mgGetImageURL($galTNPath, $data["base"].$galMedSuffix.$data["ext"])."\" alt=\"".
+  chentities($data["base"].$galMedSuffix.$data["ext"])."\"></a>\n".
+  "</div>\n".
+  mgGetNaviControlImageBox($galImagesIndex, $index, "next", 1).
+  "</div>\n".
+  "<div class=\"imageCaption\">".mgGetArr($data, "caption", "%1", "")."</div>\n";
+
+  $list = array(
+    mgGetArr($data, array("width", "height"), "<span class=\"infoDimensions\"><b>%1</b> x <b>%2</b> px</span>", NULL),
+    mgGetArr($data, "model", "<span class=\"infoModel\"><b>%1</b></span>", NULL),
+    mgGetArr($data, "fnumber", "<span class=\"infoFNumber\"><b>f/%1</b></span>", NULL),
+    mgGetArr($data, "exposure", "<span class=\"infoExposure\"><b>%1</b> sec</span>", NULL, NULL),
+    mgGetArr($data, "iso", "<span class=\"infoISO\">ISO <b>%1</b></span>", NULL),
+  );
+
+  echo
+    "<div class=\"infoBox\">\n".
+    mgGetArr($data, "datetime", "<span class=\"infoDateTime\">%1</span>", "", "mgTimeStr").
+    implode(", ", array_filter($list, function($a) { return $a !== NULL; })).
+    "</div>\n";
+
+  if ($naviFlags & GNAV_BOTTOM)
+    echo mgGetNaviControls($galImagesIndex, $index, $naviFlags);
+
+  // Javascript navigation
+  if ($naviFlags & GNAV_JAVASCRIPT)
+  {
+    $prevActive = mgGetNaviActive($galImagesIndex, $index, -1, $res, $prevURL, FALSE);
+    $nextActive = mgGetNaviActive($galImagesIndex, $index, 1, $res, $nextURL, FALSE);
+    echo
+      "<script type=\"text/javascript\">\n".
+      "var mgalPrevURL = \"".($prevActive ? $prevURL : "")."\";\n".
+      "var mgalNextURL = \"".($nextActive ? $nextURL : "")."\";\n".
+      "\n";
+?>
+function mgalNavigateTo(url)
+{
+    if (url != "")
+        window.location = url;
+}
+
+
+function mgalProcessKeyPress(ev)
+{
+    ev = ev || window.event;
+    var key = ev.keyCode ? ev.keyCode : ev.which;
+    switch (key)
+    {
+        case 37:
+        case 65:
+            // left
+            mgalNavigateTo(mgalPrevURL);
+            break;
+
+        case 39:
+        case 68:
+            // right
+            mgalNavigateTo(mgalNextURL);
+            break;
+    }
+}
+
+document.onkeypress = mgalProcessKeyPress;
+<?
+    echo
+      "</script>\n";
+  }
+}
+else
+{
+  //
+  // Gallery mode
+  //
+  // - needs sub-modes / handling of order shit
+  // - Javascript stuff for picture data updates
+  //
+  $pageTitle = $galTitlePrefix.mgGetArr($galData, "caption", " - %1", "", "chentities");
+  cmPrintPageHeader($pageTitle);
+  echo "<h1>".$pageTitle."</h1>\n";
+
+  $naviFlags = mgGetSetting("album_navigation");
+  if ($naviFlags & GNAV_BREADCRUMBS)
+    mgPrintBreadCrumbs($galData);
+
+  if (isset($galData["header"]) && strlen($galData["header"]) > 0)
+    echo "<div class=\"albumHeaderText\">".$galData["header"]."</div>\n";
+
+  mgPrintTable("albumTable", $galEntries, $galAlbumsIndex, 0, FALSE);
+  mgPrintTable("imageTable", $galEntries, $galImagesIndex, 0, FALSE);
+}
+
+mgPrintPageInfoFooter();
+cmPrintPageFooter(TRUE);
+?>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mgtool.php	Wed May 13 07:27:40 2015 +0300
@@ -0,0 +1,780 @@
+#!/usr/bin/php
+<?php
+//
+// Yet Another Image Gallery
+// Commandline tool for creating / updating gallery
+// (C) Copyright 2015 Tecnic Software productions (TNSP)
+//
+require_once "mgallery.inc.php";
+
+//
+// Array for specifying what will be copied and converted
+// from the image file's EXIF information tag(s).
+//
+$galExifConversions = array(
+  array(MG_STR, "caption"     , "ImageDescription"),
+  array(MG_STR, "copyright"   , "Copyright"),
+  array(MG_STR, "model"       , "Model"),
+  array(MG_INT, "width"       , array("COMPUTED", "Width")),
+  array(MG_INT, "height"      , array("COMPUTED", "Height")),
+  array(MG_DVA, "fnumber"     , "FNumber"),
+  array(MG_DVA, "exposure"    , "ExposureTime"),
+  array(MG_INT, "iso"         , "ISOSpeedRatings"),
+  array(MG_DVA, "focallength" , "FocalLength"),
+  array(MG_STR, "datetime"    , "DateTimeOriginal"),
+  array(MG_STR, "datetime"    , "DateTimeDigitized"),
+  array(MG_INT, "filesize"    , "FileSize"),
+);
+
+define("GCMD_UPDATE"      , 1);
+define("GCMD_RESCAN"      , 2);
+define("GCMD_CLEAN"       , 3);
+
+define("GCLEAN_CACHES"    , 0x01);
+define("GCLEAN_THUMBNAILS", 0x02);
+define("GCLEAN_ALL"       , 0x0f);
+
+
+define("GUPD_MED_IMAGE"   , 0x01);
+define("GUPD_TN_IMAGE"    , 0x02);
+define("GUPD_IMAGES"      , 0x0f);
+define("GUPD_EXIF_INFO"   , 0x10);
+define("GUPD_CAPTION"     , 0x20);
+
+
+//
+// Convert and scale image file function, for generating
+// the intermediate size images and thumbnails. Uses the
+// PHP ImageMagick bindings.
+//
+function mgConvertImage($inFilename, $outFilename, $outDim, $outFormat, $outQuality, $thumb)
+{
+  // Create conversion entity
+  $img = new Imagick($inFilename);
+  if ($img === FALSE)
+    return mgError("ImageMagick could not digest the file '".$inFilename."'.\n");
+
+  if ($outDim !== FALSE)
+  {
+    // Get dimensions, setup background
+    $dim = $img->getImageGeometry();
+    //$img->setImageBackgroundColor(imagick::COLOR_BLACK);
+    $img->setGravity(imagick::GRAVITY_CENTER);
+
+
+    if ($dim["width"] < $dim["height"])
+    {
+      $stmp = $outDim[0];
+      $outDim[0] = $outDim[1];
+      $outDim[1] = $stmp;
+    }
+
+    if ($dim["width"] != $outDim[0] || $dim["height"] != $outDim[1])
+    {
+      $outDim[1] = ($dim["height"] * $outDim[0]) / $dim["width"];
+    }
+
+    // Act based on image size vs. desired size and $thumb mode
+    if ($thumb || $dim["width"] > $outDim[0] || $dim["height"] > $outDim[1])
+    {
+      // Image is larger
+      $img->resizeImage($outDim[0], $outDim[1], Imagick::FILTER_QUADRATIC, 1);
+      $img->setImageExtent($outDim[0], $outDim[1]);
+      $img->normalizeImage();
+      $img->unsharpMaskImage(0, 0.5, 1, 0.05);
+    }
+    if ($dim["width"] < $outDim[0] || $dim["height"] < $outDim[1])
+    {
+      // Image is smaller than requested dimension(s)?
+      $img->resizeImage($outDim[0], $outDim[1], Imagick::FILTER_QUADRATIC, 1);
+      $img->setImageExtent($outDim[0], $outDim[1]);
+      $img->unsharpMaskImage(0, 0.5, 1, 0.05);
+    }
+  }
+
+  $img->setFormat($outFormat);
+  $img->setCompressionQuality($outQuality);
+
+  $img->stripImage();
+  $img->writeImage($outFilename);
+  $img->removeImage();
+  return TRUE;
+}
+
+
+//
+// Converts one value (mainly from EXIF tag information)
+// by doing explicing type casting and special conversions.
+//
+function mgConvertExifData($val, $vtype)
+{
+  switch ($vtype)
+  {
+    case MG_STR: return (string) $val;
+    case MG_INT: return intval($val);
+    case MG_BOOL: return intval($val);
+    case MG_DVA:
+      if (sscanf($val, "%d/%d", $v1, $v2) == 2)
+      {
+        if ($v1 < $v2)
+          return $val;
+        else
+          return sprintf("%1.1f", $v1 / $v2);
+      }
+      else
+        return $val;
+
+    default:
+      return $val;
+  }
+}
+
+
+//
+// Conditionally copies one "field" from an associated array/hash to another.
+// If destination is already SET, nothing will be done. If source does
+// not exist (e.g. one or more of the keys do not exist in source array),
+// a default value will be used, if provided.
+// Source may have multi-depth keys, destination has one key.
+//
+function mgCopyEntryData(&$dst, $src, $vtype, $dkey, $skeys, $default = NULL)
+{
+  // Is destination already set?
+  if (isset($dst[$dkey]))
+    return FALSE;
+
+  // If input key is not array, change it into one
+  if (!is_array($skeys))
+    $skeys = array($skeys);
+
+  // Traverse input array by using consequent keys
+  $tmp = &$src;
+  foreach ($skeys as $skey)
+  {
+    if (!array_key_exists($skey, $tmp))
+    {
+      // Key didn't exist, try for default
+      if ($default !== NULL)
+        $dst[$dkey] = $default;
+      return FALSE;
+    }
+    else
+      $tmp = &$tmp[$skey];
+  }
+
+  // Optionally convert the input value
+  $dst[$dkey] = mgConvertExifData($tmp, $vtype);
+  return TRUE;
+}
+
+
+//
+// Attempt to get gallery album data from various sources.
+//
+function mgGetAlbumData($galBasePath, $galPath)
+{
+  // Check path permissions
+  $galData = array();
+  if (is_readable($galPath))
+  {
+    // First, try to read gallery/album info file
+    $filename = mgGetPath($galPath, "info_file");
+    if ($filename !== FALSE && file_exists($filename))
+    {
+      mgDebug("Reading INFOFILE: ".$filename."\n");
+      if (($galData = parse_ini_file($filename, FALSE)) === FALSE)
+        $galData = array();
+    }
+
+    // Read header file, if any, and we don't have "header" field set yet
+    $filename = mgGetPath($galPath, "header_file");
+    if ($filename !== FALSE && file_exists($filename) &&
+        !isset($galData["header"]))
+    {
+      mgDebug("Reading HEADERFILE: ".$filename."\n");
+      $galData["header"] = file_get_contents($filename);
+    }
+
+    // Check for alternate key/values for album title/caption
+    if (isset($galData["title"]) && !isset($galData["caption"]))
+    {
+      $galData["caption"] = $galData["title"];
+      unset($galData["title"]);
+    }
+  }
+  else
+    $galData["hide"] = TRUE;
+
+  // If caption is not set, use last path component for
+  // a fallback value in case we don't discover proper title
+  // from other sources we can't check here yet.
+  $path = explode("/", $galPath);
+  $galData["fallback_caption"] = ucfirst(str_replace("_", " ", end($path)));
+
+  // Last, store the current gallery path
+  $len = strlen($galBasePath);
+  if ($len < strlen($galPath) && substr($galPath, 0, $len) == $galBasePath)
+    $galData["path"] = substr($galPath, $len);
+  else
+    $galData["path"] = "";
+
+  return $galData;
+}
+
+
+function mgReadCaptionsFile($galBasePath, $galPath)
+{
+  $captions = array();
+  $filename = mgGetPath($galPath, "captions_file");
+  if ($filename === FALSE || ($fp = @fopen($filename, "rb")) === FALSE)
+    return $captions;
+
+  mgDebug("Reading CAPTIONS: ".$filename."\n");
+
+  // Read and parse data
+  while (!feof($fp))
+  {
+    $str = trim(fgets($fp));
+    // Ignore comments and empty lines
+    if ($str != "#" && $str != "")
+    {
+      if (preg_match("/^(#?)\s*(\S+?)\s+(.+)$/", $str, $m))
+        $captions[$m[2]] = array("caption" => $m[3], "hide" => ($m[1] == "#"), "used" => FALSE);
+      else
+      if (preg_match("/^(#?)\s*(\S+?)$/", $str, $m))
+        $captions[$m[2]] = array("hide" => ($m[1] == "#"), "used" => FALSE);
+    }
+  }
+
+  fclose($fp);
+  return $captions;
+}
+
+
+function mgMakeDir($path, $perm)
+{
+  if (!file_exists($path))
+  {
+    if (mkdir($path, $perm, TRUE) === false)
+      return mgError("Could not create directory '".$path."'\n");
+  }
+  return TRUE;
+}
+
+
+function mgYesNoPrompt($msg, $default = FALSE)
+{
+  echo $msg." [".($default ? "Y/n" : "y/N")."]? ";
+  $sprompt = strtolower(trim(fgets(STDIN)));
+
+  if ($default)
+    return ($sprompt == "n");
+  else
+    return ($sprompt == "y");
+}
+
+
+function mgDelete($path, $recurse)
+{
+  global $flagDoDelete;
+  if (is_dir($path))
+  {
+    if (($dirHandle = @opendir($path)) === FALSE)
+      return mgError("Could not read directory '".$path."'.\n");
+
+    while (($dirFile = @readdir($dirHandle)) !== FALSE)
+    {
+      if ($dirFile != "." && $dirFile != "..")
+        mgDelete($path."/".$dirFile, $recurse);
+    }
+    closedir($dirHandle);
+
+    echo " - ".$path." [DIR]\n";
+    if ($flagDoDelete)
+      rmdir($path);
+  }
+  else
+  {
+    if (!$recurse)
+      echo " - ".$path."\n";
+    if ($flagDoDelete)
+      unlink($path);
+  }
+}
+
+
+function mgCheckQuit($now = FALSE)
+{
+  global $flagQuit;
+
+  // Dispatch pending signals
+  pcntl_signal_dispatch();
+
+  // Check result
+  if ($now && $flagQuit)
+    mgFatal("Quitting.\n");
+
+  return $flagQuit;
+}
+
+
+function mgNeedUpdate($entry, $field, $cvalue)
+{
+  if (!array_key_exists($field, $entry))
+    return TRUE;
+  
+  return ($entry[$field] < $cvalue);
+}
+
+
+function mgHandleDirectory($mode, $basepath, $path, $parentData, $parentEntry, $writeMode, $startAt)
+{
+  global $galExifConversions, $galTNPath, $galCleanFlags;
+
+  // Get cache file path
+  if (($cacheFilename = mgGetPath($path, "cache_file")) === FALSE)
+    return mgError("Cache filename / path not set.\n");
+
+  mgCheckQuit(TRUE);
+
+  // Read directory contents
+  $entries = array();
+  if (($dirHandle = @opendir($path)) === FALSE)
+    return mgError("Could not read directory '".$path."'.\n");
+
+  while (($dirFile = @readdir($dirHandle)) !== FALSE)
+  {
+    $realFile = $path."/".$dirFile;
+    if (is_dir($realFile))
+    {
+      if ($dirFile[0] != "." && $dirFile != $galTNPath)
+        $entries[$dirFile] = array("type" => 1, "base" => $dirFile, "ext" => "", "mtime" => filemtime($realFile));
+    }
+    else
+    if (preg_match("/^(\S+)(".mgGetSetting("format_exts").")$/i", $dirFile, $dirMatch))
+      $entries[$dirFile] = array("type" => 0, "base" => $dirMatch[1], "ext" => $dirMatch[2], "mtime" => filemtime($realFile), "hide" => false);
+  }
+  closedir($dirHandle);
+
+  mgCheckQuit();
+
+  // Cleanup mode
+  if ($mode == GCMD_CLEAN)
+  {
+    $gallery = array();
+
+    if ($writeMode)
+    {
+      if ($galCleanFlags & GCLEAN_CACHES)
+        mgDelete($cacheFilename, FALSE);
+
+      if ($galCleanFlags & GCLEAN_THUMBNAILS)
+        mgDelete($path."/".$galTNPath, TRUE);
+    }
+  }
+  else
+  // Update modes
+  if ($mode == GCMD_UPDATE || $mode == GCMD_RESCAN)
+  {
+    // Load current cache file, if it exists
+    $galEntries = array();
+    $cacheTime = -1;
+    if ($mode == GCMD_UPDATE && file_exists($cacheFilename))
+    {
+      $cacheTime = filemtime($cacheFilename);
+      @include $cacheFilename;
+    }
+
+    // Read caption data
+    $captions = mgReadCaptionsFile($basepath, $path);
+    $gallery = mgGetAlbumData($basepath, $path);
+    if ($parentData !== NULL && $parentEntry !== NULL)
+    {
+      $gallery["parent"] = $parentData;
+      mgCopyEntryData($gallery, $parentEntry, MG_STR, "caption", "caption");
+    }
+
+    // Start actual processing
+    $nentries = count($entries);
+    $nentry = 0;
+    echo $path." .. ";
+    foreach ($entries as $ename => &$edata)
+    {
+      printf("\r%s (%1.1f%%) ..", $path, ($nentry * 100.0) / $nentries);
+
+      $nentry++;
+      $efilename = $path."/".$ename;
+
+      if (array_key_exists($ename, $galEntries))
+        $galEntry = &$galEntries[$ename];
+      else
+        $galEntry = array();
+
+      mgCheckQuit(FALSE);
+
+      // Update with captions file data, if any
+      if (array_key_exists($ename, $captions))
+      {
+        foreach ($captions[$ename] as $ckey => $cval)
+          $edata[$ckey] = $cval;
+      }
+
+      // Handle entry based on type
+      if ($edata["type"] == 0)
+      {
+        $updFlags = 0;
+        $tnPath = $path."/".$galTNPath;
+        $medFilename = $tnPath."/".$edata["base"].mgGetSetting("med_suffix").$edata["ext"];
+        $tnFilename = $tnPath."/".$ename;
+        $capFilename = $path."/".$edata["base"].".txt";
+
+        // Check what we need to update ..
+        if (!file_exists($medFilename) || filemtime($medFilename) < $edata["mtime"])
+          $updFlags |= GUPD_MED_IMAGE;
+
+        if (!file_exists($tnFilename) || filemtime($tnFilename) < $edata["mtime"])
+          $updFlags |= GUPD_TN_IMAGE;
+
+        if (mgNeedUpdate($galEntry, "mtime", $edata["mtime"]))
+          $updFlags |= GUPD_EXIF_INFO;
+
+        if (file_exists($capFilename) &&
+          mgNeedUpdate($galEntry, "mtime", filemtime($capFilename)))
+          $updFlags |= GUPD_CAPTION;
+
+        // Check for EXIF info
+        if (($updFlags & GUPD_EXIF_INFO) &&
+            ($exif = @exif_read_data($efilename)) !== FALSE)
+        {
+          echo "%";
+          foreach ($galExifConversions as $conv)
+            mgCopyEntryData($edata, $exif, $conv[0], $conv[1], $conv[2]);
+        }
+        else
+        {
+          // Copy old data that is not yet in new
+          echo "*";
+          foreach ($galEntry as $okey => $odata)
+          {
+            if (!array_key_exists($okey, $edata))
+              $edata[$okey] = $odata;
+          }
+        }
+
+        // Generate thumbnails, etc.
+        if ($updFlags & GUPD_IMAGES)
+        {
+          mgMakeDir($tnPath, 0755);
+
+          if ($updFlags & GUPD_MED_IMAGE)
+          {
+            echo "1";
+            mgConvertImage($efilename, $medFilename,
+              array(mgGetSetting("med_width"), mgGetSetting("med_height")),
+              "JPEG", mgGetSetting("med_quality"), TRUE);
+          }
+
+          if ($updFlags & GUPD_TN_IMAGE)
+          {
+            echo "2";
+            mgConvertImage($efilename, $tnFilename,
+              array(mgGetSetting("tn_width"), mgGetSetting("tn_height")),
+              "JPEG", mgGetSetting("tn_quality"), TRUE);
+          }
+        }
+
+        // Check for .txt caption file
+        if ($updFlags & GUPD_CAPTION)
+        {
+          echo "?";
+          if (($tmpData = @file_get_contents($capFilename)) !== FALSE)
+            $edata["caption"] = $tmpData;
+        }
+      }
+      else
+      if ($edata["type"] == 1)
+      {
+        $tmp = mgGetAlbumData($basepath, $efilename);
+        mgCopyEntryData($edata, $tmp, MG_STR, "caption", "caption");
+        mgCopyEntryData($edata, $tmp, MG_STR, "caption", "fallback_caption");
+        mgCopyEntryData($edata, $tmp, MG_STR, "caption", "title");
+        mgCopyEntryData($edata, $tmp, MG_BOOL, "hide", "hide", FALSE);
+      }
+    }
+
+    echo "\r".$path." ..... DONE\n";
+
+    mgCheckQuit(TRUE);
+
+    // Store gallery cache for this directory
+    if ($writeMode)
+    {
+      $images = array();
+      $albums = array();
+      foreach ($entries as $ename => &$edata)
+      {
+        if ($edata["hide"])
+          continue;
+
+        unset($edata["hide"]);
+        if ($edata["type"] == 0)
+          $images[$ename] = &$edata;
+        else
+          $albums[$ename] = &$edata;
+      }
+      ksort($images);
+      ksort($albums);
+
+      $str =
+        "<?\n".
+        "\$galData = ".var_export($gallery, TRUE).";\n".
+        "\$galAlbumsIndex = ".var_export(array_keys($albums), TRUE).";\n".
+        "\$galImagesIndex = ".var_export(array_keys($images), TRUE).";\n".
+        "\$galEntries = ".var_export($entries, TRUE).";\n".
+        "?>";
+
+      if (@file_put_contents($cacheFilename, $str, LOCK_EX) === FALSE)
+        return mgError("Error writing '".$cacheFilename."'\n");
+    }
+  }
+  else
+    mgFatal("Invalid work mode '".$mode."'.\n");
+
+  mgCheckQuit(TRUE);
+
+  // Recurse to subdirectories
+  foreach ($entries as $ename => $edata)
+  if ($edata["type"] == 1)
+  {
+    $epath = $path."/".$ename."/";
+    $newWriteMode = ($writeMode === FALSE && $epath == $startAt) || $writeMode;
+
+    if (!mgHandleDirectory($mode, $basepath, $epath, $gallery, $edata, $newWriteMode, $startAt))
+      return FALSE;
+  }
+
+  mgCheckQuit(TRUE);
+
+  return TRUE;
+}
+
+
+function mgSigHandler($signo)
+{
+  global $flagQuit;
+  switch ($signo)
+  {
+    case SIGTERM:
+      mgFatal("Received SIGTERM.\n");
+      break;
+
+    case SIGQUIT:
+    case SIGINT:
+      $flagQuit = TRUE;
+      break;
+
+  }
+}
+
+
+function mgProcessGalleries($cmd, $path)
+{
+  global $galTNPath;
+
+  // Fetch the settings we need
+  if (mgReadSettings() === FALSE)
+    die("MGallery not configured.\n");
+
+  // Check validity of some settings
+  $galPath = mgGetSetting("base_path");
+  $galTNPath = mgCleanPath(TRUE, mgGetSetting("tn_path"));
+  if ($galTNPath != mgGetSetting("tn_path"))
+    mgError("Invalid tn_path, using '".$galTNPath."'.\n");
+
+  if (strpos($galTNPath, "/") !== FALSE || $galTNPath == "")
+    mgFatal("Invalid tn_path '".$galTNPath."'.\n");
+
+  $parentData = $parentEntry = NULL;
+  $writeMode = TRUE;
+  $startAt = NULL;
+
+  // Check for path argument
+  if ($path !== FALSE)
+  {
+    // Check the given path, needs to be "under" the gallery path
+    $cmp = mgCleanPath(TRUE, mgRealPath($galPath))."/";
+    $tmp = mgCleanPath(TRUE, mgRealPath($path))."/";
+    if (substr($tmp, 0, strlen($cmp)) != $cmp)
+      mgFatal("Path '".$path."' ('".$tmp."') does not reside under '".$galPath."' ('".$cmp."')!\n");
+
+    // Check if we need to bootstrap
+    if ($cmp != $tmp)
+    {
+      $bpath = mgCleanPath(TRUE, mgRealPath($tmp."../"));
+
+      if ($cmd != GCMD_CLEAN)
+      {
+        $cacheFile = mgGetPath($bpath, "cache_file");
+        if (!file_exists($cacheFile))
+          mgFatal("Can't start working from '".$path."', parent '".$cacheFile."' does not exist!\n");
+
+        @include($cacheFile);
+        if (!isset($galEntries) || !isset($galData))
+          mgFatal("Cache file '".$cacheFile."' is broken or stale.\n");
+      }
+
+      $writeMode = FALSE;
+      $startAt = $tmp;
+      $path = $bpath;
+
+      echo "Starting: '".$startAt."' inside '".$path."'.\n";
+    }
+    else
+      $path = $tmp;
+  }
+  else
+    $path = $galPath;
+
+  // Start working
+  echo "Gallery path: '".$galPath."', starting at '".$path."' ...\n";
+  mgHandleDirectory($cmd, $galPath, $path, $parentData, $parentEntry, $writeMode, $startAt);
+}
+
+
+function mgShowCopyright()
+{
+  global $mgProgVersion, $mgProgCopyright,
+    $mgProgInfo, $mgProgEmail;
+
+  echo
+  "MGTool ".$mgProgVersion." - MGallery management tool\n".
+  $mgProgInfo." ".$mgProgEmail."\n".
+  "(C) Copyright ".$mgProgCopyright."\n";
+}
+
+
+function mgShowHelp()
+{
+  global $argv;
+  echo
+    "Usage: ".basename($argv[0])." <command> [arguments]\n".
+    "\n".
+    "  --help      - Show this help.\n".
+    "  --version   - Show version information.\n".
+    "\n".
+    "  update [path]\n".
+    "    Update directories under <path> or all gallery dirs.\n".
+    "    Conditionally scans dirs for new or changed images and\n".
+    "    updates cache files as needed.\n".
+    "\n".
+    "  rescan [path]\n".
+    "    Like 'update', but forces all cache files to be regenerated\n".
+    "    and EXIF/caption/etc information to be re-scanned even\n".
+    "    if the file timestamps do not justify re-scanning.\n".
+    "\n".
+    "  clean <all|caches|thumbnails> [path]\n".
+    "    Delete all generated files or selectively cache files or\n".
+    "    everything inside thumbnail directories.\n".
+    "\n".
+    "  config\n".
+    "    Display configuration values.\n".
+    "\n";
+}
+
+
+//
+// Main code starts
+//
+if (php_sapi_name() != "cli" || !empty($_SERVER["REMOTE_ADDR"]))
+{
+  header("Status: 404 Not Found");
+  die();
+}
+
+pcntl_signal(SIGTERM, "mgSigHandler");
+pcntl_signal(SIGHUP,  "mgSigHandler");
+pcntl_signal(SIGQUIT, "mgSigHandler");
+pcntl_signal(SIGINT,  "mgSigHandler");
+
+
+$cmd = mgCArgLC(1);
+switch ($cmd)
+{
+  case "--version":
+  case "version":
+  case "ver":
+    mgShowCopyright();
+    break;
+
+  case FALSE:
+    // No arguments
+    mgError("Nothing to do. Showing help:\n");
+
+  case "--help":
+  case "help":
+    if ($cmd !== FALSE)
+      mgShowCopyright();
+    mgShowHelp();
+    break;
+
+  case "update": case "up": case "upd": case "upda":
+    mgProcessGalleries(GCMD_UPDATE, mgCArg(2));
+    break;
+
+  case "rescan": case "re": case "res":
+    mgProcessGalleries(GCMD_RESCAN, mgCArg(2));
+    break;
+
+  case "clean": case "cl": case "cle":
+    $cmode = mgCArgLC(2, 2);
+    switch ($cmode)
+    {
+      case "al": $galCleanFlags = GCLEAN_ALL; break;
+      case "ca": $galCleanFlags = GCLEAN_CACHES; break;
+      case "th": $galCleanFlags = GCLEAN_THUMBNAILS; break;
+      case FALSE:
+        mgFatal("Cleaning requires a mode argument.\n");
+
+      default:
+        mgFatal("Invalid clean mode '".mgCArg(2)."'.\n");
+    }
+    $flagDoDelete = FALSE;
+    mgProcessGalleries(GCMD_CLEAN, mgCArg(3));
+    echo "--\n";
+    if (mgYesNoPrompt("Really delete the above files and directories?"))
+    {
+      echo "OKAY.\n";
+    }
+    break;
+
+  case "config":
+    if (mgReadSettings() === FALSE)
+      die("MGallery not configured.\n");
+
+    foreach ($mgDefaults as $key => $dval)
+    {
+      $sval = mgGetSetting($key);
+      printf("%-20s = %s%s\n",
+        $key,
+        mgGetDValStr($dval[0], $sval),
+        ($dval[1] !== NULL && $sval !== $dval[1]) ? " (default: ".mgGetDValStr($dval[0], $dval[1]).")" : "");
+    }
+    break;
+
+  case "dump":
+    if (mgReadSettings() === FALSE)
+      die("MGallery not configured.\n");
+
+    foreach ($mgDefaults as $key => $dval)
+    {
+      $sval = mgGetSetting($key);
+      printf("%-20s = %s\n", $key, mgGetDValStr($dval[0], $sval));
+    }
+    break;
+
+  default:
+    mgError("Unknown option/command '".$cmd."'.\n");
+    break;
+}
+
+?>
\ No newline at end of file