<?php
// $Id: album.inc.php 1006 2009-01-22 22:12:19Z bb $
// ---- Apache configuration ----
//
// The following RewriteRules are necessary in the album directory's
// .htaccess file:
//
// RewriteEngine On
// RewriteRule ^-((image)|(thumb))/(.+)/([^/]+)$ ?album=$4&$1=$5 [L]
// RewriteRule ^-(.+)$ ?album=$1
// ---- Constants ----
$ALBUM_ROOT = '/album';
$IMAGE_EXT = array('.png', '.jpg', '.gif'); // Image extensions
$OTHER_EXT = array( // Other media extensions
'.mp3', '.mpg', '.mov', '.avi',
'.pdf', '.phps', '.html', '.txt',
'.kml', '.kmz', '.gpx',
'.svg',
'.gz', '.zip', '.bz2'
);
// ---- Utility functions ----
// Force a new extension on a file name
function force_fileext($file, $newext) {
$basename = ereg_replace('\\.[^.]*$', '', $file);
return $basename . '.' . $newext;
}
// Remove parent directory references from a file name
function sanitize($file) {
$file = trim($file, '/');
return str_replace('../', '', $file);
}
// Check if a file matches any extension from an array
function match_fileext($file, &$exts) {
foreach ($exts as $ext)
if (eregi("\\$ext\$", $file))
return true;
return false;
}
// Check if a string consists of only printable ASCII characters
function is_ascii($s) {
return is_string($s) && !preg_match('/[^\x09\x0a\x0d -~]/', $s);
}
// Sort strings with dates in them before strings without dates and
// in descending order
function date_sort($a, $b) {
$a_date = preg_match('(20\d\d(-\d\d(-\d\d)?)?)', $a, $a_match);
$b_date = preg_match('(20\d\d(-\d\d(-\d\d)?)?)', $b, $b_match);
if ($a_date != 0 && $b_date != 0)
// Dates in both strings; compare reverse dates
// If the dates are equal: compare complete strings
return (($cmp = strcmp($b_match[0], $a_match[0])) == 0) ? strcmp($a, $b) : $cmp;
elseif ($a_date == 0 && $b_date == 0)
// No dates; compare strings
return strcmp($a, $b);
else
// Sort the dated string before the undated one
return ($a_date == 0) ? 1 : -1;
}
// url-encode a string, but leave slashes alone
function urlencode2($url) {
$url = urlencode($url);
$url = str_replace('%2F', '/', $url);
return $url;
}
// Return the last album component
function last_album($album) {
$parts = explode('/', $album);
$last = $parts[sizeof($parts) - 1];
if (preg_match('/(.*) 20\d\d(-\d\d(-\d\d)?)?/', $last, $match))
// remove the date
$last = $match[1];
return $last;
}
// Return a link to an image, either as an external or
// an EXIF thumbnail, or as the file name if neither is available
function thumbnail($album, $image, &$alt) {
global $ALBUM_ROOT;
$alt = "Vorschau auf $image in " . last_album($album);
if (is_readable("$album/tn_$image"))
return "$ALBUM_ROOT/$album/tn_$image";
elseif (($thumb = @exif_thumbnail("$album/$image", $x, $y, $type)) !== false)
return sprintf("$ALBUM_ROOT/-thumb/%s/%s", $album, $image);
else
return "$ALBUM_ROOT/.nothumb.jpg";
}
// Return a thumbnail img tag
function image_thumb($album, $image) {
$alt = '';
$thumb = thumbnail($album, $image, $alt);
return thumb_img($thumb, $alt);
}
// Return a linked thumbnail image
function image_thumb_link($album, $image, $class="") {
$img = image_thumb($album, $image);
return sprintf("<div class=\"float\">%s</div>",
image_link($img, $album, $image, $class)
);
}
// Return a linked thumbnail image with text
function image_thumb_link_text($text, $album, $image, $size, $class="") {
$img = image_thumb($album, $image);
return sprintf("<div class=\"float\">%s\n<p>%s%s</p></div>",
image_link($img, $album, $image, $class), image_text_link($text, $album, $image),
$size ? ", $size" : ""
);
}
// Return a text link to an image
function image_text_link($text, $album, $image, $attrs="") {
return image_link($text, $album, $image, $attrs);
}
// Return a link to an image
function image_link($text, $album, $image, $attrs="") {
global $ALBUM_ROOT;
return sprintf("<a%s href=\"$ALBUM_ROOT/-image/%s/%s\">%s</a>",
$attrs ? " $attrs" : "", urlencode2($album), urlencode2($image), $text
);
}
// Return a link to a other file, either as a preview called
// tn_<file>.jpg, or as the file name if no preview is available
function other_thumb_link_text($text, $album, $other, $size, $class="") {
$alt = "Vorschau auf $other in " . last_album($album);
$thumb = "$album/tn_" . force_fileext($other, 'jpg');
if (is_readable($thumb))
$img = other_link(thumb_img($thumb, $alt), $album, $other, $class);
else
$img = '';
return sprintf("<div class=\"float\">%s\n<p>%s%s</p></div>",
$img, other_text_link($other, $album, $other), $size ? ", $size" : ""
);
}
// Return a text link to an image
function other_text_link($text, $album, $image, $class="") {
return other_link($text, $album, $image, $class);
}
// Return a link to a "other" file
function other_link($text, $album, $file, $class="") {
global $ALBUM_ROOT;
return sprintf("<a%s href=\"$ALBUM_ROOT/%s/%s\">%s</a>",
$class ? " class=\"$class\"" : "", $album, $file, $text
);
}
// Return an img tag for a thumbnail image
function thumb_img($file, $alt="", $x=0, $y=0) {
//$file = urlencode2($file);
$size = ($x && $y) ? " width=\"$x\" height=\"$y\"": "";
return "<img class=\"imageThumb\" src=\"$file\"$size alt=\"$alt\" title=\"$alt\" />";
}
// Return an album link
function album_link($text, $album, $attrs="") {
global $ALBUM_ROOT;
return sprintf("<a%s href=\"$ALBUM_ROOT/-%s\">%s</a>",
$attrs ? " $attrs" : "", urlencode2($album), $text
);
}
// Provide a trail of all intermediate path elements as links
// to the respective subalbum
function breadcrumbs($path) {
global $ALBUM_ROOT, $NAV_SEP_HIER;
$parts = explode('/', $path);
$submenu = new Menu($NAV_SEP_HIER);
$submenu->addItem(new MenuItem('contents', "Album", "$ALBUM_ROOT/"));
foreach ($parts as $i => $part)
if ($part != '.') {
if ($i < sizeof($parts) - 1) { // Don't link to the leaf node
$trail = ($i == 0) ? "$part" : "$trail/$part";
$rel = ($i == sizeof($parts) - 2) ? 'index' : '';
$submenu->addItem(new MenuItem($rel, $part, $ALBUM_ROOT . '/-' . urlencode2($trail)));
}
else
$submenu->addItem(new MenuItem('', $part, ''));
}
return $submenu;
}
// Return all images and subalbums in an album
function image_dir($album, &$subalbums, &$images, &$others) {
global $IMAGE_EXT, $OTHER_EXT;
if (($dh = @opendir($album)) !== false) {
while (($file = readdir($dh)) !== false) {
if (substr($file, 0, 1) == '.' || substr($file, 0, 3) == 'tn_')
continue; // Ignore dot-files and thumbnails
$path = "$album/$file";
if (!is_readable($path))
continue; // Ignore unreadable entries
if (is_dir($path))
$subalbums[] = $file; // Add directories as subalbums
elseif (match_fileext($file, $IMAGE_EXT))
$images[] = $file; // Add picture extensions as images
elseif (match_fileext($file, $OTHER_EXT))
$others[] = $file; // Add media extensions as others
}
closedir($dh);
}
usort($subalbums, 'date_sort');
sort($images);
sort($others);
}
// Return a human-readable file size
function filesize_human($size) {
if ($size >= 10e6) {
$size = (int)($size / 1e6);
return "$size MB";
}
elseif ($size >= 10e3) {
$size = (int)($size / 1e3);
return "$size kB";
}
else
return "$size Bytes";
}
// ---- Image functions ----
// Read an info file. Info files are structured like a RFC822 mail
// message; that is, a header of "Name: value" lines, followed by
// an empty line, followed by arbitrary text. The name of a value is
// always converted to lower case.
//
// The currently recognized header fields are:
// - Title: The image's title, most often a short description
// - Subtitle: A subtitle, tagline, etc.
// - Alt: An alternative text displayed when the user has turned off
// images in his browser
// - Descr: The text following the header lines. You can use all
// available HTML formatting in this section; if will be output
// inside a <div> tag with class "imageDescr" or "albumDescr".
// - Width: The image width
// - Height: The image height
// - EXIF: Setting this to any non-empty string outputs EXIF information
//
// The header lines and text are passed back to the calling function in the
// $info associative array.
//
// The image width and height are read from the file itself and also
// passed back as if they had been specified as "width" and "height"
// header fields. It is in fact possible to override an image's
// width and/or height by specifying those two fields in the info file.
function read_info($infofile, &$info) {
// Read the info file
$in_header = 1;
if (is_file($infofile) && is_readable($infofile))
foreach (file($infofile) as $line)
if ($in_header == 1)
if (!trim($line))
$in_header = 0;
elseif ($line[0] == '<') {
$in_header = 0;
$info['descr'] .= $line;
}
else {
$parts = explode(':', $line, 2);
$info[strtolower(trim($parts[0]))] = trim($parts[1]);
}
else
$info['descr'] .= $line;
}
function image_info($image, &$info) {
if (!is_file($image) || !is_readable($image))
return;
// Determine the image size
$imagesize = getimagesize($image);
$info['width'] = $imagesize[0];
$info['height'] = $imagesize[1];
$info['descr'] = '';
// Read the associated info file
read_info(force_fileext($image, 'info'), $info);
// Set the combined size attribute
$info['size'] = "width=\"{$info['width']}\" height=\"{$info['height']}\"";
}
require_once 'php/exifgps.inc.php';
// Display an image's raw EXIF info
function image_exif_raw($file) {
if (($exif = @exif_read_data($file, 0, true)) !== false) {
echo "\n<h3 class=\"imageEXIFHeader\" id=\"exif\">EXIF-Informationen</h3>\n";
foreach ($exif as $key => $section) {
echo "<p class=\"imageEXIFSection\">";
foreach ($section as $name => $val)
if (is_ascii($val))
echo "$key.$name: $val<br />\n";
else if (is_array($val)) {
echo "$key.$name: ( ";
foreach ($val as $i => $v)
echo is_ascii($v) ? $v : '?', ' ';
echo ")<br />\n";
}
//else
// echo "(not displayable)"; //print_r($val);
echo "</p>\n";
}
echo "<div class=\"clear spacer\"></div>\n\n";
}
}
// Display an image's selected EXIF info
function image_exif_cooked($file) {
global $ALBUM_ROOT;
if (
($exif = @exif_read_data($file, 0, true)) !== false &&
($exif['IFD0'] || $exif['EXIF'])
) {
echo "<h3 class=\"imageEXIFHeader\">Bildinformationen</h3>\n<p>\n";
if ($exif['IFD0']['Make'])
echo "Kamera: {$exif['IFD0']['Make']} {$exif['IFD0']['Model']}<br />\n";
if ($exif['EXIF']['DateTimeOriginal'])
echo "Datum: {$exif['EXIF']['DateTimeOriginal']}<br />\n";
if ($exif['FILE']['FileSize'])
echo "Grösse: ", filesize_human($exif['FILE']['FileSize']), "<br />\n";
if ($exif['COMPUTED']['ApertureFNumber'])
echo "Blende: {$exif['COMPUTED']['ApertureFNumber']}<br />\n";
if ($exif['EXIF']['ExposureTime']) {
$t = $exif['EXIF']['ExposureTime'];
eval("\$n = $t;");
if ($n > 1)
$t = $n;
elseif ($n > 0)
$t = '1/' . intval(1 / $n);
echo "Verschlusszeit: $t s<br />\n";
}
if ($exif['EXIF']['FocalLengthIn35mmFilm'])
echo "Brennweite: {$exif['EXIF']['FocalLengthIn35mmFilm']} mm<br />\n";
else if ($exif['EXIF']['FocalLength']) {
$t = $exif['EXIF']['FocalLength'];
eval("\$n = $t;");
echo "Brennweite: {$n} mm<br />\n";
}
if ($exif['EXIF']['ExposureBiasValue']) {
$t = $exif['EXIF']['ExposureBiasValue'];
eval("\$n = $t;");
if ($n != 0)
echo "Belichtungskorrektur: $n<br />\n";
}
if (isset($exif['GPS'])) {
echo "Ort: ", exif_to_html($exif, true),
" (<a href=\"/php/exifmap.php?image=$ALBUM_ROOT/$file\">Karte…</a>)<br />\n";
echo "<a href=\"{$_SERVER['REQUEST_URI']}&exif=1#exif\">Mehr…</a>\n";
}
else {
echo "<a href=\"{$_SERVER['REQUEST_URI']}&exif=1#exif\">Mehr…</a></p>\n";
echo "<p>Auf Karte <a href=\"/php/exifmap.php?image=$ALBUM_ROOT/$file\">positionieren…</a>\n";
}
echo "</p>\n<div class=\"clear\"></div>\n\n";
}
}
// Return the Prev/Next links of an image
function image_nav(&$images, $album, $image) {
// Search the current image in the album
if (sizeof($images) > 1 && ($key = array_search($image, $images)) !== false) {
// If found, output Previous and Next image links
if ($key > 0)
$prev_link = image_link("Zurück", $album, $images[$key - 1], 'accesskey="p" rel="prev"');
else
$prev_link = image_link("Ende", $album, $images[sizeof($images) - 1], 'accesskey="p"');
if ($key + 1 < sizeof($images))
$next_link = image_link("Weiter", $album, $images[$key + 1], 'accesskey="n" rel="next"');
else
$next_link = image_link("Anfang", $album, $images[0], 'accesskey="n" rel="start"');
return sprintf("%s\n<strong>«</strong> %d von %d <strong>»</strong>\n%s",
$prev_link, $key + 1, sizeof($images), $next_link
);
}
return '';
}
// Display an image
function album_image($file, $info) {
global $ALBUM_ROOT;
if ($info['subtitle'])
echo "<h3 class=\"imageSubtitle\">{$info['subtitle']}</h3>\n";
echo "<div class=\"imageDescr\">
<img class=\"bordered image\" src=\"$ALBUM_ROOT/$file\" {$info['size']} alt=\"{$info['alt']}\" />
{$info['descr']}</div>
<div class=\"clear\"></div>\n\n";
if ($info['exif'])
image_exif_raw($file);
else if (!$info['noexif'])
image_exif_cooked($file);
}
// ---- Album functions ----
// Print a list of image links and sizes
function album_thumbs($album, &$subalbums, &$images, &$others) {
global $IMAGE_EXT, $OTHER_EXT;
// First, display album info
$info = array();
read_info("$album/.info", $info);
if ($info['title'])
echo "\n<h2>{$info['title']}</h2>\n";
if ($info['subtitle'])
echo "\n<h3>{$info['subtitle']}</h3>\n";
if ($info['descr'])
echo "\n<div class=\"albumDescr\">{$info['descr']}</div>\n";
// Next, list the subalbums
if (sizeof($subalbums) > 0) {
echo "\n<h2>Albumauswahl</h2>\n\n<ol>\n";
foreach ($subalbums as $i => $file) {
$link = ($album == '.') ? $file : "$album/$file";
if (album_allowed($link)) {
if (++$i == 10) $i = 0; // assign access keys 1..9, 0 for items 1-10
$attrs = 'class="album"' . (($i <= 9) ? " accesskey=\"$i\"" : '');
echo "<li>" . album_link($file, $link, $attrs) . "</li>\n";
}
}
echo "</ol>\n";
}
// Then list the images as thumbnails
$total = $n = 0;
if (sizeof($images) > 0) {
echo "\n<h2 class=\"clear spacer\">Bilder</h2>\n\n";
foreach ($images as $file) {
$size = filesize("$album/$file");
$total += $size;
$n++;
echo image_thumb_link_text($file, $album, $file, filesize_human($size), "class=\"image\""), "\n";
}
}
// Finally list other media
if (sizeof($others) > 0 && $album != '.') {
echo "\n<h2 class=\"clear spacer\">Andere Dateien</h2>\n\n";
foreach ($others as $file) {
$size = filesize("$album/$file");
$total += $size;
$n++;
echo other_thumb_link_text($file, $album, $file, filesize_human($size), "class=\"image\""), "\n";
}
}
// Display statistics
if (sizeof($subalbums) == 0 && $n == 0)
echo "\n<p>Dieses Album ist noch leer.</p>\n\n";
elseif ($n > 0) {
$total = filesize_human($total);
echo "\n<p class=\"clear spacer\">$n Dateien in $total</p>\n\n";
}
else
echo "\n";
}
// Security check
function album_allowed($album) {
$srv = $_SERVER['REMOTE_ADDR'];
if ($album == 'test' &&
!ereg('192\.168\.11\.', $srv) &&
!ereg('127\.0\.0\.1', $srv) &&
$srv != '213.3.3.203'
)
return false;
return true;
}
// ---- Random images ----
// Return a random album and image name
function random(&$album, &$image) {
// rebuild the image list with:
// find \( -name \*.jpg -o -name \*.png -o -name \*.gif \) -a ! -name tn_\* >imglist.txt
$files = explode("\n", file_get_contents('.imglist.txt'));
$image = $files[array_rand($files)];
$album = substr($image, 2, strrpos($image, '/') - 2);
$image = substr($image, strlen($album) + 3);
}
function random_image(&$album, &$image) {
$album = '.';
$image = '';
random($album, $image);
}
// Return a random image thumbnail
function random_thumb($top_album='.') {
global $ALBUM_ROOT;
$tries = 0;
while (++$tries < 50) {
$album = $top_album;
$image = '';
random($album, $image);
$thumb = thumbnail($album, $image, $alt);
if ($thumb != "$ALBUM_ROOT/.nothumb.jpg")
return image_thumb_link($album, $image, "");
// Restart if the image has no thumbnail
}
return '';
}
?>