Source of “shared-php.txt”.
981 lines, 30.4 KBytes.   Last modified 1:32 pm, 9th April 2016 PDT.
1 <?php // Emacs settings: -*- mode: Fundamental; tab-width: 4; -*- 2 3 //////////////////////////////////////////////////////////////////////////// 4 // // 5 // Andrew's Album Application: shared-php.txt // 6 // // 7 // Copyright (c) 2004-2014, Andrew Birrell // 8 // // 9 // PHP subroutines common to photos.php and noscript.php // 10 // // 11 //////////////////////////////////////////////////////////////////////////// 12 13 require("settings-php.txt"); // album-specific settings 14 15 define("C_pwdDir", "/home/pachylet/pachypwd"); // for pachyauth 16 17 define("C_mainQuality", 80); // JPEG quality of main image 18 19 define("C_thumbBg", '#808080'); // background matte for thumbnails 20 21 define("C_images", "images"); // raw image root directory 22 define("C_cache", "cache"); // image cache root directory 23 define("C_hashSize", "cachedHash"); // cached hash value of each image 24 define("C_hash", "hashes"); // content-based titles directory 25 define("C_mkmeta", "./mkmeta/mkmeta"); // privileged mkmeta program 26 define("C_meta", "/_meta"); // path to meta-data directory 27 define("C_metaSort", "/sort.txt"); // sort file in C_meta 28 define("C_metaFTitle", "/title.txt"); // folder title file in C_meta 29 define("C_metaThumb", "/thumb.txt"); // folder thumb file in C_meta 30 define("C_metaTitles", "/titles/"); // titles directory in C_meta 31 32 define("C_imagesUrl", C_images); // raw image root URL 33 define("C_cacheUrl", C_cache); // image cache root URL 34 35 define("C_exclusions", '#^(\\.|Icon)|' . 36 '^(Makefile|thumbs\\.db)$|' . 37 '\\.(atn|avi|psd|sh|txt)$|~$#i'); // directory entries to ignore 38 39 define("C_sync", "sync"); // sync to disk, for debugging 40 // define("C_convert", "convert"); // image scaling, etc. 41 define("C_convert", "flock " . C_cache . " convert"); // serialize scaling 42 define("C_composite", "composite"); // image compositing program 43 define("C_identify", "identify"); // program for getting image sizes 44 define("C_useSymlink", true); // false for Windows(TM) 45 46 define("C_mainSize", "" . C_mainW . "x" . C_mainH . ">"); 47 define("C_thumbSize", "" . C_thumbW . "x" . C_thumbH); 48 define("C_decorSpace", 1); // decoration on mini or thumb 49 50 umask(0002); // rwxrwxrx, i.e. group writable 51 52 openlog("Photos", LOG_PID, LOG_DAEMON); 53 54 55 // 56 // Utility functions 57 // 58 59 function writeLog($s) { 60 syslog(LOG_INFO, $s); 61 } 62 63 64 function elapsed($startTime) { 65 // Return the number of milliseconds elapsed since $startTime, rounded 66 // to an integer. 67 // 68 $stopTime = microtime(); 69 // The format is microseconds (as a decimal fraction of a second), 70 // space, seconds (as an integer) 71 list($startUsec, $startSec) = explode(" ", $startTime); 72 list($stopUsec, $stopSec) = explode(" ", $stopTime); 73 // Because usecs are float, the entire formula is computed as a float. 74 // Subtract the seconds first to avoid limited float precision. 75 return round( (($stopSec - $startSec) + ($stopUsec - $startUsec)) * 76 1000 ); 77 } 78 79 function getImageInfo($imagepath) { 80 // Returns an associative array with useful information about the image. 81 // Returns false if the file doesn't exist or isn't readable. 82 // 83 // A non-false result always contains "size". If the file is a 84 // recognizable image, it also contains "type", "width" and "height". 85 // Other entries depend on the presence of suitable image metadata. 86 // 87 if (!file_exists($imagepath) || !is_readable($imagepath)) return false; 88 $res = array(); 89 $data = @exif_read_data($imagepath, null, true); 90 if ($data === false || 91 !isset($data["FILE"]) || !isset($data["COMPUTED"])) { 92 $res["size"] = filesize($imagepath); 93 return $res; 94 } 95 // foreach ($data as $key => $value) writelog("Metadata: $key"); 96 $file = $data["FILE"]; 97 $computed = $data["COMPUTED"]; 98 $res["size"] = $file["FileSize"]; 99 $res["type"] = $file["FileType"]; 100 $res["width"] = $computed["Width"]; 101 $res["height"] = $computed["Height"]; 102 if (isset($data["IFD0"])) { 103 $ifd0 = $data["IFD0"]; 104 // foreach ($ifd0 as $key => $value) writelog("IFD0: $key => $value"); 105 if (isset($ifd0["Model"])) { 106 $res["model"] = $ifd0["Model"]; 107 } 108 } 109 if (isset($data["EXIF"])) { 110 $exif = $data["EXIF"]; 111 // foreach ($exif as $key => $value) writelog("EXIF: $key => $value"); 112 if (isset($exif["DateTimeOriginal"])) { 113 $matches = array(); 114 if (preg_match('#^(\\d\\d\\d\\d):(\\d\\d):(\\d\\d) ' . 115 '(\\d\\d):(\\d\\d):(\\d\\d)#', 116 $exif["DateTimeOriginal"], $matches)) { 117 $udate = gmmktime($matches[4], $matches[5], $matches[6], 118 $matches[2], $matches[3], $matches[1]); 119 if ($udate !== false && $udate != -1) $res["date"] = $udate; 120 } 121 } 122 if (isset($exif["ExposureTime"])) { 123 $res["sec"] = $exif["ExposureTime"]; 124 } 125 if (isset($exif["FNumber"])) { 126 $nd = explode("/", $exif["FNumber"]); 127 if (isset($nd[1])) $res["f"] = round($nd[0] / $nd[1], 1); 128 } 129 if (isset($exif["ISOSpeedRatings"])) { 130 $res["iso"] = $exif["ISOSpeedRatings"]; 131 } 132 if (isset($exif["FocalLength"])) { 133 $nd = explode("/", $exif["FocalLength"]); 134 if (isset($nd[1])) $res["mm"] = round($nd[0] / $nd[1], 1); 135 } 136 } 137 return $res; 138 } 139 140 141 // 142 // Image cache 143 // 144 145 function getCacheFileName($imagepath, $size) { 146 // Given the local file pathname of a raw image, return the local 147 // file pathname for the cached derived file $size. The resulting 148 // path always lies within C_cache, so later file creation based on it 149 // is safe (assuming $imagepath has been cleaned by the caller). 150 // 151 $cacheFile = C_cache . "/" . 152 preg_replace("#^" . preg_quote(C_images) . "/#", "", $imagepath); 153 $suffix = ($size == C_hashSize ? ".txt" : 154 (preg_match('#\\.png$#', $imagepath) ? ".png" : ".jpg")); 155 $cacheFile .= ($size == C_hashSize ? "-hash" : 156 ($size == C_mainSize ? "-main" : 157 ($size == C_thumbSize ? "-thumb" : "-mini"))) . 158 $suffix; 159 return $cacheFile; 160 } 161 162 function ensureCacheDirExists($dirpath) { 163 // Ensure given directory exists, creating if necessary. Assumes that 164 // $dirpath is within C_cache. 165 // 166 if (!file_exists($dirpath)) { 167 if ($dirpath == C_cache) die("Cache directory missing"); 168 ensureCacheDirExists(dirname($dirpath)); 169 writeLog("mkdir $dirpath"); 170 if (!mkdir($dirpath)) die("Directory creation failed"); 171 } 172 } 173 174 function cacheDerivedFile($imagepath, $size) { 175 // Create a file derived from the contents of image $imagepath, and 176 // cache it. The cache is invalidated when the mtime of $imagepath 177 // changes. 178 // 179 // Returns the cache file name 180 // 181 $finalFile = getCacheFileName($imagepath, $size); 182 $rawMtime = filemtime($imagepath); 183 if (file_exists($finalFile) && $rawMtime == filemtime($finalFile)) { 184 // Use the existing cached file 185 } else if ($size == C_hashSize) { 186 // Construct cached hash value 187 ensureCacheDirExists(dirname($finalFile)); 188 if (file_exists($finalFile)) { 189 writeLog("invalidated cache $imagepath"); 190 } 191 $thisHash = hash_file("sha256", $imagepath); 192 if (file_put_contents($finalFile, $thisHash) === false) { 193 writeLog("write cachedHash failed $imagepath"); 194 } else { 195 touch($finalFile, $rawMtime); 196 } 197 } else { 198 // Construct cached scaled image 199 ensureCacheDirExists(dirname($finalFile)); 200 $finalPng = preg_match('#\\.png$#', $finalFile); 201 $sourcePath = ($size == C_thumbSize ? 202 cacheDerivedFile($imagepath, C_mainSize) : $imagepath); 203 $withBorder = (!$finalPng && $size == C_thumbSize); 204 $devRandom = fopen("/dev/urandom", "rb"); 205 $uid = fread($devRandom, 16); 206 fclose($devRandom); 207 $uid = bin2hex($uid); 208 $tempfile = C_cache . "/aaa$uid" . ($finalPng ? ".png" : ".jpg"); 209 $escSize = escapeshellarg($size); 210 exec(C_convert . 211 " -size $escSize" . // scale when reading; runs faster 212 " -quality " . ($size == C_mainSize ? C_mainQuality : 100) . 213 " " . escapeshellarg($sourcePath) . 214 " -auto-orient" . 215 " -resize $escSize" . 216 ($size == C_thumbSize && C_thumbCrop ? 217 "^ -gravity center -crop $escSize+0+0" : "") . 218 " -unsharp 1x2" . 219 ($withBorder ? " -bordercolor white -border 1x1" : "") . 220 " " . escapeshellarg($tempfile) 221 ); 222 if (!file_exists($tempfile)) { 223 // C_convert failed, use placeholder 224 writeLog("failed to scale " . escapeshellarg($sourcePath)); 225 copy("missing.jpg", $tempfile); 226 } 227 touch($tempfile, $rawMtime); 228 if (!@rename($tempfile, $finalFile)) { 229 // Windows won't rename over an existing file, strangely 230 if (file_exists($finalFile)) unlink($finalFile); 231 rename($tempfile, $finalFile); 232 } 233 } 234 return $finalFile; 235 } 236 237 238 // 239 // Operations that use the C_meta directory 240 // 241 242 function ensureMetaExists($thisD) { 243 // If the meta-data directory inside $thisD exists, return true; else 244 // create it if possible and return true; else return an error string. 245 // Also returns an error if $thisD is not a directory. 246 // 247 // Note that the mkmeta program always treats its argument as a path 248 // relative to C_images (configured in its Makefile). 249 // 250 if (!is_dir($thisD)) return "not a directory"; 251 $metapath = $thisD . C_meta; 252 if (!file_exists($metapath) || 253 !is_dir($metapath) || 254 !is_writeable($metapath)) { 255 if (preg_match('#' . C_meta . '(/|$)#', $thisD)) { 256 return "attempting to create recursive " . C_meta; 257 } 258 // Use the setuid program mkmeta to create the metadata directory, 259 // and its "titles" sub-directory. The program will also fix up 260 // ownership, gid, and access rights for existing metadata 261 // directories. 262 if ($thisD == C_images) $thisD = C_images . "/."; 263 $mkmetaArg = preg_replace("#^" . preg_quote(C_images) . "/#", 264 "", $thisD); 265 $output = array(); 266 $status; 267 exec(C_mkmeta . " " . escapeshellarg($mkmetaArg), $output, $status); 268 if ($status != 0) return "\"ensureMetaExists\" failed: " . $status; 269 } 270 return true; 271 } 272 273 function getMetaFileTitleName($barepath) { 274 // Given the local file pathname of a folder or raw image (without 275 // extension), return the pathname for its title file in the relevant 276 // metadata directory. 277 // 278 // For folders, this file contains the folder's title; for images, this 279 // file contains the name of the most recently encountered file in the 280 // contents-based hash directory; that file contains the title. 281 // 282 if (is_dir($barepath)) { 283 ensureMetaExists($barepath); 284 return $barepath . C_meta . C_metaFTitle; 285 } else { 286 $myDir = dirname($barepath); 287 ensureMetaExists($myDir); 288 $myBase = basename($barepath); 289 return $myDir .C_meta . C_metaTitles . $myBase . ".txt"; 290 } 291 } 292 293 function writeMetaFileTitle($titlepath, $title) { 294 // Write title into a specific title file in C_meta. Do nothing if 295 // $titlepath is false. Return true on success, or an error message on 296 // failure. 297 // 298 if ($titlepath !== false) { 299 if (file_put_contents($titlepath, $title) === false) { 300 writeLog("writeMetaFileTitle failed $titlepath"); 301 return "writeMetaFileTitle failed"; 302 } 303 } 304 return true; 305 } 306 307 function getMetaThumbpath($thisD) { 308 // Return the pathname from C_meta for the image or folder whose 309 // thumbnail should be used for this directory, or null if nothing is 310 // stored in C_meta (or it's not readable). 311 // 312 $metapath = $thisD . C_meta . C_metaThumb; 313 if (file_exists($metapath) && 314 ($metathumb = file_get_contents($metapath)) !== false) { 315 return "$thisD/$metathumb"; 316 } 317 return null; 318 } 319 320 function isMetaThumbpath($filepath) { 321 // Return true iff $filepath is the item recorded in C_meta whose 322 // thumbnail should be used as thumbnail for its containing directory. 323 // 324 return (getMetaThumbpath(dirname($filepath)) === $filepath); 325 } 326 327 function writeMetaThumb($filepath, $thumb) { 328 // If "thumb" record this image or folder as being the one who's 329 // thumbnail should be used as its containing folder's thumbnail. 330 // If not "thumb" remove such a record if it exists. Return true on 331 // success and an error message on failure. 332 // 333 $thisD = dirname($filepath); 334 $found = ensureMetaExists($thisD); 335 if ($found !== true) return $found; 336 $metapath = $thisD . C_meta . C_metaThumb; 337 $existing = getMetaThumbpath($thisD); 338 if ($thumb) { 339 if ($existing !== $filepath && 340 file_put_contents($metapath, basename($filepath)) === false) { 341 return "Failed to write metadata thumb file"; 342 } 343 } else { 344 if ($existing === $filepath && unlink($metapath) === false) { 345 return "Filed to delete metadata thumb file"; 346 } 347 } 348 return true; 349 } 350 351 function findThumb($thisD) { 352 // Return the pathname for the image whose thumbnail should be used for 353 // this directory, or null if this directory contains no images. 354 // 355 // If there's an explicit thumb in C_meta, and it exists and has a 356 // thumbnail, use that. Otherwise, use the thumbnail of the first 357 // sub-directory, if it exists. Otherwise, use the first image, if 358 // there is one. Otherwise return null. 359 // 360 $thumbpath = getMetaThumbpath($thisD); 361 if (!is_null($thumbpath) && file_exists($thumbpath)) { 362 if (is_dir($thumbpath)) { 363 $thumbpath = findThumb($thumbpath); 364 if (!is_null($thumbpath)) return $thumbpath; 365 } else { 366 return $thumbpath; 367 } 368 } 369 $entries = getEntries($thisD); 370 foreach ($entries->dirs as $entry) { 371 $thispath = "$thisD/$entry"; 372 $dirThumb = findThumb($thispath); 373 if (!is_null($dirThumb)) return $dirThumb; 374 } 375 foreach ($entries->images as $entry) { 376 return "$thisD/$entry"; 377 } 378 return null; 379 } 380 381 function writeSort($thisD, $sortArg) { 382 // Write the sort-order file for this directory and return true; else 383 // return an error string. 384 // 385 $found = ensureMetaExists($thisD); 386 if ($found !== true) return $found; 387 // $sortArg is a list of image paths; we want just the image filenames 388 $sort = explode("\n", $sortArg); 389 foreach ($sort as $key => $value) $sort[$key] = basename($value); 390 $sortData = implode("\n", $sort); 391 $metapath = $thisD . C_meta . C_metaSort; 392 if (file_put_contents($metapath, $sortData) === false) { 393 return "Failed to write metadata sort file"; 394 } 395 return true; 396 } 397 398 function sortImages($thisD, $images) { 399 // Given the list of images in $thisD, return an array with them 400 // appropriately sorted. Used only from getEntries. 401 // 402 natcasesort($images); 403 $sortpath = $thisD . C_meta . C_metaSort; 404 if (!file_exists($sortpath) || !is_readable($sortpath)) return $images; 405 $sortData = file_get_contents($sortpath); 406 if ($sortData === false) return $images; 407 $sort = explode("\n", $sortData); 408 $res = array(); 409 // Add items from $sort iff they exist in $images, and at most once 410 $imageFlip = array_flip($images); // lets us avoid n * array_search 411 foreach ($sort as $sortee) { 412 if (isset($imageFlip[$sortee])) { 413 $res[] = $sortee; 414 unset($images[$imageFlip[$sortee]]); 415 unset($imageFlip[$sortee]); 416 } 417 } 418 // Add any remaining items from $images 419 foreach ($images as $image) $res[] = $image; 420 return $res; 421 } 422 423 424 // 425 // Titles 426 // 427 428 // The primary calls are writeFileTitle and fileTitle, although admin.php 429 // uses the lower-level functions too. 430 431 // Note: the titles are in UTF-8, and IE fails on illegal UTF-8 in XML 432 433 function stripExtension($path) { 434 // Strip file name extension, if any 435 // 436 return preg_replace('#[.][^./]*$#i', '', $path); 437 } 438 439 $missingHash = false; // hash of the "missing.jpg" file, deferred 440 441 function getHashpath($imagepath) { 442 // Given the local file pathname of an image, return the local file 443 // pathname for the content-based file that should contain its title. 444 // 445 // Returns null if we can't read the cached hash value. 446 // 447 global $missingHash; 448 if (is_dir($imagepath)) die("getHashpath with a directory"); 449 $thisHashFile = cacheDerivedFile($imagepath, C_hashSize); 450 if (($thisHash = file_get_contents($thisHashFile)) === false) { 451 writeLog("getHashpath failed $imagepath"); 452 return null; 453 } 454 return C_hash . "/$thisHash.txt"; 455 } 456 457 function readTitle($titlepath) { 458 // Attempt to read title from a specific file; return false on failure. 459 // 460 // Returns false if $titlepath is null. 461 // 462 $title = false; 463 if (!is_null($titlepath) && file_exists($titlepath)) { 464 $title = file_get_contents($titlepath); 465 } 466 return $title; 467 } 468 469 function writeFileTitle($filepath, $title) { 470 // Write title for given image or directory into the appropriate files. 471 // Return true on success, or an error message on failure. 472 // 473 $barepath = stripExtension($filepath); 474 $titleFileName = getMetaFileTitleName($barepath); 475 if (is_dir($filepath)) { 476 return writeMetaFileTitle($titleFileName, $title); 477 } else { 478 $hashpath = getHashpath($filepath); 479 if (is_null($hashpath)) { 480 writeLog("getHashpath failed $filepath"); 481 return "getHashpath failed"; 482 } else { 483 if (file_put_contents($hashpath, $title) === false) { 484 writeLog("writeFileTitle failed $hashpath"); 485 return "writeFileTitle failed"; 486 } else if (readTitle($titleFileName) !== $hashpath) { 487 return writeMetaFileTitle($titleFileName, $hashpath); 488 } 489 } 490 } 491 return true; 492 } 493 494 function fileTitle($filepath) { 495 // Return string suitable for sub-title line for this image or directory 496 // prefering the contents of the appropriate file, and defaulting to the 497 // image or directory name. 498 // 499 // For folders, the title is in the folder's C_meta directory. 500 // For images, the title is in the C_hash directory, named by 501 // content, or the name of that file is in the containing folder's 502 // C_meta directory. 503 // 504 $barepath = stripExtension($filepath); 505 $titleFileName = getMetaFileTitleName($barepath); 506 507 if (is_dir($filepath)) { 508 $title = readTitle($titleFileName); 509 } else { 510 $hashpath = getHashpath($filepath); 511 $title = readTitle($hashpath); 512 if (!is_null($hashpath)) { 513 if ($title === false) { 514 // Recover using prior hash, stored in titleFileName 515 $prior = readTitle($titleFileName); 516 if ($prior !== false) $title = readTitle($prior); 517 if ($title !== false) writeFileTitle($filepath, $title); 518 } else if (readTitle($titleFileName) !== $hashpath) { 519 // Install new pointer file 520 writeMetaFileTitle($titleFileName, $hashpath); 521 } 522 } 523 } 524 525 // Default to the image file's basename 526 if ($title === false) { 527 $title = ($filepath == C_images ? "Photos" : basename($barepath)); 528 } 529 return $title; 530 } 531 532 533 // 534 // File system subroutines, mostly searching 535 // 536 537 $dirCache = array(); // cache of directory enumerations 538 539 function getEntries($thisD) { 540 // Return an object with ->dirs being this directory's sub-directories, 541 // and ->images being the contained images. Non-image files and 542 // irrelevancies are excluded. Everything is sorted appropriately. 543 // 544 if (isset($dirCache[$thisD])) return $dirCache[$thisD]; 545 $dirEnum = opendir($thisD); 546 $dirs = array(); 547 $images = array(); 548 if ($dirEnum) { 549 while ($entry = readdir($dirEnum)) { 550 if ($entry != "." && 551 $entry != ".." && 552 "/$entry" != C_meta) { 553 if (is_dir("$thisD/$entry")) { 554 $dirs[] = $entry; 555 } else if (!preg_match(C_exclusions, $entry)) { 556 $images[] = $entry; 557 } 558 } 559 } 560 closedir($dirEnum); 561 natcasesort($dirs); 562 $images = sortImages($thisD, $images); 563 } 564 unset($dirEnum); 565 $entries->dirs = $dirs; 566 $entries->images = $images; 567 $dirCache[$thisD] = $entries; 568 return $entries; 569 } 570 571 function cleanPath($filepath) { 572 // Fixup up use of ".", ".." and empty arcs in $filepath 573 // Initial "/" will work like initial empty relative arc 574 // Result is always a non-empty relative path not ending in "/" 575 // 576 $arcs = explode("/", $filepath); 577 for ($i = 0; $i < count($arcs); $i++) { 578 if ($arcs[$i] == "..") { 579 for ($j = $i-1; $j >= 0; $j--) { 580 if ($arcs[$j] != "") { 581 $arcs[$j] = ""; 582 break; 583 } 584 } 585 $arcs[$i] = ""; 586 } else if ($arcs[$i] == ".") { 587 $arcs[$i] = ""; 588 } 589 } 590 $destArcs = array(); 591 $foundOne = false; 592 for ($i = 0; $i < count($arcs); $i++) { 593 if ($arcs[$i] != "") { 594 $destArcs[] = $arcs[$i]; 595 $foundOne = true; 596 } 597 } 598 if (!$foundOne) $destArcs[] = "."; 599 $dest = implode("/", $destArcs); 600 return $dest; 601 } 602 603 function findPath($basename, $thisD = C_images) { 604 // Return full pathname for some file or directory $basename within 605 // $thisD. Returns null if nothing suitable is found. 606 // 607 $entries = getEntries($thisD); 608 foreach ($entries->dirs as $entry) { 609 $thispath = "$thisD/$entry"; 610 if ($entry == $basename) return $thispath; 611 $sub = findPath($basename, $thispath); 612 if (!is_null($sub)) return $sub; 613 } 614 foreach ($entries->images as $entry) { 615 if ($entry == $basename) return "$thisD/$entry"; 616 } 617 return null; 618 } 619 620 // findFirst, findLast, findPrev and findNext allow enumeration of the 621 // entire set of images in the album. The enumeration order is the obvious 622 // tree-walk, depth-first: if a folder has sub-folders as well as directly 623 // contained images, the contents of the sub-folders come first. 624 625 function findFirst($thisD) { 626 // Return first image file's pathname within $dirpath, recursively. 627 // Return null if no such image. 628 // 629 $entries = getEntries($thisD); 630 foreach ($entries->dirs as $entry) { 631 $thispath = "$thisD/$entry"; 632 $dirFirst = findFirst($thispath); 633 if (!is_null($dirFirst)) return $dirFirst; 634 } 635 foreach ($entries->images as $entry) { 636 return "$thisD/$entry"; 637 } 638 return null; 639 } 640 641 function findLast($thisD) { 642 // Return last image file's pathname within $dirpath, recursively. 643 // Return null if no such image. 644 // 645 $entries = getEntries($thisD); 646 foreach (array_reverse($entries->images) as $entry) { 647 return "$thisD/$entry"; 648 } 649 foreach (array_reverse($entries->dirs) as $entry) { 650 $thispath = "$thisD/$entry"; 651 $dirLast = findLast($thispath); 652 if (!is_null($dirLast)) return $dirLast; 653 } 654 return null; 655 } 656 657 function findPrev($imagepath) { 658 // Return previous image file to given $imagepath (which might be a 659 // directory path),within C_images, or null if there's no such item. 660 // 661 // Assumes that $imagepath actually exists and is within C_images. 662 // 663 if ($imagepath == C_images) { 664 return null; 665 } else { 666 $thisD = dirname($imagepath); 667 $entries = getEntries($thisD); 668 $found = false; 669 foreach (array_reverse($entries->images) as $entry) { 670 $thispath = "$thisD/$entry"; 671 if ($thispath == $imagepath) { 672 $found = true; 673 } else if ($found) { 674 return $thispath; 675 } 676 } 677 foreach (array_reverse($entries->dirs) as $entry) { 678 $thispath = "$thisD/$entry"; 679 if ($thispath == $imagepath) { 680 $found = true; 681 } else if ($found) { 682 $dirChild = findLast($thispath); 683 if (!is_null($dirChild)) return $dirChild; 684 } 685 } 686 // Didn't find it: this is first entry in our directory 687 return findPrev($thisD); 688 } 689 } 690 691 function findNext($imagepath) { 692 // Return next image file to given $imagepath (which might be a 693 // directory path), within C_images, or null if there's no such item. 694 // 695 // Assumes that $imagepath actually exists and is within C_images. 696 // 697 if ($imagepath == C_images) { 698 return null; 699 } else { 700 $thisD = dirname($imagepath); 701 $entries = getEntries($thisD); 702 $found = false; 703 foreach ($entries->dirs as $entry) { 704 $thispath = "$thisD/$entry"; 705 if ($thispath == $imagepath) { 706 $found = true; 707 } else if ($found) { 708 $dirChild = findFirst($thispath); 709 if (!is_null($dirChild)) return $dirChild; 710 } 711 } 712 foreach ($entries->images as $entry) { 713 $thispath = "$thisD/$entry"; 714 if ($thispath == $imagepath) { 715 $found = true; 716 } else if ($found) { 717 return $thispath; 718 } 719 } 720 // Didn't find it: this is last entry in our directory 721 return findNext($thisD); 722 } 723 } 724 725 726 // findNextDir, findLastDir and findPrevDir allow enumeration of the entire 727 // set of directories in the album. The enumeration order is the obvious 728 // tree-walk, with a directory being placed before its sub-directories. 729 730 function findNextDir($thisD, $descend) { 731 // Return next directory to $thisD: if it has a non-empty child 732 // and $descend, then the first such child; otherwise next 733 // sibling or ancestor; otherwise null 734 // 735 // Only directories that contain images are eligible. 736 // Assumes that $thisD actually exists and is within C_images. 737 // 738 if ($descend) { 739 $entries = getEntries($thisD); 740 foreach ($entries->dirs as $entry) { 741 $thispath = "$thisD/$entry"; 742 if (!is_null(findFirst($thispath))) return $thispath; 743 } 744 } 745 if ($thisD == C_images) return null; 746 $parent = dirname($thisD); 747 $entries = getEntries($parent); 748 $found = false; 749 foreach ($entries->dirs as $entry) { 750 $thispath = "$parent/$entry"; 751 if ($thispath == $thisD) { 752 $found = true; 753 } else if ($found) { 754 if (!is_null(findFirst($thispath))) return $thispath; 755 } 756 } 757 return findNextDir($parent, false); 758 } 759 760 function findLastDir($thisD) { 761 // Return last non-empty child of $thisD, recursively; otherwise 762 // if $thisD is non-empty, return $thisD; otherwise return null 763 // 764 $entries = getEntries($thisD); 765 $nonEmpty = false; 766 foreach ($entries->images as $entry) { 767 $nonEmpty = true; 768 break; 769 } 770 foreach (array_reverse($entries->dirs) as $entry) { 771 $thispath = "$thisD/$entry"; 772 $lastDir = findLastDir($thispath); 773 if (!is_null($lastDir)) return $lastDir; 774 $nonEmpty = true; 775 } 776 return ($nonEmpty ? $thisD : null); 777 } 778 779 function findPrevDir($thisD) { 780 // Return previous directory to $thisD, including children of 781 // earlier siblings or ancestors; otherwise return null. 782 // 783 // Only directories that contain images are eligible. 784 // Assumes that $thisD actually exists and is within C_images. 785 // 786 if ($thisD == C_images) return null; 787 $parent = dirname($thisD); 788 $entries = getEntries($parent); 789 $found = false; 790 foreach (array_reverse($entries->dirs) as $entry) { 791 $thispath = "$parent/$entry"; 792 if ($thispath == $thisD) { 793 $found = true; 794 } else if ($found) { 795 $lastDir = findLastDir($thispath); 796 if (!is_null($lastDir)) return $lastDir; 797 } 798 } 799 return $parent; 800 } 801 802 803 // 804 // Conversion of internal pathnames to URLs, XML- or HTML-encoded 805 // 806 807 function encodeUrlPath($urlpath) { 808 // URL-encode and XML- (or HTML-) encode the given URL path, which 809 // can be host-relative or directory-relative. I.e., URL-encode 810 // everything except "/" separators, then XML- (or HTML-) encode 811 // everything. 812 // 813 $arcs = explode("/", $urlpath); 814 for ($i = 0; $i < count($arcs); $i++) { 815 $arcs[$i] = rawurlencode($arcs[$i]); 816 } 817 return htmlspecialchars(implode("/", $arcs)); 818 } 819 820 function urlForRawImage($imagepath) { 821 // Given the local file pathname of a raw image, return a URL for it. 822 // 823 // Result is URL-encoded and XML- (or HTML-) encoded. 824 // 825 $rawUrl = preg_replace("#^" . preg_quote(C_images) . "#", 826 C_imagesUrl, $imagepath); 827 return encodeUrlPath($rawUrl); 828 } 829 830 function urlPath($imagepath) { 831 // Given the local file pathname of a folder or raw image, return the 832 // value that we expect to receive back as the "path" CGI argument to 833 // identify that folder or image. Return "" for a null path. 834 // 835 // Result is URL-encoded and XML- (or HTML-) encoded. 836 // 837 // Anything that's consistent with our treatment of $path CGI arg is OK, 838 // subject to the facts that the XML and Javascript use the path "" to 839 // mean "absent", and that the Javascript assumes that child paths have 840 // their parents paths as a prefix. 841 // 842 // In practice, we use a clean path relative to C_images, since this 843 // produces the simplest URL's. Note that RFC 3986 says it's OK to have 844 // "/" in the query part or the fragment part of a URI, and we do so 845 // for legibility of the fragment part used by the JavaScript to 846 // tweak the page's URL. 847 // 848 if (is_null($imagepath)) return ""; 849 if ($imagepath == C_images) return "."; 850 if (strpos($imagepath, C_images . "/") !== 0) die("Bad urlPath call"); 851 $trimmed = preg_replace("#^" . preg_quote(C_images) . "/#", 852 "", $imagepath); 853 return encodeUrlPath($trimmed); 854 } 855 856 857 // 858 // Output functions, producing fragments of XML or HTML 859 // 860 861 $xmlDataPatterns = array('#&#', '#<#', '#>#', '#"#'); 862 $xmlDataEscapes = array('&#38;', '&#60;', '&#62;', '&#34;'); 863 864 function xmlSpecials($str) { 865 // Return string minimally escaped to live inside XML or HTML. 866 // 867 // Note that Safari doesn't handle "&amp;" inside XML attribute values, 868 // so calling "htmlspecials" instead doesn't work. 869 // 870 global $xmlDataPatterns, $xmlDataEscapes; 871 return preg_replace($xmlDataPatterns, $xmlDataEscapes, $str); 872 } 873 874 function putTitleXML($imagepath) { 875 // Put XML title tag for given image or folder 876 // 877 echo " <title>" . xmlSpecials(fileTitle($imagepath)) . "</title>\n"; 878 } 879 880 function putParentXML($imagepath) { 881 // Put the XML "parent" tags for the given image or folder 882 // (recursively), each with its title and path attributes 883 // 884 if ($imagepath != C_images) { 885 $parent = dirname($imagepath); 886 putParentXML($parent); 887 echo " <parent path=\"" . urlPath($parent) . "\">\n"; 888 putTitleXML($parent); 889 echo " </parent>\n"; 890 } 891 } 892 893 function putImageXML($imagepath, $size, $withSizes = true) { 894 // Put XML or HTML attributes width, height, src for the cached image 895 // at the given size. 896 // 897 // We append the cached image's inode-modified time to the cached image 898 // URL, to prevent false caching in the browser. 899 // 900 $cacheFile = cacheDerivedFile($imagepath, $size); 901 $cacheUrl = preg_replace("#^" . preg_quote(C_cache) . "#", 902 C_cacheUrl, $cacheFile); 903 if ($withSizes) { 904 $sizes = getimagesize($cacheFile); 905 ?> 906 width="<?php echo ($sizes ? $sizes[0] : 16) ?>" 907 height="<?php echo ($sizes ? $sizes[1] : 16) ?>" 908 <?php 909 } 910 ?> 911 src="<?php echo encodeUrlPath($cacheUrl) . 912 "?v=" . filectime($cacheFile) ?>" 913 <?php 914 } 915 916 function putThumbXML($filepath) { 917 // Put XML for the thumbnail of this folder or image. 918 // 919 ?> 920 <thumbnail 921 <?php 922 if (is_dir($filepath)) { 923 $thumbpath = findThumb($filepath); 924 if (!is_null($thumbpath)) putImageXML($thumbpath, C_thumbSize); 925 } else { 926 putImageXML($filepath, C_thumbSize); 927 } 928 ?> 929 ></thumbnail> 930 <?php 931 } 932 933 function putCommentsXML($imagepath) { 934 // Put XML attributes for the given image's date, exposure, and size. 935 // 936 $info = getImageInfo($imagepath); 937 if ($info == false) { // non-existent or unreadable file 938 echo " size=\"not found\"\n"; 939 } else { 940 $fsize = $info["size"]; 941 if ($fsize > 1048575) { 942 $fsize = sprintf("%0.1f MBytes", $fsize/1048576); 943 } else if ($fsize > 1023) { 944 $fsize = sprintf("%0.1f KBytes", $fsize/1024); 945 } else { 946 $fsize = "$fsize Bytes"; 947 } 948 echo " size=\"" . 949 (isset($info["width"]) ? 950 $info["width"] . " x " . $info["height"] . " pixels, " : 951 "") . 952 $fsize . 953 "\"\n"; 954 if (isset($info["date"])) { 955 $udate = $info["date"]; 956 $date = gmdate("D, j M Y, H:i", $udate); 957 echo " date=\"". xmlSpecials($date) . "\"\n"; 958 } 959 if (isset($info["model"])) { 960 echo " model=\"", xmlSpecials($info["model"]) . "\"\n"; 961 } 962 $details = array(); 963 if (isset($info["sec"])) { 964 $details[] = $info["sec"] . " sec"; 965 } 966 if (isset($info["f"])) { 967 $details[] = "f/" . $info["f"]; 968 } 969 if (isset($info["iso"])) { 970 $details[] = "ISO " . $info["iso"]; 971 } 972 if (isset($info["mm"])) { 973 $details[] = $info["mm"] . "mm"; 974 } 975 $exp = implode(", ", $details); 976 echo " exposure=\"" . xmlSpecials($exp) . "\"\n"; 977 } 978 } 979 980 981 ?>
End of listing