info343/submit.php

<?php
   ini_set('track_errors', true);
   header('Content-type: text/plain');
   
   $MASTER_LOG = 'submissions/master.log';
   if (!is_writable($MASTER_LOG)) {
      httpdie(500, 'Internal Server Error', "There was an error attempting to receive your files.", "[error] Master log '$MASTER_LOG' is not writeable.");
   }
   
   // httpdie(500, 'Internal Server Error', "There was an error attempting to receive your files.", "[debug] Test fail.");
   
   date_default_timezone_set('America/Los_Angeles');
   
   $NOW = time();
   $TIMESTAMP = date('c');
   $IP_ADDR = $_SERVER['REMOTE_ADDR'];
   
   foreach (array('username', 'password', 'assignmenttype', 'assignmentid', 'section') as $param) {
      if (isset($_POST[$param])) {
         ${strtoupper($param)} = $_POST[$param];
      } else {
         httpdie(400, "Bad Request", "You made an invalid request.", "[error] Parameter '$param' not supplied.");
      }
   }
   
   $ASSIGNMENTS_XML = new DOMDocument();
   $ASSIGNMENTS_XML->load('xml/assignments.xml');
   $assignments_xpath = new Domxpath($ASSIGNMENTS_XML);
   
   // turnin[@section="' + SECTION + '"] | turnin[@section="all"]
   $assignmentpath = "//{$ASSIGNMENTTYPE}[@id='{$ASSIGNMENTID}']";
   $opensquery = "{$assignmentpath}/turnin[@section='{$SECTION}']/@opens | {$assignmentpath}/turnin[@section='all']/@opens";
   $closesquery = "{$assignmentpath}/turnin[@section='{$SECTION}']/@closes | {$assignmentpath}/turnin[@section='all']/@closes";
   $latedaysquery = "{$assignmentpath}/turnin[@section='{$SECTION}']/@latedays | {$assignmentpath}/turnin[@section='all']/@latedays";
   
   $openstr = $assignments_xpath->query($opensquery)->item(0)->textContent;
   $closestr = $assignments_xpath->query($closesquery)->item(0)->textContent;
   
   $latedaysobj = $assignments_xpath->query($latedaysquery);
   $latedays = 0;
   if ($latedaysobj) {
      $latedays = intval($latedaysobj->item(0)->textContent);
   }
   
   $opens = strtotime($openstr);
   $closes = strtotime($closestr);
   $cutoff = strtotime("+$latedays days", $closes);
   $cutoffstr = date("r", $cutoff);
   
   $closes_grace = strtotime("+15 minutes", $closes);
   $cutoff_grace = strtotime("+15 minutes", $cutoff);
   
   $typefile = $ASSIGNMENTTYPE == 'minilab' ? 'lecture' : $ASSIGNMENTTYPE;
   $ENTRY_XML = new DOMDocument();
   $ENTRY_XML->load("xml/{$typefile}s.xml");
   $entry_xpath = new Domxpath($ENTRY_XML);
   
   $ASSIGNMENTNAME = $entry_xpath->query("//{$ASSIGNMENTTYPE}[@id='{$ASSIGNMENTID}']/title")->item(0)->textContent;
   
   if ($NOW < $opens) {
      httpdie(400, "Bad Request", "The assignment '$ASSIGNMENTID' is not open for turnin yet. (Opens $openstr)");
   } else if ((!$latedays && ($NOW > $closes_grace)) || ($latedays && ($NOW > $cutoff_grace))) {
      httpdie(400, "Bad Request", "The assignment '$ASSIGNMENTID' has already closed. (Closed $closestr)");
   } else {
      list($data, $info) = authenticate("$USERNAME:$PASSWORD");
      if ($info['http_code'] != 200 && $info['http_code'] != 401) {
         httpdie(500, 'Internal Server Error', 'An internal error occurred.', "[error] Unrecognized response: {$info['http_code']} / $data");
      } else if ($info['http_code'] == 401) {
         httpdie(401, 'Authorization Required', 'Authentication failure.', "[info] Authentication failure for user $USERNAME.");
      } else {
         $realname = trim(preg_replace('/^Authorized: /', '', $data));
         // $realname = "foo";
         
         $days_late = 0;
         if ($latedays && ($NOW > $closes_grace)) {
            // calculate days late
            $days_late++;
            for ($start = $closes_grace, $end = strtotime("+1 day", $start); $end <= $cutoff_grace; $start = $end, $end = strtotime("+1 day", $start), $days_late++) {
               if ($NOW <= $end && $NOW > $start) {
                  break;
               }
            }
         }
         
         $files = $_FILES['files'];
         $receipt = save_files($files, $days_late);
         email_submitter($realname, $receipt, $files, $days_late);
         
         // Store the submission
         header('Content-type: text/plain');
         print "Submission accepted. Receipt: $receipt\n";
         print $days_late ? ($days_late == 1 ? "$days_late day late" : "$days_late days late") : 'on time';
      }
   }
   
   // echo "\n\nassignment: $ASSIGNMENTTYPE $ASSIGNMENTID\nsection: $SECTION\nopens: $opens\ncloses: $closes\n\n";
   // echo '$_FILES: ';
   // var_dump($_FILES);
   // echo '$_POST: ';
   // var_dump($_POST);
   
   
   function save_files($files, $days_late) {
      global $ASSIGNMENTTYPE, $ASSIGNMENTID, $USERNAME, $MASTER_LOG, $TIMESTAMP, $IP_ADDR;
      
      $dirname = "submissions/$ASSIGNMENTTYPE/$ASSIGNMENTID/$USERNAME/$TIMESTAMP";
      if (!@mkdir($dirname, 0770, true)) {
         httpdie(500, 'Internal Server Error', 'There was an error processing your uploaded files.', "[error] Directory '$dirname' could not be created. Error: $php_errormsg");
      }
      // chdir($dirname);
      
      $latedaystr = $days_late ? ($days_late == 1 ? "$days_late day late" : "$days_late days late") : 'on time';
      
      manifest_log("$dirname/MANIFEST", <<<EOF
Timestamp: {$TIMESTAMP} ({$latedaystr})
Username: {$USERNAME}
IP: {$IP_ADDR}
User Agent: {$_SERVER['HTTP_USER_AGENT']}
Files submitted:
EOF
);
      
      $errors = array();
      for ($i = 0; $i < count($files['name']); $i++) {
         $filename = $files['name'][$i];
         manifest_log("$dirname/MANIFEST", "   {$filename} ({$files['error'][$i]})");
         $error = '';
         if ($files['error'][$i]) {
            $error = "[error] File '$filename' not correctly uploaded for user '$USERNAME'. Error code: {$files['error'][$i]}";
         } else if (!is_uploaded_file($files['tmp_name'][$i])) {
            $error = "[error] tmp_name associated with '$filename' not an uploaded file.";
         } else if (!@move_uploaded_file($files['tmp_name'][$i], "$dirname/$filename")) {
            $error = "[error] File '$filename' could not be moved to destination '$dirname' for user '$USERNAME'. Error: '$php_errormsg'";
         }
         
         if ($error) {
            manifest_log("$dirname/MANIFEST", "   -> $error\n");
            array_push($errors, $error);
         }
      }
      if (count($errors)) {
         manifest_log($MASTER_LOG, "$TIMESTAMP $USERNAME@$IP_ADDR: Submission failed. Errors: " . count($errors) . " / " . count($files));
         httpdie(500, 'Internal Server Error', 'One or more of your uploaded files was not successfully uploaded.', implode("\n", $errors));
      } else {
         global $ASSIGNMENTID, $NOW;
         $receipt = make_receipt($ASSIGNMENTID, $USERNAME, $NOW, $IP_ADDR);
         manifest_log("$dirname/MANIFEST", "Receipt: $receipt");
         manifest_log($MASTER_LOG, "$TIMESTAMP $USERNAME@$IP_ADDR: Accepted submission for assignment '$ASSIGNMENTID'. Receipt: $receipt");
         return $receipt;
      }
   }
   
   function make_receipt($assid, $username, $time, $ip) {
      // string IP → 4-byte int
      $ippack = 0;
      foreach (explode('.', $IP_ADDR) as $i => $byte) {
         $ippack |= intval($byte) << ($i * 8);
      }
      
      // [0-9a-z] ASCII username → sequence of base36-decoded 6-bit integers → hex
      // 0~9 → 0x0~0x9
      // a~z → 0xA~0x23
      $usernamepack = 0;
      foreach (str_split($username) as $i => $chr) {
         $usernamepack |= base_convert($chr, 36, 10) << ($i * 6);
      }
      
      srand(crc32($assid));
      $message = sprintf("%x%x%x", $time, $ippack, $usernamepack);
      return str_shuffle($message);
   }
   
   function email_submitter($realname, $receipt, $files, $days_late) {
      global $USERNAME, $ASSIGNMENTNAME, $ASSIGNMENTTYPE, $ASSIGNMENTID, $TIMESTAMP, $MASTER_LOG, $IP_ADDR;
      
      $from = 'Morgan Doocy <mdoocy@uw.edu>';
      $reply_to = 'Morgan Doocy <mdoocy@uw.edu>';
      // $to = 'Morgan Doocy <mdoocy@uw.edu>';
      $to = "$realname <$USERNAME@uw.edu>";
      $bcc = 'Morgan Doocy <mdoocy@uw.edu>';
      $subject = "[info344] {$ASSIGNMENTNAME} turnin receipt";
      
      $filenames = array();
      for ($i = 0; $i < count($files['name']); $i++) {
         array_push($filenames, $files['name'][$i]);
      }
      $filelist = "\t" . implode("\n\t", $filenames);
      
      $latedaystr = $days_late ? ($days_late == 1 ? "$days_late day late" : "$days_late days late") : 'on time';
      
      $body = <<<EOB
{$realname},

This is an automated notification that your {$ASSIGNMENTNAME} was successfully submitted. Please save this email for your records.

UWNetID:  {$USERNAME}
Timesamp:  {$TIMESTAMP} ({$latedaystr})
Files submitted:
{$filelist}
Receipt:  {$receipt}

Note: Plase do not rely on receiving this email for your records, as it could be filtered by your email service. You should always save the receipt displayed after submitting your assignment.


Thank you!
EOB;
      
      $dirname = "submissions/$ASSIGNMENTTYPE/$ASSIGNMENTID/$USERNAME/$TIMESTAMP";
      
      if (!@mail($to, $subject, $body, "From: $from\r\nBcc:$bcc\r\nReply-To: $reply_to")) {
         manifest_log($MASTER_LOG, "$TIMESTAMP $USERNAME@$IP_ADDR: Email sending failed to '$to' for '$ASSIGNMENTID'. Receipt: $receipt. Error: '$php_errormsg'");
         error_log("[error] Mail sending to '$to' failed. Error: '$php_errormsg'");
         manifest_log("$dirname/MANIFEST", "Mail delivery to '$to' failed.");
      } else {
         manifest_log($MASTER_LOG, "$TIMESTAMP $USERNAME@$IP_ADDR: Mail accepted for delivery to '$to' for '$ASSIGNMENTID'. Receipt: $receipt");
         manifest_log("$dirname/MANIFEST", "Mail accepted for delivery to '$to'.");
      }
   }
   
   function manifest_log($file, $message) {
      file_put_contents($file, "$message\n", FILE_APPEND);
   }
   
   function httpdie($code, $name, $message, $diemessage = '') {
      header("HTTP/1.1 $code $name");
      header('Content-Type: text/plain');
      error_log($diemessage ? $diemessage : $message);
      die($message);
   }
   
   function authenticate($credentials) {
      $url = "https://info343.ischool.uw.edu/auth/auth.php";
      $page = "/auth/auth.php";
      $headers = array (
         "GET $page HTTP/1.1",
         "Host: info343.ischool.uw.edu",
         "Authorization: Basic " . base64_encode($credentials)
      );
      
      $ch = curl_init();
      curl_setopt($ch, CURLOPT_URL, $url);
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
      curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
      curl_setopt($ch, CURLOPT_TIMEOUT, 60);
      curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
      // curl_setopt($ch, CURLOPT_USERAGENT, $defined_vars['HTTP_USER_AGENT']);
      
      // Apply the XML to our curl call
      // curl_setopt($ch, CURLOPT_POST, 1);
      // curl_setopt($ch, CURLOPT_POSTFIELDS, $xml_data);
      
      $data = curl_exec($ch);
      $info = curl_getinfo($ch);
      
      if (curl_errno($ch)) {
         httpdie(500, "Internal Server Error", "Curl error.", "[error] Curl error: " . curl_error($ch));
      }
      
      curl_close($ch);
      return array($data, $info);
   }
?>