process_completed_test_user_data.php 13.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289
<?php
/* --------------------------------------------------------------------------------------------------------------
 * Copyright (C) 2018 by William Paul Liggett (junktext@junktext.com)
 * This Source Code Form is subject to the terms of the Mozilla Public License (MPL), v. 2.0.
 * If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
 *
 * Filename:  process_completed_test_user_data.php
 * 
 * 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.
 * 
 * Output:    Two separate CSV files named in the manner of:
 *              ov_subject_12_condition_4_on_20180225_2219_raw_1_random_letter_times.csv
 *              ov_subject_12_condition_4_on_20180225_2219_raw_2_user_activity.csv
 * 
 * Details:   The filenames have the "raw" wording because they will be used to create a single, collated CSV
 *            file that attempts to bring together a reliable report. However, since the data is showing when the
 *            user pressed the space bar as the random letters were displayed briefly, there might be false
 *            positives such as if the user pressed the space bar 20 ms into a letter being shown. So, they
 *            simply got it right by accident. Though, it's not clear what determines a false positive so this
 *            is why the design features two raw CSV files that can be used to easily re-generate a single, 
 *            collated CSV report at a later time.
 * -------------------------------------------------------------------------------------------------------------- */

// 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

// 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 "../../../../protected_site_configs/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.       ++++
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

// -----------------------------------------------------------------------------------------------------------------------
// Random Letter Log: Details when each random letter was presented. No user activity is recorded.
// -----------------------------------------------------------------------------------------------------------------------
if(isset($_POST['ov_random_letter_log'])) {
    // JS string. Unfortunately, this is not a direct JS array like the `ov_user_activity_log'.
    // 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,
    // such that with this setting at 1000 the `ov_random_letter_log' gets cut off on key 166 and, likewise, with
    // `max_input_var' set at 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 string is sanitized later.
    $string_ov_random_letter_log = $_POST['ov_random_letter_log'];
    
    // Breaks the string into a single-dimension array.
    $flat_ov_random_letter_log = explode(",", $string_ov_random_letter_log);
    
    // Builds up a two-dimensional array like it was stored in JS.
    $ov_random_letter_log = array();
    $temp_array = array();
    $column = 0;
    for($i = 0; $i < count($flat_ov_random_letter_log); $i++) {
        array_push($temp_array, $flat_ov_random_letter_log[$i]);
        if($column < 5) {
            $column++;
        }
        else {
            array_push($ov_random_letter_log, $temp_array);
            $temp_array = array();
            $column = 0;
        }
    }

    // Sanitizes the JS array.
    foreach($ov_random_letter_log as $key => &$value) {
        // Title heading should only have: "GlobalTrialCounter", "CurrentTrial", "TimeStamp", "Type", "Block", and "ov_random_letter_is_o".
        if($key === 0) {
            if(
                ($value[0] !== "GlobalTrialCounter") && 
                ($value[1] !== "CurrentTrial") &&
                ($value[2] !== "TimeStamp") &&
                ($value[3] !== "Type") &&
                ($value[4] !== "Block") &&
                ($value[5] !== "ov_random_letter_is_o") 
            ) {
                output_json_response(false, "Bad title headings for ov_random_letter_log at key: " . intval($key) . ", value (full row): " . var_export($value, true));
                return;
            } 

            // The title headings were validated. Skip to the next row in the array.
            else {
                continue;
            }
        }

        // Safely converts any data submitted to only interval numbers.
        $value[0] = intval($value[0]);  // GlobalTrialCounter
        $value[1] = intval($value[1]);  // CurrentTrial
        $value[2] = intval($value[2]);  // TimeStamp in Unix time (ms)
        
        // Type: Must be either "P" or "E".
        $value[3] = htmlspecialchars($value[3], ENT_QUOTES);
        if(($value[3] !== "P") && ($value[3] !== "E")) {
            output_json_response(false, "Invalid 'Type' data for ov_random_letter_log at key: " . intval($key) . ", value (full row): " . var_export($value, true));
            return;
        }
        
        $value[4] = intval($value[4]);  // Block
        
        // ov_random_letter_is_o: Must be either 0 or 1.
        $value[5] = intval($value[5]);
        if(($value[5] !== 0) && ($value[5] !== 1)) {
            output_json_response(false, "Invalid 'ov_random_letter_is_o' data for ov_random_letter_log at key: " . intval($key) . ", value (full row): " . var_export($value, true));
            return;
        }
    }

    // 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_1_random_letter_times.csv";
    
    // Creates the CSV file and stores it on the web server.
    try {
        $new_csv_file = @fopen($filepath . $filename, "w");
        
        // Checks whether the file pointer is valid.
        if(!$new_csv_file) {
            throw new Exception();
        }

        foreach($ov_random_letter_log as $csv_data) {
            fputcsv($new_csv_file, $csv_data);
        }

        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);
    }
    
    // Something went wrong with the new CSV file.
    catch (\Exception $e) {
        output_json_response(false, "Exception: Cannot create a new CSV file for some reason.");
        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'])) {
    // JS array that is stored in a CSV-like manner. Title headings = strings, Rows = integers.
    $ov_user_activity_log = $_POST['ov_user_activity_log'];

    // Sanitizes the JS array.
    foreach($ov_user_activity_log as $key => &$value) {
        // Title heading should only have: "GlobalTrialCounter" and "ov_participant_response_time".
        if($key === 0) {
            if(($value[0] !== "GlobalTrialCounter") && ($value[1] !== "ov_participant_response_time")) {
                output_json_response(false, "Bad title headings for ov_user_activity_log at key: " . intval($key) . ", value (full row): " . var_export($value, true));
                return;
            } 

            // The title headings were validated. Skip to the next row in the array.
            else {
                continue;
            }
        }

        // Safely converts any data submitted to only interval numbers.
        $value[0] = intval($value[0]);  // GlobalTrialCounter
        $value[1] = intval($value[1]);  // ov_participant_response_time in Unix time (ms)
    }

    // 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";
    
    // Creates the CSV file and stores it on the web server.
    try {
        $new_csv_file = @fopen($filepath . $filename, "w");
        
        // Checks whether the file pointer is valid.
        if(!$new_csv_file) {
            throw new Exception();
        }

        foreach($ov_user_activity_log as $csv_data) {
            fputcsv($new_csv_file, $csv_data);
        }

        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);
    }
    
    // Something went wrong with the new CSV file.
    catch (\Exception $e) {
        output_json_response(false, "Exception: Cannot create a new CSV file for some reason.");
        return;
    }

    // Reports that the CSV file was created successfully.
    output_json_response(true, "");
    return;
}