<?php // ---- Apache configuration ---- // // The following RewriteRules are necessary in the album's parent directory's // .htaccess file: // // RewriteEngine On // RewriteRule ^album/-(image|thumb)/(.+)/([^/]+)$ album/?album=$2&$1=$3 [B,L] // RewriteRule ^album/-(.+)$ album/?album=$1 [L] // ---- Constants ---- $ALBUM_ROOT = '/album'; $IMAGE_EXT = array('.png', '.jpg', '.jpeg', '.gif'); // Image extensions $OTHER_EXT = array( // Other media extensions '.mp3', '.mpg', '.mp4', '.mov', '.avi', '.pdf', '.phps', '.html', '.txt', '.kml', '.kmz', '.gpx', '.svg', '.gz', '.zip', '.bz2' ); // ---- Utility functions ---- // get a GET parameter and URL-decode it if present function get($var) { return empty($_GET[$var]) ? '' : urldecode($_GET[$var]); } // Force a new extension on a file name function force_fileext($file, $newext) { $basename = preg_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 (preg_match('/' . preg_quote($ext) . '$/i', $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); } // Check if a value can be printed function is_print($s) { return is_numeric($s) || is_ascii($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 ( @exif_thumbnail("$album/$image", $x, $y, $type) !== false || make_thumb($album, $image) ) return "$ALBUM_ROOT/-thumb/$album/$image"; else return "$ALBUM_ROOT/.nothumb.jpg"; } function make_thumb($album, $image) { global $ALBUM_ROOT; $dir = "${_SERVER['DOCUMENT_ROOT']}$ALBUM_ROOT/$album"; // mkthumb is ran as user bb, so no writeable check needed $cmd = "sudo -u bb /home/bb/bin/mkthumb " . escapeshellarg("$dir/$image") . " 2>&1"; exec($cmd, $output, $exit_code); if ($exit_code != 0) { echo "<!-- make_thumb(): $cmd returns $exit_code:\n\n", htmlentities(implode("\n", $output)), "\n-->\n"; return False; } return True; } // 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="") { global $ALBUM_ROOT; $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 = ''; if (preg_match('/\\.km[lz]$/', $other)) { $url = rawurlencode("http://{$_SERVER['SERVER_NAME']}$ALBUM_ROOT/$album/$other"); // Due to a decoding quirk at Google, encoded spaces must be encoded again?!? $url = str_replace('%20', '%2520', $url); $map = " (<a href='http://maps.google.com/maps?q=$url'>Karte</a>)\n"; } else $map = ''; return sprintf("<div class=\"float\">%s\n<p>%s%s%s</p></div>", $img, other_text_link($other, $album, $other), $size ? ", $size" : '', $map ); } // 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; $descr = ''; 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; $descr = $line; } else { $parts = explode(':', $line, 2); $info[strtolower(trim($parts[0]))] = trim($parts[1]); } else $descr .= $line; if (!empty($descr)) $info['descr'] = $descr; } 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_print($val)) echo "$key.$name: $val<br />\n"; else if (is_array($val)) { echo "$key.$name: ( "; foreach ($val as $i => $v) echo is_print($v) ? $v : '?', ' '; echo ")<br />\n"; } else echo "$key.$name: <" . base64_encode($val) . "><br />\n"; 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 && (isset($exif['IFD0']) || isset($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']) { $aperture = str_replace("f", "ƒ", $exif['COMPUTED']['ApertureFNumber']); echo "Blende: {$aperture}<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 (isset($exif['EXIF']['FocalLengthIn35mmFilm'])) echo "Brennweite: {$exif['EXIF']['FocalLengthIn35mmFilm']} mm<br />\n"; else if (isset($exif['EXIF']['FocalLength'])) { $t = $exif['EXIF']['FocalLength']; eval("\$n = $t;"); echo "Brennweite: {$n} mm<br />\n"; } if (isset($exif['EXIF']['ISOSpeedRatings'])) echo "ISO: {$exif['EXIF']['ISOSpeedRatings']}<br />\n"; if (isset($exif['EXIF']['ExposureBiasValue'])) { $t = $exif['EXIF']['ExposureBiasValue']; eval("\$n = $t;"); if ($n != 0) { $sign = $n > 0 ? '+' : ''; echo "Belichtungskorrektur: $sign$n<br />\n"; } } if (isset($exif['GPS']) && isset($exif['GPS']['GPSLatitude']) && isset($exif['GPS']['GPSLongitude'])) { 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 (!empty($info['subtitle'])) echo "<h3 class=\"imageSubtitle\">{$info['subtitle']}</h3>\n"; echo "<div class=\"image\"> <img class=\"bordered image\" src=\"$ALBUM_ROOT/$file\" alt=\"{$info['alt']}\" /> </div>\n"; if (!empty($info['descr'])) echo "<div class=imageDescr>\n{$info['descr']}</div>\n"; echo "<div class=\"clear\"></div>\n\n"; if (!empty($info['exif'])) image_exif_raw($file); else if (empty($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 (!empty($info['title'])) echo "\n<h2>{$info['title']}</h2>\n"; if (!empty($info['subtitle'])) echo "\n<h3>{$info['subtitle']}</h3>\n"; if (!empty($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' && !preg_match('/^192\.168\.11\./', $srv) && !preg_match('/^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 ''; } ?>