Index: aicc.php
===================================================================
RCS file: /cvsroot/moodle/moodle/mod/scorm/aicc.php,v
retrieving revision 1.42
diff -u -r1.42 aicc.php
--- aicc.php	26 Jun 2010 00:48:35 -0000	1.42
+++ aicc.php	2 Jul 2010 09:37:52 -0000
@@ -55,13 +55,14 @@
         } else {
             print_error('cannotcallscript');
         }
-        scorm_write_log("aicc", "scoid: $scoid", $scoid);
-        scorm_write_log("aicc", "mode: $mode", $scoid);
-        scorm_write_log("aicc", "status: $status", $scoid);
-        scorm_write_log("aicc", "attempt: $attempt", $scoid);
-        scorm_write_log("aicc", "command: $command", $scoid);
-        scorm_write_log("aicc", "sessionid: $sessionid", $scoid);
-        scorm_write_log("aicc", "aiccdata:\r\n$aiccdata", $scoid);
+        $aiccrequest = "MOODLE scoid: $scoid"
+                     . "\r\nMOODLE mode: $mode"
+                     . "\r\nMOODLE status: $status"
+                     . "\r\nMOODLE attempt: $attempt"
+                     . "\r\nAICC sessionid: $sessionid"
+                     . "\r\nAICC command: $command"
+                     . "\r\nAICC aiccdata:\r\n$aiccdata";
+        scorm_debug_log_write("aicc", "HACP Request:\r\n$aiccrequest", $scoid);
         ob_start();
 
         if ($scorm = $DB->get_record('scorm',array('id'=>$sco->scorm))) {
@@ -386,5 +387,5 @@
         }
     }
     $aiccresponse = ob_get_contents();
-    scorm_write_log("aicc", "response:\r\n$aiccresponse", $scoid);
-    ob_end_flush();
\ No newline at end of file
+    scorm_debug_log_write("aicc", "HACP Response:\r\n$aiccresponse", $scoid);
+    ob_end_flush();
Index: lib.php
===================================================================
RCS file: /cvsroot/moodle/moodle/mod/scorm/lib.php,v
retrieving revision 1.141
diff -u -r1.141 lib.php
--- lib.php	25 Jun 2010 07:30:56 -0000	1.141
+++ lib.php	2 Jul 2010 09:18:27 -0000
@@ -969,27 +969,127 @@
     $navigation->nodetype = navigation_node::NODETYPE_LEAF;
 }
 /**
- * writes log output to a temp log file
+ * Get the filename for a temp log file
  *
  * @param string $type - type of log(aicc,scorm12,scorm13) used as prefix for filename
- * @param string $text - text to be written to file.
- * @param integer $scoid - scoid of object this log entry is for.
+ * @param integer $scoid - scoid of object this log entry is for
+ * @return string The filename as an absolute path
  */
-function scorm_write_log($type, $text, $scoid) {
+function scorm_debug_log_filename($type, $scoid) {
+    global $CFG, $USER;
+
+    $logpath = $CFG->dataroot.'/temp/scormlogs';
+    $logfile = $logpath.'/'.$type.'debug_'.$USER->id.'_'.$scoid.'.log';
+    return $logfile;
+}
+/**
+ * Writes log output to a temp log file
+ *
+ * @param string $type - type of log(aicc,scorm12,scorm13) used as prefix for filename
+ * @param string $text - text to be written to file
+ * @param integer $scoid - scoid of object this log entry is for
+ *  
+ */
+function scorm_debug_log_write($type, $text, $scoid) {
     global $CFG, $USER;
 
     $debugenablelog = debugging('', DEBUG_DEVELOPER);
     if (!$debugenablelog || empty($text)) {
         return ;
     }
-    if (make_upload_directory('temp/scormlogs/')) {
-        $logpath = $CFG->dataroot.'/temp/scormlogs';
 
-        $logfile = $logpath.'/'.$type.'debug_'.$USER->id.'_'.$scoid.'.log';
+    if (make_upload_directory('temp/scormlogs/')) {
+        $logfile = scorm_debug_log_filename($type, $scoid);
         @file_put_contents($logfile, date('Y/m/d H:i:s O')." DEBUG $text\r\n", FILE_APPEND);
     }
 }
 /**
+ * Remove debug log file
+ *
+ * @param string $type - type of log(aicc,scorm12,scorm13) used as prefix for filename
+ * @param integer $scoid - scoid of object this log entry is for
+ * @return boolean True if the file is successfully deleted, false otherwise
+ */
+function scorm_debug_log_remove($type, $scoid) {
+    global $CFG, $USER;
+
+    $debugenablelog = debugging('', DEBUG_DEVELOPER);
+    $logfile = scorm_debug_log_filename($type, $scoid);
+    if (!$debugenablelog || !file_exists($logfile)) {
+        return false;
+    }
+
+    return @unlink($logfile);
+}
+/**
+ * Print out log output, full or incremental
+ *
+ * @param string $type - type of log(aicc,scorm12,scorm13) used as prefix for filename
+ * @param integer $scoid - scoid of object this log entry is for
+ * @param string $action - type of the retrieval(full,incremental). Default: full
+ * @return mixed True if anything has been echoed, false on failure e.g. if the file doesn't exists or no update has been found, null otherwise
+ * @todo SplFileObject should be the preferred choice but it's not backward compatible with 1.9 requirements 
+ */
+function scorm_debug_log_print($type, $scoid, $action = 'full') {
+    global $CFG, $SESSION, $USER;
+
+    $debugenablelog = debugging('', DEBUG_DEVELOPER);
+    if (!$debugenablelog) {
+        return null;
+    }
+
+    $logfile = scorm_debug_log_filename($type, $scoid);;
+    if (file_exists($logfile) && is_readable($logfile)) {
+        switch($action) {
+            case 'incremental':
+                $last = null;
+                if (isset($SESSION->scorm_debug_log_last_position)) {
+                    $last = $SESSION->scorm_debug_log_last_position;
+                    $handle = @fopen($logfile, 'r');
+                    if ($handle) {
+                        $position = filesize($logfile);
+                        $least = ($position > $last ? $last : -1); // -1 => log smaller than previous run
+                        if ($least > 0) {
+                            if (fseek($handle, $least, SEEK_SET) > -1) {
+                                $line = null;
+                                while (!feof($handle)) {
+                                    $line = stream_get_line($handle, 4096, "\r\n");;
+                                    // Use a fixed EOL, \n, and flush the line to output
+                                    echo rtrim($line, "\r\n") . "\n";
+                                }
+                                // Track the Last Position
+                                $SESSION->scorm_debug_log_last_position = $position;
+                            }
+                        }
+                        fclose($handle);
+                        return true;
+                    }
+                    return false;
+                }
+                // Get the full log: no valid last timestamp found in session i.e. no break
+            default:
+                // Get the content: slow but memory proof
+                $handle = @fopen($logfile, 'r');
+                if ($handle) {
+                    $position = filesize($logfile);
+                    $line = null;
+                    while (!feof($handle)) {
+                        $line = $buffer = stream_get_line($handle, 4096, "\r\n");;
+                        // Fix the EOL with \n and flush the line to output
+                        echo rtrim($line, "\r\n") . "\n";
+                    }
+                    fclose($handle);
+                    // Track the Last Position
+                    $SESSION->scorm_debug_log_last_position = $position;
+                    return true;
+                }
+                return false;
+        }
+    }
+
+    return false;
+}
+/**
  * writes overview info for course_overview block - displays upcoming scorm objects that have a due date
  *
  * @param object $type - type of log(aicc,scorm12,scorm13) used as prefix for filename
--- aicc_debug.php
+++ aicc_debug.php
@@ -0,0 +1,205 @@
+<?php
+    // Prevent caching Headers
+    header("Expires: Mon, 28 Jun 2010 00:00:00 GMT");
+    header("Cache-Control: no-cache");
+    header("Pragma: no-cache");
+
+    require_once('../../../config.php');
+    include_once($CFG->dirroot.'/mod/scorm/lib.php');
+
+    $ajax = optional_param('ajax', 0, PARAM_INT); // 1=update, 2=delete, <any>=get
+
+    $PAGE->set_url('/mod/scorm/datamodels/aicc_debug.php', array('ajax' => $ajax));
+
+    // Simple authorization: $USER will be already able to access just
+    //                       to his/her perfomance on his/her own attempts
+    require_login();
+
+    // Required parameters from SESSION
+    if (isset($SESSION->scorm_scoid)) {
+        $scoid = $SESSION->scorm_scoid;
+    } else {
+        print_error('cannotcallscript');
+    }
+
+    $logfile = scorm_debug_log_filename($type, $scoid);
+    switch ($ajax) {
+        case 1: // update
+            $result = scorm_debug_log_print('aicc', $scoid, 'incremental');
+            exit();
+        case 2: // delete
+            $result = scorm_debug_log_remove('aicc', $scoid);
+            echo ($result ? 'true' : 'false');
+            exit();
+    }
+
+    // Clean up any previous incremental read
+    unset($SESSION->scorm_debug_log_last_position);
+
+?>
+<html>
+    <head>
+        <title>AICC HACP debug log viewer: $CFG->dataroot<?php echo str_replace($CFG->dataroot, '', $logfile); ?></title>
+        <style>
+            html, body { margin:5; padding:5; }
+            html, body { height:100%; background-color:#fff; }
+            body {
+                font-family:Arial, Helvetica, Sans-Serif;
+                font-size:12px;
+                margin:0px 0px 0px 0px; padding:0px 0px 0px 0px;
+                background-color:#fff;
+            }
+            .even {background-color:#fff; }
+            .odd {background-color:#e8f2fe; }
+            .error {background-color:#fff; color:red; }
+            #aicclogbtns {
+                width: 90%;
+                text-align:left;
+                border:3px solid #ccc;
+                margin:10px;
+                padding:5px;
+            }
+            #aicclog {
+                width: 90%;
+                height: 80%;
+                overflow:auto;
+                text-align:left;
+                border:3px solid #ccc;
+                margin:10px;
+                padding:5px;
+            }
+        </style>        
+        <script type="text/javascript" src="../request.js"></script>
+        <script type="text/javascript">
+        //<![CDATA[
+            function updateBtns()
+            {
+                document.getElementById("btnupdate").disabled = run;
+                document.getElementById("btnstop").disabled = !run;
+            }
+            function start()
+            {
+                run = true;
+                // Refresh the buttons
+                updateBtns()
+                // Wait for logs
+                document.getElementById("aicclogloading").innerHTML = "Waiting for logs...";
+                document.getElementById("aicclogloading").style.display = "";
+                // Start updating...
+                update();
+            }
+            function stop()
+            {
+                run = false;
+                updateBtns();
+            }
+            function remove()
+            {
+                if (confirm("Remove the current debug log file in the server?")) {
+                    stop();
+                    if (doRemove()) {
+                        document.getElementById("aicclogdata").innerHTML = "";
+                        sClass = "even";
+                    } else {
+                        alert("Error! Unable to remove the debug log file");
+                    } 
+                    start();
+                }
+            }
+            function doRemove()
+            {
+                var result = false;
+                if (xhr != null) {
+                    xhr.open("GET", "<?php echo $CFG->wwwroot; ?>/mod/scorm/datamodels/aicc_debug.php?ajax=2", false);
+                    xhr.setRequestHeader("Content-Type", "text/plain");
+                    xhr.send(null);
+                    var result = ("true" == xhr.responseText ? true : false);
+                }
+                return result;
+            }
+            function update()
+            {
+                if (run && (xhr != null)) {
+                    xhr.open("GET", "<?php echo $CFG->wwwroot; ?>/mod/scorm/datamodels/aicc_debug.php?ajax=1", true);
+                    xhr.setRequestHeader("Content-Type", "text/plain");
+                    xhr.onreadystatechange = updateLog;
+                    xhr.send(null);
+                    // Start the timer: update the log console every 3 secs
+                    timer = setTimeout("update()", 3000);
+                }
+            }
+            function updateLog()
+            {
+                if (xhr.readyState == 4) { // Async load is completed
+                    if (xhr.status == 200) { // HTTP request => 200 status code
+                        objLoading = document.getElementById("aicclogloading");
+                        var text = xhr.responseText;
+                        if (text.length > 0) {
+                            // Got the valuable text
+                            objLoading.style.display = "none";
+                            // Apply the even/odd class to the logs
+                            var logs = xhr.responseText.split("\n");
+                            var html = ' ';
+                            for (i = 0; i < logs.length - 1; i++) {
+                                html += '<div class="' + sClass + '">';
+                                // Toggle style on timestamp match: YYYY/MM/DD hh:mm:ss <Timezone, as per PHP date('O')> DEBUG
+                                var reTimestamp = new RegExp("([0-9]){4}\\/([0-9]){2}\\/([0-9]){2}\\s([0-9]){2}:([0-9]){2}:([0-9]){2}\\s([0-9\-+]+)\\sDEBUG", "g");
+                                var foundTimestamp = reTimestamp.test(logs[i]);
+                                if (foundTimestamp) { // Toggle the class
+                                    sClass = (sClass == "even" ? "odd" : "even");
+                                    html += '</div><div class="' + sClass + '">';
+                                }
+                                // Write the log line
+                                html += logs[i] + "<br/>\n";
+                            }
+                            // Close the block
+                            html += logs[i] + "</div>\n";
+                            // Print out
+                            objPane = document.getElementById("aicclogdata");
+                            objPane.innerHTML += html;
+                            objPane = document.getElementById("aicclog");
+                            // New text added => Scroll to the bottom
+                            objPane.scrollTop = objPane.scrollHeight;
+                            objPane = null;
+                        } else {
+                            if (objLoading.style.display != "none") {
+                                objLoading.innerHTML += '.';
+                            }
+                        }
+                    } else if (xhr.status > 0) { // 0 means e.g.:
+                                                 // - cross domain conflict
+                                                 // - schemes w/o status code like file:/// or ftp://
+                                                 // - server timeouts
+                        alert("Error! Unable to update: XMLHttpRequest status is " + xhr.status);
+                    }
+                }
+            }
+        //]]>
+        </script>
+    </head>
+    <body>
+        <div name="aicclogbtns" id="aicclogbtns" class="aicclog">
+            Log viewer: <button id="btnupdate"  onclick="start();">Start</button>
+                        <button id="btnstop"    onclick="stop();">Stop</button>
+                        <button id="btnrefresh" onclick="window.location.reload();">Refresh</button>
+                        <button id="btnremove"  onclick="remove();">Delete...</button>
+        </div>
+        <div name="aicclog" id="aicclog" class="aicclog">
+            <div name="aicclogloading" id="aicclogloading"></div>
+            <div name="aicclogdata" id="aicclogdata"></div>
+        </div>
+        <script type="text/javascript">
+        //<![CDATA[
+            var run = false;
+            var sClass = "even";
+            var xhr = NewHttpReq();
+            if (xhr == null) {
+                alert("Error! Unable to create the XMLHttpRequest object");
+            } else {
+                start();
+            }    
+        //]]>
+        </script>
+    </body>
+</html>
+        


