Add diagnostics to check integrity of data folder master
Add diagnostics to check integrity of data folder

--- a/common.inc.php
+++ b/common.inc.php
@@ -2,194 +2,218 @@
 include_once("config.inc.php");

 

 include("php-calendar.lib.php");

+

 function include_header($title) {

-		?>

-		<!doctype html>

-<!--[if lt IE 7]> <html class="no-js ie6 oldie" lang="en"> <![endif]-->

-<!--[if IE 7]>    <html class="no-js ie7 oldie" lang="en"> <![endif]-->

-<!--[if IE 8]>    <html class="no-js ie8 oldie" lang="en"> <![endif]-->

-<!--[if gt IE 8]><!--> <html class="no-js" lang="en"> <!--<![endif]-->

-<head>

-  <meta charset="utf-8">

-  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">

-

-  <title></title>

-  <meta name="description" content="">

-  <meta name="author" content="">

-

-  <meta name="viewport" content="width=device-width,initial-scale=1">

-

-  <!-- CSS concatenated and minified via ant build script-->

-  <link rel="stylesheet" href="css/style.css">

-  <!-- end CSS-->

-

-  <script src="js/libs/modernizr-2.0.6.min.js"></script>

-</head>

-

-<body>

-

-  <div id="container">

-    <header>

-

-    </header>

-    <div id="main" role="main">

-	<?php

-}

-function include_footer() {

-	?>

-	    </div>

-    <footer>

-

-    </footer>

-  </div> <!--! end of #container -->

-  

-</body>

-</html>

-	<?php

+    ?>

+    <!doctype html>

+    <!--[if lt IE 7]> <html class="no-js ie6 oldie" lang="en"> <![endif]-->

+    <!--[if IE 7]>    <html class="no-js ie7 oldie" lang="en"> <![endif]-->

+    <!--[if IE 8]>    <html class="no-js ie8 oldie" lang="en"> <![endif]-->

+    <!--[if gt IE 8]><!--> <html class="no-js" lang="en"> <!--<![endif]-->

+        <head>

+            <meta charset="utf-8">

+            <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">

+

+            <title></title>

+            <meta name="description" content="">

+            <meta name="author" content="">

+

+            <meta name="viewport" content="width=device-width,initial-scale=1">

+

+            <!-- CSS concatenated and minified via ant build script-->

+            <link rel="stylesheet" href="css/style.css">

+            <!-- end CSS-->

+

+            <script src="js/libs/modernizr-2.0.6.min.js"></script>

+        </head>

+

+        <body>

+

+            <div id="container">

+                <header>

+

+                </header>

+                <div id="main" role="main">

+                    <?php

+                }

+

+                function include_footer() {

+                    ?>

+                </div>

+                <footer>

+

+                </footer>

+            </div> <!--! end of #container -->

+            <!-- Grab Google CDN's jQuery, with a protocol relative URL; fall back to local if necessary -->

+            <script src="//ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.js"></script>

+            <script>window.jQuery || document.write('<script src="js/libs/jquery-1.5.1.min.js">\x3C/script>')</script>

+            <script type="text/javascript" src="js/jquery.imagefit.js"></script>

+            <script>

+                $(window).load(function(){

+                    $('.col1').imagefit();

+                });

+            </script>

+        </body>

+    </html>

+    <?php

 }

 

 function include_sidebar() {

-  ?>

+    ?>

     <a href="upload.php">Upload a new day</a><br>

     <a href="help.php">Help/Instructions</a><br>

     <hr>

-<?php

-foreach (getCalendarMonths() as $month) {

-    echo generate_calendar($month['year'], $month['month'], getCalendarDays($month['year'], $month['month']), 3);

-}

+    <?php

+    foreach (getCalendarMonths() as $month) {

+        echo generate_calendar($month['year'], $month['month'], getCalendarDays($month['year'], $month['month']), 3);

+    }

 }

 

 function getNextAvailableDate() {

-  if ($previousDate = getPreviousDate()) {

-    $nextDayTime = strtotime("+1 day",strtotime($previousDate));

-    if (date("m",$nextDayTime) == "08" and date("d",$nextDayTime) == "24") {

-      // skip the 24th of August

-      $nextDayTime = strtotime("+1 day",$nextDayTime);

-    } 

-    return date("Y-m-d",$nextDayTime);

-  } else {

-    return START_DATE;

-  }

-}

+    if ($previousDate = getPreviousDate()) {

+        $nextDayTime = strtotime("+1 day", strtotime($previousDate));

+        if (date("m", $nextDayTime) == "08" and date("d", $nextDayTime) == "24") {

+            // skip the 24th of August

+            $nextDayTime = strtotime("+1 day", $nextDayTime);

+        }

+        return date("Y-m-d", $nextDayTime);

+    } else {

+        return START_DATE;

+    }

+}

+

+function getDateFromFilename($filename) {

+    $fnameParts = explode(".", $filename);

+    return $fnameParts[0];

+}

+

+function getFilenameForDate($date, $fileext = "") {

+    $results = glob(DATA_DIR . "/" . $date . "*" . $fileext);

+    if (sizeof($results) > 0) {

+        return $results[0];

+    } else

+        return false;

+}

+

 function getPreviousDate() {

- $datedFiles = scandir(DATA_DIR); // sorted descendingly by default

- if (sizeof($datedFiles) > 2) {// always at least 2 even for an empty folder because of ./ and ../

-  return removeImageFileExtensions($datedFiles[sizeof($datedFiles)-1]);

-} else {

-  return false;

-}

-}

+    $datedFiles = scandir(DATA_DIR); // sorted descendingly by default

+    if (sizeof($datedFiles) > 2) {// always at least 2 even for an empty folder because of ./ and ../

+        return getDateFromFilename($datedFiles[sizeof($datedFiles) - 1]);

+    } else {

+        return false;

+    }

+}

+function getFirstDate() {

+    // not romance help

+    $datedFiles = scandir(DATA_DIR); // sorted descendingly by default

+    if (sizeof($datedFiles) > 2) {// always at least 2 even for an empty folder because of ./ and ../

+        return getDateFromFilename($datedFiles[2]);

+    } else {

+        return false;

+    }

+}

+

 function getPhoto($displayDate) {

-  $cacheFile = CACHE_DIR.$displayDate.".png";

-  if (file_exists($cacheFile)) {

-    return $cacheFile;

-  } else {

-    if (file_exists(DATA_DIR.$displayDate.".jpg")) {

-        $source_gd_image = imagecreatefromjpeg( DATA_DIR.$displayDate.".jpg");

-      

-    } else if (file_exists(DATA_DIR.$displayDate.".png")) {

-              $source_gd_image = imagecreatefrompng( DATA_DIR.$displayDate.".png" );

-   } else {

-     return false;

-   }

-   

-    if ( $source_gd_image === false )

-    {

-      return false;

-    }

-$source_image_width = imagesx($source_gd_image);

- $source_image_height =  imagesy($source_gd_image);

-

-               $header_gd_image = imagecreatefrompng( "img/header.png" );

-$header_image_width = imagesx($header_gd_image);

- $header_image_height =  imagesy($header_gd_image);

-

- $white = imagecolorallocate($source_gd_image, 255, 255, 255);

-  $black = imagecolorallocate($source_gd_image, 0, 0, 0);

-  function calculateTextBox($text,$fontFile,$fontSize) { 

-    /************ 

-    simple function that calculates the *exact* bounding box (single pixel precision). 

-    The function returns an associative array with these keys: 

-    left, top:  coordinates you will pass to imagettftext 

-    width, height: dimension of the image you have to create 

-    *************/ 

-    $rect = imagettfbbox($fontSize,0,$fontFile,$text); 

-    $minX = min(array($rect[0],$rect[2],$rect[4],$rect[6])); 

-    $maxX = max(array($rect[0],$rect[2],$rect[4],$rect[6])); 

-    $minY = min(array($rect[1],$rect[3],$rect[5],$rect[7])); 

-    $maxY = max(array($rect[1],$rect[3],$rect[5],$rect[7])); 

-    

-    return array( 

-     "left"   => abs($minX) - 1, 

-     "top"    => abs($minY) - 1, 

-     "width"  => $maxX - $minX, 

-     "height" => $maxY - $minY, 

-     "box"    => $rect 

-    ); 

-} 

-$date = strtotime($displayDate);

-// First we create our bounding box for the first text

-$textDayName = date("l",$date);

-$sizeDayName = 18;

-$fontDayName = "./img/mplus-1p-medium.ttf";

-$bboxDayName = calculateTextBox($textDayName,$fontDayName,$sizeDayName);

-

-$textDate = date("jS F Y",$date);

-$sizeDate = 14;

-$fontDate = "./img/mplus-1p-regular.ttf";

-$bboxDate = calculateTextBox($textDate,$fontDate,$sizeDate);

-

-$margin = 15;

-$maxX = $header_image_width + $margin*2 + max($bboxDayName['width'],$bboxDate['width']) + $margin*2;

-$maxY = max($header_image_height + $margin*2 , ($bboxDayName['height']+$margin+$bboxDate['height']));

-

-// Draw a white rectangle

-imagefilledrectangle($source_gd_image, 0, 0, $maxX, $maxY, $white);

-

-imagecopy($source_gd_image, $header_gd_image,$margin,$margin,0,0,$header_image_width,$header_image_height);

+    $cacheFile = CACHE_DIR . $displayDate . ".png";

+    if (file_exists($cacheFile)) {

+        return $cacheFile;

+    } else {

+        if ($fname = getFilenameForDate($displayDate, ".jpg")) {

+            $source_gd_image = imagecreatefromjpeg($fname);

+        } else if ($fname = getFilenameForDate($displayDate, ".png")) {

+            $source_gd_image = imagecreatefrompng($fname);

+        } else {

+            return false;

+        }

+

+        if ($source_gd_image === false) {

+            return false;

+        }

+        $source_image_width = imagesx($source_gd_image);

+        $source_image_height = imagesy($source_gd_image);

+

+        $white = imagecolorallocate($source_gd_image, 255, 255, 255);

+        $black = imagecolorallocate($source_gd_image, 0, 0, 0);

+

+        function calculateTextBox($text, $fontFile, $fontSize) {

+            /*             * ********** 

+              simple function that calculates the *exact* bounding box (single pixel precision).

+              The function returns an associative array with these keys:

+              left, top:  coordinates you will pass to imagettftext

+              width, height: dimension of the image you have to create

+             * *********** */

+            $rect = imagettfbbox($fontSize, 0, $fontFile, $text);

+            $minX = min(array($rect[0], $rect[2], $rect[4], $rect[6]));

+            $maxX = max(array($rect[0], $rect[2], $rect[4], $rect[6]));

+            $minY = min(array($rect[1], $rect[3], $rect[5], $rect[7]));

+            $maxY = max(array($rect[1], $rect[3], $rect[5], $rect[7]));

+

+            return array(

+                "left" => abs($minX) - 1,

+                "top" => abs($minY) - 1,

+                "width" => $maxX - $minX,

+                "height" => $maxY - $minY,

+                "box" => $rect

+            );

+        }

+

+        $date = strtotime($displayDate);

+        $textDayName = date("l", $date);

+        $sizeDayName = 24 + round(($source_image_height - 1000) / 1000) * 2;

+        $fontDayName = "./img/mplus-1p-medium.ttf";

+        $bboxDayName = calculateTextBox($textDayName, $fontDayName, $sizeDayName);

+

+        $textDate = date("jS F Y", $date);

+        $sizeDate = 24 + floor(($source_image_height - 1000) / 100);

+        $fontDate = "./img/mplus-1p-regular.ttf";

+        $bboxDate = calculateTextBox($textDate, $fontDate, $sizeDate);

+

+        $margin = 15;

+

+// Draw a black rectangle

+        imagefilledrectangle($source_gd_image, 0, 0, $source_image_width, $margin * 2 + $bboxDayName['height'], $black);

 

 // Write it

-imagettftext($source_gd_image, $sizeDayName, 0, $header_image_width+$margin*2+$bboxDayName['left'], $margin+$bboxDayName['top'], $black, $fontDayName, $textDayName);

-imagettftext($source_gd_image, $sizeDate, 0, $header_image_width+$margin*2+$bboxDate['left'], $margin+$bboxDayName['height']+$margin+$bboxDate['top'], $black, $fontDate, $textDate);

-

-    imagepng( $source_gd_image, $cacheFile, 9 );

-    imagedestroy( $source_gd_image );

-    return $cacheFile;

-  }

-}

-function getCalendarDays($year,$month) {

-  $result = Array();

-  if ($handle = opendir(DATA_DIR)) {

-    while (false !== ($file = readdir($handle))) {

-        if ($file != "." && $file != ".." && startsWith($file,"$year-$month")) {

-          $parts = explode("-",$file);

-          $day = removeImageFileExtensions($parts[2]);

-           $result[$day]=Array("index.php?date=$year-$month-$day",'linked-day');

-        }

-      }

-  }

-  ksort($result);

-  return $result;

-}

+        imagettftext($source_gd_image, $sizeDayName, 0, $margin + $bboxDayName['left'], $margin + $bboxDayName['top'], $white, $fontDayName, $textDayName);

+        imagettftext($source_gd_image, $sizeDate, 0, (($source_image_width - $bboxDayName['width']) / 2) + $bboxDayName['width'] - $bboxDate['left'], + $margin + $bboxDate['top'], $white, $fontDate, $textDate);

+

+        imagepng($source_gd_image, $cacheFile, 9);

+        imagedestroy($source_gd_image);

+        return $cacheFile;

+    }

+}

+

+function getCalendarDays($year, $month) {

+    $result = Array();

+    if ($handle = opendir(DATA_DIR)) {

+        while (false !== ($file = readdir($handle))) {

+            if ($file != "." && $file != ".." && startsWith($file, "$year-$month")) {

+                $parts = explode("-", $file);

+                $day = getDateFromFilename($parts[2]);

+                $result[$day] = Array("index.php?date=$year-$month-$day", 'linked-day');

+            }

+        }

+    }

+    ksort($result);

+    return $result;

+}

+

 function getCalendarMonths() {

-  $months = Array();

-  if ($handle = opendir(DATA_DIR)) {

-    while (false !== ($file = readdir($handle))) {

-       if ($file != "." && $file != "..") {

-          $parts = explode("-",$file);

-          $months[$parts[0].$parts[1]]=Array("month"=>$parts[1],"year"=>$parts[0]);

-        }

-      }

-   }

-  return $months;

-}

-function startsWith($haystack, $needle)

-{

-  // source: http://stackoverflow.com/questions/834303/php-startswith-and-endswith-functions

+    $months = Array();

+    if ($handle = opendir(DATA_DIR)) {

+        while (false !== ($file = readdir($handle))) {

+            if ($file != "." && $file != "..") {

+                $parts = explode("-", $file);

+                $months[$parts[0] . $parts[1]] = Array("month" => $parts[1], "year" => $parts[0]);

+            }

+        }

+    }

+    return $months;

+}

+

+function startsWith($haystack, $needle) {

+    // source: http://stackoverflow.com/questions/834303/php-startswith-and-endswith-functions

     $length = strlen($needle);

     return (substr($haystack, 0, $length) === $needle);

 }

-function removeImageFileExtensions($filename) {

-  return  str_replace(Array(".png",".jpg"),"",$filename);

-}

 ?>

--- a/confirmUpload.php
+++ b/confirmUpload.php
@@ -1,46 +1,47 @@
 <?php
+
 include("common.inc.php");
 include_header("confirmUpload");
 $error = false;
 
 if (!isset($_FILES['userfile'])) {
-	echo 'No file was uploaded. You should start from the <a href="upload.php">upload page</a>';
-	$error = true;
+    echo 'No file was uploaded. You should start from the <a href="upload.php">upload page</a>';
+    $error = true;
 } else {
- $imageinfo = getimagesize($_FILES['userfile']['tmp_name']);
- $source_image_type = $imageinfo['mime'];
-$source_image_width = $imageinfo[0];
- $source_image_height = $imageinfo[1];
+    $imageinfo = getimagesize($_FILES['userfile']['tmp_name']);
+    $source_image_type = $imageinfo['mime'];
+    $source_image_width = $imageinfo[0];
+    $source_image_height = $imageinfo[1];
 }
 
-if($error == false && ($source_image_type != 'image/png' && $source_image_type != 'image/jpeg')) {
+if ($error == false && ($source_image_type != 'image/png' && $source_image_type != 'image/jpeg')) {
     echo "Sorry, we only accept PNG and JPEG images. Your image was of type '$source_image_type'.<br>";
-	$error = true;
-} 
-if($error == false && ($source_image_width < MIN_IMAGE_SIZE || $source_image_height < MIN_IMAGE_SIZE)) {
-    echo "Sorry, we only accept images larger than ".MIN_IMAGE_SIZE." pixels. Your image was $source_image_width x $source_image_height pixels big. <br>";
-	$error = true;
-} 
- 
-if($error == false && ($source_image_width != $source_image_height)) {
-    echo "Sorry, we only accept images that are exactly square (the height is the same as the width). Your image was $source_image_width x $source_image_height pixels big. <br>";
-	$error = true;
-} 
+    $error = true;
+}
+if ($error == false && ($source_image_width < MIN_IMAGE_SIZE || $source_image_height < MIN_IMAGE_SIZE || $source_image_width != $source_image_height)) {
+    echo "Sorry, your image wasn't big enough. In order to ensure that John's calendar looks super-beautiful, we only accept images that are 1000 px or larger and are squares. Your image was $source_image_width x $source_image_height pixels big. <br>";
+    $error = true;
+}
+    $hash = md5_file($_FILES['userfile']['tmp_name']);
+if ($error == false && sizeof(glob(DATA_DIR."*".$hash."*")) > 0) {
+    echo "Sorry, we already have an image identical to this one.<br>";
+    $error = true;
+}
+
 if (!$error) {
-	$fileExtension = ($source_image_type == 'image/png' ? ".png" : ".jpg");
-	$fileDate = getNextAvailableDate();
-	echo "Uploaded file meets all necessary requirements, next available date is $fileDate <br>";
-	$uploaddir = '/var/spool/uploads/'; # Outside of web root
-	$uploadfile = DATA_DIR  . $fileDate . $fileExtension;
-	if (file_exists($uploadfile)) {
-		echo "Oh no! A file for $fileDate already exists! Please retry in a moment<br>";
-	} else {
-		if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)) {
-		    echo "File was successfully uploaded.\n<br>";
-		} else {
-		    echo "File uploading failed.\n<br>";
-		}
-	}
+    $fileExtension = ($source_image_type == 'image/png' ? ".png" : ".jpg");
+    $fileDate = getNextAvailableDate();
+    //echo "Uploaded file meets all necessary requirements, next available date is $fileDate <br>";
+    $uploadfile = DATA_DIR . $fileDate . ".".$hash. $fileExtension;
+    if (file_exists($uploadfile)) {
+        echo "Oh no! A file for $fileDate already exists! Please retry in a moment<br>";
+    } else {
+        if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)) {
+            echo "Yay! Your image has been added as $fileDate. Click <A href='index.php'>here</a> to go back to the site.\n<br>";
+        } else {
+            echo "File uploading failed.\n<br>";
+        }
+    }
 }
 include_footer();
 ?>

file:b/diagnostics.php (new)
--- /dev/null
+++ b/diagnostics.php
@@ -1,1 +1,55 @@
+<?php
 
+include "common.inc.php";
+if (!($firstDate = getFirstDate()))
+    die("Cannot complete diagnotics, cannot get first file date");
+$currentDate = $firstDate;
+if (!($lastDate = getPreviousDate()))
+    die("Cannot complete diagnotics, cannot get last file date");
+$hashes = Array();
+while ($currentDate != $lastDate) {
+    $results = glob(DATA_DIR . "/" . $currentDate . "*");
+    if (sizeof($results) == 0) {
+        echo "Error on $currentDate, no photo found for that date <br>";
+    } else if (sizeof($results) > 1) {
+        echo "Error on $currentDate, more than one photo found for that date: <br>";
+        var_dump($results);
+        echo "<br>";
+    } else {
+        // normal day
+        $fname = basename($results[0]);
+        $fnParts = explode(".", $fname);
+        if (sizeof($fnParts) < 3) {
+            echo "Error on $currentDate, malformed file name {$fname} <br>";
+        } else {
+            if (strtotime($currentDate) != strtotime($fnParts[0])) {
+                echo "Error on $currentDate, file name {$fname} date does not match expected date <br>";
+            }
+            if ($imgsize = getimagesize($results[0])) {
+                if ($imgsize[0] < MIN_IMAGE_SIZE || $imgsize[1] < MIN_IMAGE_SIZE || $imgsize[0] != $imgsize[1]) {
+                    echo "Error on $currentDate, file name {$fname} is not large enough dimensions or non-square dimensions <br>";
+                }
+            } else {
+                echo "Error on $currentDate, file name {$fname} could not be opened as an image file. File may have been corrupted <br>";
+            }
+            $hash = md5_file($results[0]);
+            if ($hash != $fnParts[1]) {
+                echo "Error on $currentDate, file name {$fname} hash does not match expected hash. File may have been corrupted or altered <br>";
+            }
+            if (in_array($hash, array_keys($hashes))) {
+                echo "Error on $currentDate, file {$fname} hash matches that of {$hashes[$hash]}. Files may be identitical duplicates of the same image <br>";
+            }
+            $hashes[$hash] = $fname;
+
+            if (sizeof($fnParts[2]) > 3 || ($fnParts[2] != "png" && $fnParts[2] != "jpg")) {
+                echo "Error on $currentDate, file name {$fname} extension unexpected <br>";
+            }
+        }
+    }
+
+    // go to next day
+    $currentDate = date("Y-m-d", strtotime("+1 day", strtotime($currentDate)));
+}
+echo "Scanned files from $firstDate to $currentDate, no errors detected<br>";
+?>
+

file:a/index.php -> file:b/index.php
--- a/index.php
+++ b/index.php
@@ -12,7 +12,7 @@
 if ($photoFilename == false) {

     echo "Oops, no photo uploaded for this day<br>";

 } else {

-echo '<img src="'.$photoFilename.'">';

+echo '<img alt="Photo for '.$displayDate.'" src="'.$photoFilename.'">';

 }

 ?>

     </div> <div class="col2">


--- /dev/null
+++ b/js/jquery.imagefit.js
@@ -1,1 +1,48 @@
-
+/* jquery.imagefit 
+ *
+ * Version 0.2 by Oliver Boermans <http://www.ollicle.com/eg/jquery/imagefit/>
+ *
+ * Extends jQuery <http://jquery.com>
+ *
+ */
+(function($) {
+	$.fn.imagefit = function(options) {
+		var fit = {
+			all : function(imgs){
+				imgs.each(function(){
+					fit.one(this);
+					})
+				},
+			one : function(img){
+				$(img)
+					.width('100%').each(function()
+					{
+						$(this).height(Math.round(
+							$(this).attr('startheight')*($(this).width()/$(this).attr('startwidth')))
+						);
+					})
+				}
+		};
+		
+		this.each(function(){
+				var container = this;
+				
+				// store list of contained images (excluding those in tables)
+				var imgs = $('img', container).not($("table img"));
+				
+				// store initial dimensions on each image 
+				imgs.each(function(){
+					$(this).attr('startwidth', $(this).width())
+						.attr('startheight', $(this).height())
+						.css('max-width', $(this).attr('startwidth')+"px");
+				
+					fit.one(this);
+				});
+				// Re-adjust when window width is changed
+				$(window).bind('resize', function(){
+					fit.all(imgs);
+				});
+			});
+		return this;
+	};
+})(jQuery);

file:a/readme.txt -> file:b/readme.txt
--- a/readme.txt
+++ b/readme.txt
@@ -2,7 +2,7 @@
 

 Minimum Requirements

 PHP4 with gd2 library enabled

-

+	If it's not installed or not enabled, you will get a "Fatal error: Call to undefined function imagecreatefromjpeg() in common.inc.php on line 88" error when viewing images

 Install:

 Unzip onto a webserver and you're done!

 


file:a/upload.php -> file:b/upload.php
--- a/upload.php
+++ b/upload.php
@@ -4,7 +4,7 @@
 ?>
 
 <form name="upload" action="confirmUpload.php" method="POST" ENCTYPE="multipart/form-data">
-Select the file to upload: <input type="file" name="userfile">
+Select the file to upload: <input type="file" name="userfile" accept="image/*">
 <input type="submit" name="upload" value="upload">
 </form>