Commit c37a5217 authored by William Paul Liggett's avatar William Paul Liggett

CSV Reports: The collated CSV is now automatically created after a participant...

CSV Reports: The collated CSV is now automatically created after a participant completes a test. See the Change Log for more changes.
parent b5d72e79
......@@ -11,81 +11,17 @@
* fast they reacted to a random letter that was displayed to them and whether they responded to the
* correct "O" letter.
*
* Input: One array that contains two CSV filenames as strings. The files must be named in a certain format,
* otherwise, nothing will happen.
* Input: Two raw CSV filenames as strings. The files must be named in a certain format, otherwise nothing
* will happen.
*
* Output: A new CSV file titled in the manner of:
* Output: A new collated CSV file titled in the manner of:
* ov_subject_14_condition_3_on_20180330_1340_EDT_collated_file_report.csv
* -------------------------------------------------------------------------------------------------------------- */
// TODO: THE LOGIN CODE IS CURRENTLY DUPLICATED WITH THE `process_completed_test_user_data.php' FILE. I'LL FIX THIS LATER.
//
// Enables user sessions.
session_start();
// Function: output_json_response()
require_once "output_json_response.php";
// Ensures the user submitting their test data has a valid account.
if(isset($_POST['ov_user_account'])) {
$ov_user_account = $_POST['ov_user_account'];
// Sanitizes the JS array.
$ov_user_account[0] = intval($ov_user_account[0]); // JS: var ov_database_user_sk;
$ov_user_account[1] = intval($ov_user_account[1]); // JS: var ov_subject_id;
$ov_user_account[2] = intval($ov_user_account[2]); // JS: var ov_test_condition;
$ov_user_account[3] = htmlspecialchars($ov_user_account[3], ENT_QUOTES, "UTF-8"); // JS: var ov_login_code;
$submitted_username = $ov_user_account[1];
$submitted_password = $ov_user_account[3];
// Check to see if the PHP session timed out and reopen the session for an authorized user.
if(!isset($_SESSION['ov_database_user_sk'])) {
// Logs into the OpenVigilance Task tests database to verify the person is allowed to take a test.
// `$pdo' is defined as the database connection.
require_once "../settings.php";
require_once "../" . PROTECTED_SITE_CONFIGS_DIR . "/openvigilance_db_connection_user.php";
// Confirms whether the login is valid and sets the boolean `$valid_login_user' variable.
// Also, the user's full account details acquired from the database are stored in `$account_found'.
require_once "valid_login_user.php";
// Login invalid
if(!$valid_login_user) {
output_json_response(false, "User login invalid.");
return;
}
// Login valid! Reopens the PHP session.
$_SESSION['ov_database_user_sk'] = intval($account_found['sk']);
$_SESSION['ov_user'] = $submitted_username;
$_SESSION['ov_test_condition'] = intval($account_found['test_condition']);
$_SESSION['ov_login_code'] = $submitted_password;
// Informs the web browser that the user has logged in successfully.
output_json_response(true, "");
}
else {
// Informs the web browser that the user has logged in successfully.
output_json_response(true, "");
}
}
// The user should've been logged in by now. If not, then I won't allow strangers to post strange data.
if(!isset($_SESSION['ov_database_user_sk'])) {
output_json_response(false, "The user must be logged in before submitting test data.");
return;
}
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// ++++ Authenticated user! We'll go ahead and attempt to create the necessary CSV files on the server. ++++
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
if(isset($_POST['csv_filenames'])) {
function generate_collated_csv_file_report($filename_raw_1, $filename_raw_2) {
// Sanitizes the filename strings provided as input.
$raw1_csv_filename_random_letter_times = htmlspecialchars($_POST['csv_filenames'][0], ENT_QUOTES, "UTF-8");
$raw2_csv_filename_user_activity = htmlspecialchars($_POST['csv_filenames'][1], ENT_QUOTES, "UTF-8");
$raw1_csv_filename_random_letter_times = htmlspecialchars($filename_raw_1, ENT_QUOTES, "UTF-8");
$raw2_csv_filename_user_activity = htmlspecialchars($filename_raw_2, ENT_QUOTES, "UTF-8");
// Sanitizes the filenames even more by ensuring they MUST match the format in a manner similar to:
// 1st CSV = "ov_subject_14_condition_3_on_20180330_1340_EDT_raw_1_random_letter_times.csv"
......@@ -189,8 +125,7 @@ if(isset($_POST['csv_filenames'])) {
default:
// We'll assume there won't need to be more than 99,999 subject IDs.
output_json_response(false, "The `Subject ID' is larger than 99,999 or the filename is invalid.");
return;
return "Error: The `Subject ID' is larger than 99,999 or the filename is invalid.";
}
// Actually stores snippets of each filename as individual portions to analyze later.
......@@ -290,14 +225,13 @@ if(isset($_POST['csv_filenames'])) {
// Bad filenames! So, we won't do anything with them.
if(!$filenames_passes_sanitation_test) {
output_json_response(false, "The filename for either the 1st or 2nd CSV file did not pass the validation rules.");
return;
return "Error: The filename for either the 1st or 2nd CSV file did not pass the validation rules.";
}
// Good filenames! Let's move on.
else {
try {
$filepath = "../user-data-files/";
$filepath = "../user-data-files/raw-input/";
// Tries to open the raw CSV files in read-only mode.
$csv_file_1 = @fopen($filepath . $raw1_csv_filename_random_letter_times, "r");
......@@ -497,13 +431,11 @@ if(isset($_POST['csv_filenames'])) {
chmod($filepath . $filename, 0444);
// Success!
output_json_response(true, "New collated CSV file created!!!", var_export($php_array_collated, true));
return;
return true;
}
catch(\Exception $e) {
output_json_response(false, $e->getMessage());
return;
return $e->getMessage();
}
}
}
\ No newline at end of file
......@@ -9,7 +9,7 @@
* Purpose: Processes a user's activity data after a vigilance test has been completed.
* The page is meant to be used as a simple web service, such as with Ajax or the like.
*
* Input: The JS arrays named `ov_random_letter_log' and `ov_user_activity_log' with actual data inside.
* Input: One JS array with the `ov_random_letter_log' and `ov_user_activity_log' data inside.
*
* Output: Two separate CSV files named in the manner of:
* ov_subject_12_condition_4_on_20180225_2219_raw_1_random_letter_times.csv
......@@ -25,13 +25,8 @@
* -------------------------------------------------------------------------------------------------------------- */
// The file is used in the manner of:
// 1st CSV file...
// * Outer-Ajax: Log-in user via JS vars
// ** Inner-Ajax: Submit the Random Letter Log
//
// 2nd CSV file...
// * Outer-Ajax: Log-in user via JS vars
// ** Inner-Ajax: Submit the User Activity Log
// 1. Outer-Ajax: Log-in user via JS vars
// 2. Inner-Ajax: Submit the Random Letter Log and the User Activity Log
// Enables user sessions.
session_start();
......@@ -39,6 +34,9 @@ session_start();
// Function: output_json_response()
require_once "output_json_response.php";
// Function: generate_collated_csv_file_report()
require_once "generate_collated_csv_file_report.php";
// Ensures the user submitting their test data has a valid account.
if(isset($_POST['ov_user_account'])) {
$ov_user_account = $_POST['ov_user_account'];
......@@ -98,14 +96,14 @@ if(!isset($_SESSION['ov_database_user_sk'])) {
// -----------------------------------------------------------------------------------------------------------------------
// Random Letter Log: Details when each random letter was presented. No user activity is recorded.
// -----------------------------------------------------------------------------------------------------------------------
if(isset($_POST['ov_random_letter_log'])) {
if(isset($_POST['ov_raw_input_data'])) {
// JS string. Unfortunately, this is not a direct JS array as originally built by the client.
// The reason for this is that there is so much data created with the random letters that submitting it
// directly as a JS array for standard processing has the side-effect of breaching PHP's `max_input_var' setting.
// For instance with max_input_vars=1000 the `ov_random_letter_log' gets cut off on key 166 and, likewise, with
// max_input_var=5000, the `ov_random_letter_log' gets cut off at key 833 of 1531 (from a full 12+12=24 min OV test).
// Note: The $_POST string is sanitized later.
$string_ov_random_letter_log = $_POST['ov_random_letter_log'];
$string_ov_random_letter_log = $_POST['ov_raw_input_data'][0];
// Breaks the string into a single-dimension array.
$flat_array_ov_random_letter_log = explode(",", $string_ov_random_letter_log);
......@@ -155,6 +153,12 @@ if(isset($_POST['ov_random_letter_log'])) {
$value[1] = intval($value[1]); // CurrentTrial
$value[2] = intval($value[2]); // TimeStamp in Unix time (ms)
// Used for naming both of the raw CSV data files.
if($key === 1) {
// This is the datetime in ms since 1970, from when the 1st random letter was shown in the practice test.
$datetime_since_1970 = $value[2];
}
// Type: Must be either "P" or "E".
$value[3] = htmlspecialchars($value[3], ENT_QUOTES);
if(($value[3] !== "P") && ($value[3] !== "E")) {
......@@ -179,20 +183,21 @@ if(isset($_POST['ov_random_letter_log'])) {
// Sets up the filename to be used for the CSV.
date_default_timezone_set("America/New_York");
$today = date("Ymd"); // Date Format: YYYYMMDD with leading zeroes (e.g., 20180302)
$time = date("Hi"); // Time Format: HHMM with leading zeroes (e.g., 1846 or 0908)
// Date/Time Format: YYYYMMDD_HHMM with leading zeroes (e.g., 20180302_0908)
$datetime = date("Ymd_Hi", substr($datetime_since_1970, 0, 10));
$tz_abbr = date("T"); // Abbreviated timezone, such as: EST
// File path:
$filepath = "../user-data-files/";
$filepath = "../user-data-files/raw-input/";
// Filename format example: ov_subject_12_condition_4_on_20180225_2219_EST_raw_2_user_activity.csv
$filename = "ov_subject_" . strval($_SESSION['ov_user']) . "_condition_" . strval($_SESSION['ov_test_condition']) .
"_on_" . $today . "_" . $time . "_" . $tz_abbr . "_raw_1_random_letter_times.csv";
// Filename format example: ov_subject_12_condition_4_on_20180225_2219_EST_raw_1_random_letter_times.csv
$filename_raw_1 = "ov_subject_" . strval($_SESSION['ov_user']) . "_condition_" . strval($_SESSION['ov_test_condition']) .
"_on_" . $datetime . "_" . $tz_abbr . "_raw_1_random_letter_times.csv";
// Creates the CSV file and stores it on the web server.
try {
$new_csv_file = @fopen($filepath . $filename, "w");
$new_csv_file = @fopen($filepath . $filename_raw_1, "w");
// Checks whether the file pointer is valid.
if(!$new_csv_file) {
......@@ -206,7 +211,7 @@ if(isset($_POST['ov_random_letter_log'])) {
fclose($new_csv_file);
// Sets the file to be read-only to indicate that no data should ever be modified directly later on!
chmod($filepath . $filename, 0444);
chmod($filepath . $filename_raw_1, 0444);
}
// Something went wrong with the new CSV file.
......@@ -215,20 +220,14 @@ if(isset($_POST['ov_random_letter_log'])) {
return;
}
// Reports that the CSV file was created successfully.
output_json_response(true, "");
return;
}
// -----------------------------------------------------------------------------------------------------------------------
// User Activity Log: Documents when the user pressed the space bar.
// -----------------------------------------------------------------------------------------------------------------------
if(isset($_POST['ov_user_activity_log'])) {
// -----------------------------------------------------------------------------------------------------------------------
// User Activity Log: Documents when the user pressed the space bar.
// -----------------------------------------------------------------------------------------------------------------------
// JS string. Unfortunately, this is not a direct JS array as originally built by the client.
// The reason for this is that there is so much data created with the user that submitting it
// directly as a JS array for standard processing has the side-effect of breaching PHP's `max_input_var' setting.
// Note: The $_POST string is sanitized later.
$string_ov_user_activity_log = $_POST['ov_user_activity_log'];
$string_ov_user_activity_log = $_POST['ov_raw_input_data'][1];
// Breaks the string into a single-dimension array.
$flat_array_ov_user_activity_log = explode(",", $string_ov_user_activity_log);
......@@ -273,22 +272,14 @@ if(isset($_POST['ov_user_activity_log'])) {
// Recommended by the PHP Manual
unset($value);
// Sets up the filename to be used for the CSV.
date_default_timezone_set("America/New_York");
$today = date("Ymd"); // Date Format: YYYYMMDD with leading zeroes (e.g., 20180302)
$time = date("Hi"); // Time Format: HHMM with leading zeroes (e.g., 1846 or 0908)
$tz_abbr = date("T"); // Abbreviated timezone, such as: EST
// File path:
$filepath = "../user-data-files/";
// Filename format example: ov_subject_12_condition_4_on_20180225_2219_EST_raw_2_user_activity.csv
$filename = "ov_subject_" . strval($_SESSION['ov_user']) . "_condition_" . strval($_SESSION['ov_test_condition']) .
"_on_" . $today . "_" . $time . "_" . $tz_abbr . "_raw_2_user_activity.csv";
// The `$datetime' and `$tz_abbr' was set previously when processing the 1st raw file (random letter times).
$filename_raw_2 = "ov_subject_" . strval($_SESSION['ov_user']) . "_condition_" . strval($_SESSION['ov_test_condition']) .
"_on_" . $datetime . "_" . $tz_abbr . "_raw_2_user_activity.csv";
// Creates the CSV file and stores it on the web server.
try {
$new_csv_file = @fopen($filepath . $filename, "w");
$new_csv_file = @fopen($filepath . $filename_raw_2, "w");
// Checks whether the file pointer is valid.
if(!$new_csv_file) {
......@@ -302,7 +293,7 @@ if(isset($_POST['ov_user_activity_log'])) {
fclose($new_csv_file);
// Sets the file to be read-only to indicate that no data should ever be modified directly later on!
chmod($filepath . $filename, 0444);
chmod($filepath . $filename_raw_2, 0444);
}
// Something went wrong with the new CSV file.
......@@ -311,7 +302,17 @@ if(isset($_POST['ov_user_activity_log'])) {
return;
}
// Reports that the CSV file was created successfully.
output_json_response(true, "");
return;
$collated_csv_creation_msg = generate_collated_csv_file_report($filename_raw_1, $filename_raw_2);
if($collated_csv_creation_msg === true) {
// Reports that all of the CSV files were created successfully. Raw 1, Raw 2, and the Collated CSV.
output_json_response(true, "");
return;
}
else {
// There was some problem creating all of the CSV files, especially with the collated CSV.
output_json_response(false, $collated_csv_creation_msg);
return;
}
}
......@@ -38,17 +38,33 @@
<p class="version_info"><strong>TO DO:</strong></p>
<ol>
<li>Collated CSV Reports: Embed the collated CSV generator function into the process to create a new report
automatically after each test is concluded. [Estimate: 1 hours remaining]
</li>
<li>OPTIONAL -- Download CSV Files: All of the user data CSV files are stored under the 'user-data-files' directory,
beneath the main OpenVigilance Task URL. At present, this is nothing fancy, as it just contains a link to each
CSV file. However, I could create the ability to download all of the files as a ZIP archive if desired.
CSV file. However, I could create the ability to download all of the files as a ZIP archive if desired. Or I
could at least create a less-annoying version of how the download links are displayed, since the sorting order
is a bit confusing as it uses the web server's default order (i.e., 10, 11, 12, ... 1, 20, 21, 22, ... 2, etc).
[Estimate: 2-4 hours]
</li>
</ol>
<p class="version_info">Version 1.10.2 (2018-04-07):</p>
<p><em>Estimated labor time: <span id="hours_logged_for_update_24">2.5</span> hours.</em></p>
<ul>
<li>
CSV Reports: The collated CSV is now automatically created after a participant completes a test. Also, the
date/time stamp on the raw & collated CSV files are now set to when the participant clicks on the "Start Test"
button (after they login) instead of when they submitted the test to the server. Extensive system tests were
conducted to ensure the flow still works accurately.
</li>
<li>
Older CSV Reports: The date/time stamps on all of the older tests that were already taken by participants were
changed to also show when they actually began the test.
</li>
<li>
Refactored some of the code to reduce the number of Ajax calls and I de-duplicated an authentication process.
</li>
</ul>
<p class="version_info">Version 1.10.1 (2018-04-05):</p>
<p><em>Estimated labor time: <span id="hours_logged_for_update_23">1</span> hours.</em></p>
<ul>
......@@ -333,7 +349,8 @@
parseFloat($("#hours_logged_for_update_20").html()) +
parseFloat($("#hours_logged_for_update_21").html()) +
parseFloat($("#hours_logged_for_update_22").html()) +
parseFloat($("#hours_logged_for_update_23").html());
parseFloat($("#hours_logged_for_update_23").html()) +
parseFloat($("#hours_logged_for_update_24").html());
// Displays the total labor hours at the top of the page.
$("#total_labor_hours").html(calculated_labor_time);
......
......@@ -31,9 +31,6 @@ var ov_random_letter_log = [["GlobalTrialCounter", "CurrentTrial", "TimeStamp",
// this rate within their profile on their OS. It's just a setting in their control panel.
var ov_user_activity_log = [["GlobalTrialCounter", "ov_participant_response_time"]];
// Records the datetime in ms since the Unix epoch. It'll be set once the test actually begins.
var ov_test_began_datetime = -1;
// Tracks whether it is a practice test ("P") or an actual experiment ("E").
var ov_test_type = "";
......@@ -349,6 +346,11 @@ var ov_participant_response_time = -1;
// The JS vars were pre-set by PHP already as embedded source code for the user's visit.
var ov_user_account = [ov_database_user_sk, ov_subject_id, ov_test_condition, ov_login_code];
// The raw input data contains both the random letter times and when the user responded. Each data portion has been
// converted from an array to a flattened string to avoid reaching PHP `max_input_vars' setting. In this case, the
// single array of two strings will only take up 2 input vars!
var ov_raw_input_data = [ov_random_letter_log.toString(), ov_user_activity_log.toString()];
// Internal function only used if there was an Ajax failure for some reason.
function report_ajax_error(detailed_error_message = "") {
ajax_any_error_whatsoever = true;
......@@ -395,22 +397,21 @@ var ov_participant_response_time = -1;
throw new Error("An Ajax error occurred at calling location:" + detailed_error_message);
}
// Random Letter Log: Confirms the user is logged in and then submits the `ov_random_letter_log' array.
// Confirms the user is logged in and then submits the raw input data that was collected.
$.ajax({
type: "POST",
url: "admin/process_completed_test_user_data.php",
data: {ov_user_account: ov_user_account},
success: function(response) {
ajax_sequence_response_trail += "Random Letter -- 1st Ajax Response: " + response + "<br /><br />";
ajax_sequence_response_trail += "1st Ajax Response: " + response + "<br /><br />";
// Submits the JS array `ov_user_activity_log' to the server.
$.ajax({
type: "POST",
url: "admin/process_completed_test_user_data.php",
data: {ov_random_letter_log: ov_random_letter_log.toString() }, // Flattens the two-dimensional array into one really long string.
data: {ov_raw_input_data: ov_raw_input_data },
success: function(response, textStatus, jqXHR) {
ajax_sequence_response_trail += "Random Letter -- 2nd Ajax Response: " + response + "<br /><br />";
ajax_sequence_response_trail += "2nd Ajax Response: " + response + "<br /><br />";
var response_as_json = JSON.parse(response);
......@@ -436,46 +437,6 @@ var ov_participant_response_time = -1;
}
});
// User Activity Log: Confirms the user is logged in and then submits the `ov_user_activity_log' array.
$.ajax({
type: "POST",
url: "admin/process_completed_test_user_data.php",
data: {ov_user_account: ov_user_account},
success: function(response) {
ajax_sequence_response_trail += "User Activity -- 1st Ajax Response: " + response + "<br /><br />";
// Submits the JS array `ov_user_activity_log' to the server.
$.ajax({
type: "POST",
url: "admin/process_completed_test_user_data.php",
data: {ov_user_activity_log: ov_user_activity_log.toString() }, // Flattens the two-dimensional array into one really long string.
success: function(response, textStatus, jqXHR) {
ajax_sequence_response_trail += "User Activity -- 2nd Ajax Response: " + response + "<br /><br />";
var response_as_json = JSON.parse(response);
if(response_as_json.successful !== true) {
report_ajax_error("OV Error Code: #AJ004<br />jqXHR: " + jqXHR.toString() +
"<br />textStatus: " + textStatus + "<br />JSON response: " + response +
ajax_sequence_response_trail);
}
},
error: function(jqXHR, textStatus, errorThrown) {
report_ajax_error("OV Error Code #AJ005\njqXHR: " + jqXHR.toString() +
"<br />textStatus: " + textStatus + "<br />errorThrown: " + errorThrown +
ajax_sequence_response_trail);
}
});
},
error: function(jqXHR, textStatus, errorThrown) {
report_ajax_error("OV Error Code #AJ006<br />jqXHR: " + jqXHR.toString() +
"<br />textStatus: " + textStatus + "<br />errorThrown: " + errorThrown +
ajax_sequence_response_trail);
}
});
// Rarely needed, but essential if something goes weird with submitting the data to the server via Ajax.
// Ensures that if an Ajax error occurred, that we don't accidentally report that the CSV files were created correctly.
if((ajax_any_error_whatsoever) && $("#vigilance_display_banner_text").html() !== "Network Failure") {
......@@ -605,9 +566,6 @@ var ov_participant_response_time = -1;
$("#h1_page_title").remove();
$("#login_welcome_msg").remove();
$("#StartTestButton").remove();
// Records when the time actually began the test. Time in ms since the Unix epoch.
ov_test_began_datetime = Date.now();
var thirty_seconds_in_ms = parseFloat(30000); // 30 seconds = 30,000 ms [Used between the practice and the real test.]
var practice_time_in_ms = thirty_seconds_in_ms; // Practice Test is only 30 seconds for now.
......@@ -648,7 +606,7 @@ var ov_participant_response_time = -1;
if(ov_test_condition === 1) {
// No break for the participant. Concludes the test once 24 minutes have passed.
setTimeout(concludeTest, (practice_time_in_ms + thirty_seconds_in_ms + twelve_minutes_in_ms * 2));
}
}
else if(ov_test_condition === 2) {
// Hides the initial OV display after 12 minutes to show a blank screen.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment