function billUser($full_data_array)
{
    /***
     * Do a charge against the user, then update the status all around.
     *
     * https://developers.braintreepayments.com/android+php/reference/request/transaction/sale
     ***/
    $data_array = $full_data_array["post_data"];
    $brainTreeTransaction = array("amount" => $data_array["amount"], "paymentMethodNonce" => $data_array["nonce"], 'customer' => array(), 'options' => array('storeInVaultOnSuccess' => true, 'submitForSettlement' => true));
    global $billingTokens;
    $billingTokens = array();
    try {
        $braintree = Braintree_Transaction::sale($brainTreeTransaction);
        $result = array("status" => $braintree->success, "braintree_result" => $braintree);
        if (!$braintree->success) {
            $result["app_error_code"] = 115;
            $result["details"] = array();
            $result["requested_action"] = "bill";
            $result["human_error"] = "Failed to charge your card. Please try again later.";
            $result["given"] = $data_array;
        } else {
            try {
                # Post-process
                $billingTokens["status"] = true;
                $postProcessDetails = array();
                $givenArgs = smart_decode64($data_array["billActionDetail"]);
                $billingTokens["provided"] = $givenArgs;
                $postAction = $givenArgs["action"];
                switch ($postAction) {
                    # Set all the $postProcessDetails data
                    case "session":
                        break;
                    case "roleChange":
                        $newRole = $givenArgs["newRole"];
                        # Do the role change
                        break;
                    default:
                        throw new Exception("Bad post-process action '{$postAction}'");
                        break;
                }
                $billingTokens["post_process_details"] = $postProcessDetails;
            } catch (Exception $e) {
                # An internal error! We want to log this.
                $error_id = Stronghash::createSalt();
                $write = "\n\n\n\n" . datestamp() . " - " . json_encode($_REQUEST);
                $write .= "\nError ID: {$error_id}";
                $write .= "\nGot error: " . $e->getMessage();
                $write .= "\n" . implode("\n", jTraceEx($e));
                file_put_contents('billing_errors.log', $write, FILE_APPEND | LOCK_EX);
                $billingTokens["status"] = false;
                $billingTokens["error"] = $e->getMessage();
                $billingTokens["human_error"] = "Your card was charged, but we couldn't update the server. Please contact support with error ID {$error_id}";
                $billingTokens["need_popup"] = true;
                $result["status"] = false;
                $result["app_error_code"] = 116;
            }
        }
    } catch (Exception $e) {
        # Just the Braintree errors
        $result = array("status" => false, "error" => $e->getMessage(), "human_error" => "Unable to charge your card", "app_error_code" => 114, "given" => $data_array);
    }
    return $result;
}
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);
}
function advancedSearchProjectContains($get)
{
    /***
     * Similar to advancedSearchProject, but rather than requiring the
     * whole project is contained in the bounds, there only has to be
     * a nonzero amount of area of a project contained within bounds.
     ***/
    global $db;
    $searchParams = smart_decode64($get["q"], false);
    $search = array();
    $response = array("notices" => array());
    foreach ($searchParams as $col => $searchQuery) {
        if (checkColumnExists($col, false)) {
            if ($searchQuery["data"] != "*") {
                $searchQuery["data"] = $db->sanitize($searchQuery["data"]);
                $search[$col] = $searchQuery;
            }
        } else {
            $response["notices"][] = "'{$col}' is an invalid column and was ignored.";
        }
    }
    $response["search"] = $searchParams;
    $response['status'] = true;
    # The API hit returns data from these columns
    $returnCols = array("public", "project_id", "disease", "project_title", "bounding_box_n", "bounding_box_e", "bounding_box_w", "bounding_box_s", "disease_morbidity", "disease_mortality", "disease_samples", "disease_positive", "includes_anura", "includes_caudata", "includes_gymnophiona", "sampled_species", "carto_id");
    # For numerical comparisons, we have to allow a type specification
    $allowedSearchTypes = array("<", ">", "<=", ">=", "=");
    $loose = isset($get["loose"]) ? toBool($get["loose"]) : true;
    $boolean_type = "AND";
    $where_arr = array();
    foreach ($search as $col => $searchQuery) {
        $crit = $searchQuery["data"];
        $validSearchType = empty($searchQuery["search_type"]) ? true : in_array($searchQuery["search_type"], $allowedSearchTypes);
        if (!empty($searchQuery["search_type"]) && !$validSearchType) {
            $response["notices"][] = "'" . $searchQuery["search_type"] . "' isn't a valid search type";
        }
        if ($validSearchType && !is_numeric($crit)) {
            $response["notices"][] = "Search types may only be specified for numeric data ('" . $searchQuery["search_type"] . "' tried to be specified for '{$crit}')";
        }
        if (!$validSearchType || !is_numeric($crit)) {
            $where_arr[] = $loose ? 'LOWER(`' . $col . "`) LIKE '%" . $crit . "%'" : '`' . $col . "`='" . $crit . "'";
        } else {
            # The query is numeric AND we have a search type specified
            $where_arr[] = "`" . $col . "` " . $searchQuery["search_type"] . " " . $crit;
        }
    }
    $where = '(' . implode(' ' . strtoupper($boolean_type) . ' ', $where_arr) . ')';
    $query = "SELECT " . implode(",", $returnCols) . " FROM `" . $db->getTable() . "` WHERE {$where}";
    $response["query"] = $query;
    $db->invalidateLink();
    $r = mysqli_query($db->getLink(), $query);
    if ($r === false) {
        $response["status"] = false;
        $response["error"] = mysqli_error($db->getLink());
        $response["query"] = $query;
        returnAjax($response);
    }
    $queryResult = array();
    $baseRows = mysqli_num_rows($r);
    $boolCols = array("public", "includes_anura", "includes_caudata", "includes_gymnophiona");
    while ($row = mysqli_fetch_assoc($r)) {
        # Authenticate the project against the user
        if (checkProjectIdAuthorized($row["project_id"], true)) {
            # Clean up data types
            foreach ($row as $col => $val) {
                if (is_numeric($val)) {
                    if (in_array($col, $boolCols)) {
                        $row[$col] = toBool($val);
                    } else {
                        $row[$col] = floatval($val);
                    }
                }
                if ($col == "carto_id") {
                    $cartoObj = json_decode($val);
                    if (!is_array($cartoObj)) {
                        $cartoObj = $val;
                    } else {
                        foreach ($cartoObj as $k => $v) {
                            $nk = str_replace("&#95;", "_", $k);
                            try {
                                unset($cartoObj[$k]);
                            } catch (Exception $e) {
                                $response["notices"][] = $e->getMessage();
                            }
                            $cartoObj[$nk] = $v;
                        }
                    }
                    $row[$col] = $cartoObj;
                }
            }
            $queryResult[] = $row;
        }
    }
    $response['result'] = $queryResult;
    $response['count'] = sizeof($response['result']);
    $response['base_count'] = $baseRows;
    returnAjax($response);
}