function authorizeApp($authenticationInformation)
{
    /***
     * Primary wrapper function for everything not directly login / auth
     * related.
     *
     * @param array $authenticationInformation
     *
     * $authenticationInformation should be an array containing the
     * following keys:
     *
     * @key "auth" - the SHA1 hash of "entropy", server secret key, "action", "app_version"
     * @key "key"  - The encryption key to decrypt the server secret key
     * @key "app_version" - the application version number
     * @key "entropy" - A randomly generated string of 16 characters
     * @key "action" - The action to be executed
     * @key "data" - A base64-encoded JSON object with structured data for
     * the action
     * @key "user_id" - the dblink for the user. This will always be
     * appended to the values in the "data" key.
     ***/
    if (is_array($authenticationInformation)) {
        $auth = $authenticationInformation['auth'];
        $auth_key = $authenticationInformation['key'];
        $version = $authenticationInformation['app_version'];
        $entropy = $authenticationInformation['entropy'];
        $action = $authenticationInformation['action'];
        $user = $authenticationInformation['user_id'];
        $device = $authenticationInformation['device_identifier'];
        $action_data = smart_decode64($authenticationInformation['data']);
        if (!is_array($action_data)) {
            returnAjax(array('status' => false, 'human_error' => 'The application and server could not communicate. Please contact support.', 'error' => 'Invalid data object', 'app_error_code' => 101));
        } else {
            # Check structure of variables
            try {
                # Reserved for specific data-type checking
            } catch (Exception $e) {
                returnAjax(array('status' => false, 'human_error' => 'The application and server could not communicate. Please contact support.', 'error' => $e->getMessage(), 'app_error_code' => 108));
            }
            # Save variables to be used later
            $action_data['dblink'] = $user;
            $app_verify = array('device' => $device, 'authorization_token' => $auth, 'auth_prepend' => $entropy, 'auth_postpend' => $action . $version, 'appsecret_key' => $auth_key, 'dblink' => $user);
            $action_data['application_verification'] = $app_verify;
            $u = new UserFunctions($user);
        }
    } else {
        returnAjax(array('status' => false, 'human_error' => 'The application and server could not communicate. Please contact support.', 'error' => 'Invalid request', 'app_error_code' => 102));
    }
    /***
     * See if the action is a valid action.
     * Most of these are just going to be wrappers for the
     * async_login_handler functions.
     ***/
    if (empty($action)) {
        $action = 'sync';
    }
    $action_function_map = array('save' => 'saveToUser', 'read' => 'getFromUser', 'sync' => 'syncUserData');
    if (!array_key_exists($action, $action_function_map)) {
        returnAjax(array('status' => false, 'human_error' => 'The application and server could not communicate. Please contact support.', 'error' => 'Invalid action', 'app_error_code' => 103));
    }
    # See if the user exists
    # get the key for $user from the server
    /***
     * Now, we want to authenticate the app against this information.
     * $auth should be the SHA1 hash of:
     *
     * $auth = sha1($entropy.$SERVER_KEY.$action.$version)
     *
     * If it isn't, the request is bad.
     ***/
    $r = $u->verifyApp($app_verify);
    if (!$r['status']) {
        returnAjax(array('status' => false, 'human_error' => "This app isn't authorized. Please log out and log back in.", 'error' => 'Invalid app credentials', 'app_error_code' => 106));
    }
    # Call the $action
    $action_data['user_data'] = $r['data'];
    $action_result = $action_function_map[$action]($action_data);
    $action_result['elapsed'] = elapsed();
    returnAjax($action_result);
}
    header('Content-type: application/json');
    $json = json_encode($data, JSON_FORCE_OBJECT);
    $replace_array = array(""", """);
    print str_replace($replace_array, "\\\"", $json);
    exit;
}
$provider = isset($_REQUEST['provider']) ? strtolower($_REQUEST['provider']) : null;
switch ($provider) {
    case "google":
        returnAjax(authGoogle($_REQUEST));
        break;
    case "test":
        returnAjax(array("computed" => computeUserPassword($_REQUEST["q"]), "encoded" => base64_encode($_REQUEST["q"] . $your_secret . ($your_secret | $_REQUEST["q"]))));
        break;
    default:
        returnAjax(array("status" => false, "error" => "Invalid provider"));
}
function authGoogle($get)
{
    require_once 'lib/vendor/autoload.php';
    # require_once 'lib/google-php-client/vendor/autoload.php';
    global $google_clientid, $google_secret, $google_config_file_path;
    /*************************************************
     * Ensure you've downloaded your oauth credentials
     ************************************************/
    if (!file_exists($google_config_file_path)) {
        return array("status" => false, "error" => "Bad config file path");
    }
    /************************************************
     * NOTICE:
     * The redirect URI is to the current page, e.g:
    $uploadStatus['thumb_path'] = $resizeStatus['output'];
    $uploadStatus["wrote_thumb"] = str_replace("uploaded/","",$resizeStatus["output"]);
    $uploadStatus['mime_provided'] = $passed_mime;
    return $uploadStatus;
}

/***************
 * Actual script
 ***************/

if (isset($_SERVER['QUERY_STRING'])) {
    parse_str($_SERVER['QUERY_STRING'], $_REQUEST);
}
$do = isset($_REQUEST['do']) ? strtolower($_REQUEST['do']) : null;

# Check the cases ....

switch ($do) {
# Extend other switches in here as cases ...
case 'upload_file':
    returnAjax(handleUpload());
    break;
case 'upload_image':
    returnAjax(doUploadImage());
    break;
default:
    $default_answer = array('status' => false, 'error' => 'Invalid action', 'human_error' => 'No valid action was supplied.');
    # doUploadImage()
    returnAjax($default_answer);
}
    $sheets = $_REQUEST['sheets'];
    $sheets_arr = explode(',', $sheets);
    if (sizeof($sheets_arr) > 1) {
        # PHPExcel only wants array for many sheets
        $sheets = $sheets_arr;
    }
    try {
        returnAjax(array(
            'status' => true,
            'data' => excelToArray($validatedPath, $header, $sheets),
            'path' => array(
                'requested_path' => $_REQUEST['path'],
                'validated_path' => $validatedPath,
            ),
        ));
    } catch (Exception $e) {
        returnAjax(array(
            'status' => false,
            'error' => $e->getMessage(),
        ));
    }
    break;
default:
    returnAjax(array(
        'status' => false,
        'error' => "Invalid action (got '$do')",
        'args' => $_REQUEST,
        'human_error' => "The server recieved an instruction it didn't understand. Please try again.",
    ));
}
function doAsyncLogin($get)
{
    $u = new UserFunctions();
    $totp = empty($get["totp"]) ? false : $get["totp"];
    $r = $u->lookupUser($get["username"], $get["password"], true, $totp);
    if ($r["status"] === true) {
        $return = $u->createCookieTokens($r["data"]);
        unset($return["source"]);
        unset($return["raw_cookie"]);
        unset($return["basis"]);
    } else {
        $return = $r;
    }
    returnAjax($return);
}
function validateCaptcha($get)
{
    global $recaptcha_private_key;
    $params = array('secret' => $recaptcha_private_key, 'response' => $get['recaptcha_response']);
    $raw_response = do_post_request('https://www.google.com/recaptcha/api/siteverify', $params);
    $response = json_decode($raw_response, true);
    if ($response['success'] === false) {
        switch ($response['error-codes'][0]) {
            case 'invalid-input-response':
                $parsed_error = 'Invalid CAPTCHA. Please retry it.';
            case 'missing-input-response':
                $parsed_error = 'Please be sure to solve the CAPTCHA.';
            default:
                $parsed_error = 'There was a problem with your CAPTCHA. Please try again.';
        }
        $a = array('status' => false, 'error' => 'Bad CAPTCHA', 'human_error' => $parsed_error, 'recaptcha_response' => array('raw_response' => $raw_response, 'parsed_response' => $response));
    } else {
        if (!empty($get["project"])) {
            global $db;
            $project = $db->sanitize($get['project']);
            $query = array('project_id' => $project);
            $resultCols = array("author_data", "technical_contact", "technical_contact_email");
            $result = $db->getQueryResults($query, $resultCols, 'AND', false, true);
            $author_data = json_decode($result[0]['author_data'], true);
            $a = array('status' => true, 'author_data' => $author_data, "technical" => array("name" => $result[0]["technical_contact"], "email" => $result[0]["technical_contact_email"]), 'raw_result' => $result[0]);
        }
        if (!empty($get["user"])) {
            global $udb;
            $viewUser = $udb->sanitize($get["user"]);
            $cols = array("username", "phone", "alternate_email", "public_profile");
            $query = array("dblink" => $viewUser);
            $result = $udb->getQueryResults($query, $cols, "OR", false, true);
            if (empty($result)) {
                $response = $udb->getQueryResults($query, $cols, "OR", false, true, false, true);
            } else {
                $response = $result[0];
                $response["public_profile"] = json_decode($response["public_profile"], true);
            }
            $a = array("status" => true, "response" => $response, "raw_result" => $result, "query" => $get);
        }
    }
    returnAjax($a);
}
    $login_status["human_error"] = "You're not logged in as a valid user to edit this. Please log in and try again.";
    returnAjax($login_status);
}
switch ($admin_req) {
    # Stuff
    case "save":
        returnAjax(saveEntry($_REQUEST));
        break;
    case "new":
        returnAjax(newEntry($_REQUEST));
        break;
    case "delete":
        returnAjax(deleteEntry($_REQUEST));
        break;
    default:
        returnAjax(getLoginState($_REQUEST, true));
}
function saveEntry($get)
{
    /***
     * Save a new taxon entry
     ***/
    $data64 = $get["data"];
    $enc = strtr($data64, '-_', '+/');
    $enc = chunk_split(preg_replace('!\\015\\012|\\015|\\012!', '', $enc));
    $enc = str_replace(' ', '+', $enc);
    $data_string = base64_decode($enc);
    $data = json_decode($data_string, true);
    if (!isset($data["id"])) {
        # The required attribute is missing
        $details = array("original_data" => $data64, "decoded_data" => $data_string, "data_array" => $data);
function checkColumnExists($column_list, $userReturn = true, $detailReturn = false)
{
    /***
     * Check if a comma-seperated list of columns exists in the
     * database.
     * @param string $column_list (comma-sep)
     * @return array
     ***/
    if (empty($column_list)) {
        return true;
    }
    global $db;
    $cols = $db->getCols();
    foreach (explode(',', $column_list) as $column) {
        if (!array_key_exists($column, $cols)) {
            if ($userReturn || $detailReturn) {
                $response = array('status' => false, 'error' => 'Invalid column. If it exists, it may be an illegal lookup column.', 'human_error' => "Sorry, you specified a lookup criterion that doesn't exist. Please try again.", 'columns' => $column_list, 'bad_column' => $column);
                if ($userReturn) {
                    returnAjax($response);
                }
                return $response;
            } else {
                return false;
            }
        }
    }
    if ($userReturn) {
        returnAjax(array('status' => true));
    } else {
        return true;
    }
}