# HG changeset patch # User Matti Hamalainen # Date 1431491260 -10800 # Node ID ac688606ec4b1958f3c7047937a8d956b308828c Initial import of code. diff -r 000000000000 -r ac688606ec4b basic.css --- /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; +} diff -r 000000000000 -r ac688606ec4b example.cfg --- /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 = "MGallery v0.1 © 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 diff -r 000000000000 -r ac688606ec4b mgallery.inc.php --- /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 @@ +"; +$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, "MGallery ".$mgProgVersion." © 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 diff -r 000000000000 -r ac688606ec4b mgallery.php --- /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 @@ += 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 = "
"; + + if ($url !== FALSE) + { + $img .= + "\"".$galIndex[$index]."\""; + } + + return $img."
\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 = "[".($active ? "".$str."" : $str)."]"; + else + $str = ""; + + if ($delta < 0) + return $img.$str; + else + return $str.$img; +} + + +function mgGetNaviControls(&$galIndex, $index, $naviFlags) +{ + global $galPath; + + return + "
". + mgGetControl("<<", "prev", $galIndex, $index, -1, $naviFlags). + "[^^]". + mgGetControl(">>", "next", $galIndex, $index, 1, $naviFlags). + "
\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 "\n"; + for ($index = $start; $index < $end; $index++) + { + $filename = &$galIndex[$index]; + $data = &$galEntries[$filename]; + + if ($n == 0) echo " \n"; + + echo + " \n"; + + if (++$n >= $rowLimit) + { + echo " \n"; + $n = 0; + } + } + if ($n > 0) + { + while ($n++ < $rowLimit) + echo " \n"; + echo " \n"; + } + echo "
\n"; + + if ($data["type"] == 0) + { + echo + "
". + "\"".". + "
". + mgGetArr($data, "caption", "
%1
", "", "chentities"); +/* + if ($mode == "") + { + echo + " \n"; + } +*/ + } + else + { + echo + " ". + "\"".chentities($data["caption"])."\"\n". + "
".chentities($data["caption"])."
\n"; + } + + echo + "
\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 "
".$str."
"; +} + + +function mgPrintBreadCrumbs($galData) +{ + $res = array(); + if ($galData["caption"]) + $res[] = chentities($galData["caption"]); + + $tmp = $galData; + while (isset($tmp["parent"])) + { + $pdata = $tmp["parent"]; + $res[] = "".chentities($pdata["caption"]).""; + $tmp = $tmp["parent"]; + } + + if (count($res) > 1) + { + $res = array_map(function ($a) { return "".$a.""; }, $res); + echo + "
". + implode("", array_reverse($res)). + "
\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 + "

Gallery error

\n". + "

Gallery path ".chentities($galPath)." does not exist or is invalid.

\n"; + + //echo "

".$filename."

".$filename2."

"; + + 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 "

".chentities($pageTitle)."

\n"; + + if ($naviFlags & GNAV_BREADCRUMBS) + mgPrintBreadCrumbs($galData); + + if ($naviFlags & GNAV_TOP) + echo mgGetNaviControls($galImagesIndex, $index, $naviFlags); + + echo + "
\n". + mgGetNaviControlImageBox($galImagesIndex, $index, "prev", -1). + "
\n". + "". + "\"".\n". + "
\n". + mgGetNaviControlImageBox($galImagesIndex, $index, "next", 1). + "
\n". + "
".mgGetArr($data, "caption", "%1", "")."
\n"; + + $list = array( + mgGetArr($data, array("width", "height"), "%1 x %2 px", NULL), + mgGetArr($data, "model", "%1", NULL), + mgGetArr($data, "fnumber", "f/%1", NULL), + mgGetArr($data, "exposure", "%1 sec", NULL, NULL), + mgGetArr($data, "iso", "ISO %1", NULL), + ); + + echo + "
\n". + mgGetArr($data, "datetime", "%1", "", "mgTimeStr"). + implode(", ", array_filter($list, function($a) { return $a !== NULL; })). + "
\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 + "\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 "

".$pageTitle."

\n"; + + $naviFlags = mgGetSetting("album_navigation"); + if ($naviFlags & GNAV_BREADCRUMBS) + mgPrintBreadCrumbs($galData); + + if (isset($galData["header"]) && strlen($galData["header"]) > 0) + echo "
".$galData["header"]."
\n"; + + mgPrintTable("albumTable", $galEntries, $galAlbumsIndex, 0, FALSE); + mgPrintTable("imageTable", $galEntries, $galImagesIndex, 0, FALSE); +} + +mgPrintPageInfoFooter(); +cmPrintPageFooter(TRUE); +?> \ No newline at end of file diff -r 000000000000 -r ac688606ec4b mgtool.php --- /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 +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 = + ""; + + 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])." [arguments]\n". + "\n". + " --help - Show this help.\n". + " --version - Show version information.\n". + "\n". + " update [path]\n". + " Update directories under 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 [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