<?php

/**
 * This file contains the code used by the generated form pages.
 */

namespace FormTools\Modules\FormBuilder;

use FormTools\Core;
use FormTools\Fields;
use FormTools\Forms as CoreForms;
use FormTools\General as CoreGeneral;
use FormTools\Hooks as CoreHooks;
use FormTools\Modules;
use FormTools\Settings;
use FormTools\Submissions;
use FormTools\Views;
use FormTools\ViewFields;
use FormTools\ViewTabs;
use PDO, Exception;

class FormGenerator
{
    /**
     * Called by forms generated by the Form Builder.
     *
     * @param array $config the published form configuration (result of calling fb_get_form_configuration())
     *              plus a couple of extra override vars for error purposes (error_code, validation_error).
     * @param integer $page an integer representing the page in the form: 1 to n (where n <= 8)
     * @param integer $submission_id this doesn't have to be passed for when the form is offline.
     */
    public static function generateFormPage($config, $page = 1, $submission_id = "")
    {
        $published_form_id = $config["published_form_id"];

        // convert the placeholders into a placeholder_id => value hash
        $placeholders = array();
        foreach ($config["placeholders"] as $info) {
            $placeholders[$info["placeholder_id"]] = $info["placeholder_value"];
        }

        $settings = array(
            "mode"                       => "live",
            "published_form_id"          => $published_form_id,
            "form_id"                    => $config["form_id"],
            "view_id"                    => $config["view_id"],
            "submission_id"              => $submission_id,
            "template_set_id"            => $config["set_id"],
            "page"                       => $page,
            "include_review_page"        => ($config["include_review_page"] == "yes") ? true : false,
            "include_thanks_page_in_nav" => ($config["include_thanks_page_in_nav"] == "yes") ? true : false,
            "is_online"                  => ($config["is_online"] == "yes") ? true : false,
            "thankyou_page_content"      => $config["thankyou_page_content"],
            "form_offline_page_content"  => $config["form_offline_page_content"],
            "review_page_title"          => $config["review_page_title"],
            "thankyou_page_title"        => $config["thankyou_page_title"],
            "placeholders"               => $placeholders,

            // special override vars
            "error_code"                 => isset($config["error_code"]) ? $config["error_code"] : "",
            "validation_error"           => isset($config["validation_error"]) ? $config["validation_error"] : ""
        );

        // add in the templates
        foreach ($config["templates"] as $template_info) {
            $template_type = $template_info["template_type"];
            $settings["{$template_type}_template_id"] = $template_info["template_id"];
        }

        echo FormGenerator::generateForm($settings);
    }

    /**
     * Our top-level generation function. This generates the page from the appropriate template set and returns
     * every page in the form. Its used in the form builder section - it allows you to pass in particular values on the fly
     * to override what's actually stored in the database for the form configuration.
     *
     * Assumptions:
     * - the template set and templates within it have been validated for (a) all containing the required templates /
     *   placeholders and (b) the template set it complete.
     *
     * @param array $settings a hash containing the following info:
     *    Required:
     *      "form_id"
     *      "view_id"
     *      "set_id" => the template set to use
     *      "page"   => a particular page in the form (1-X, where X=the thankyou page)
     *
     *      - specific template IDs -
     *      "page_layout_template_id"
     *      "header_template_id"
     *      "footer_template_id"
     *      "navigation_template_id"
     *      "continue_block_template_id"
     *      "form_page_template_id"
     *      "review_page_template_id"
     *      "thankyou_page_template_id"
     *      "error_message_template_id"
     *
     *    Optional:
     *      "submission_id"    => if this is being used to generate the actual form, this will contain a value (unless the
     *                            form is offline)
     *      "placeholders"     => an array of IDs
     *      "error_code"       => for serious errors
     *      "validation_error" => when the page just failed server side validation, this contains the error message.
     */
    public static function generateForm($settings)
    {
        global $api;

        $root_dir = Core::getRootDir();
        $root_url = Core::getRootUrl();

        $mode    = $settings["mode"]; // preview / live
        $form_id = $settings["form_id"];
        $view_id = $settings["view_id"];
        $published_form_id          = isset($settings["published_form_id"]) ? $settings["published_form_id"] : "";
        $template_set_id            = $settings["template_set_id"];
        $include_review_page        = $settings["include_review_page"];
        $include_thanks_page_in_nav = $settings["include_thanks_page_in_nav"];

        $templates = array(
            "page_layout"    => Templates::getTemplate($settings["page_layout_template_id"]),
            "header"         => Templates::getTemplate($settings["header_template_id"]),
            "footer"         => Templates::getTemplate($settings["footer_template_id"]),
            "navigation"     => Templates::getTemplate($settings["navigation_template_id"]),
            "continue_block" => Templates::getTemplate($settings["continue_block_template_id"]),
            "error_message"  => Templates::getTemplate($settings["error_message_template_id"])
        );

        $form_info = CoreForms::getForm($form_id);
        $view_info = Views::getView($view_id);

        // a little helper function to make sense of the awful crap returned by Views::getView
        $view_tabs = General::getViewTabsFromViewInfo($view_info);

        // now figure out what page type we're dealing with. We figure this out by looking at the $settings["page"]
        // value, which contains a numeric value 1-8
        $current_page = $settings["page"];

        if (isset($settings["error_code"]) && !empty($settings["error_code"])) {
            $templates["page"] = array(
                "content"   => "<div>Error: <span class=\"fb_error_code\">{$settings["error_code"]}</span></div>",
                "page_type" => "error"
            );
            $current_page = 1;
        } else if (!$settings["is_online"]) {
            $templates["page"] = Templates::getTemplate($settings["form_offline_page_template_id"]);
            $current_page = 1;
        } else {
            $page_type = Forms::getCurrentPageType($current_page, $view_tabs, $include_review_page);

            switch ($page_type) {
                case "form_page":
                    $templates["page"] = Templates::getTemplate($settings["form_page_template_id"]);
                    break;
                case "review_page":
                    $templates["page"] = Templates::getTemplate($settings["review_page_template_id"]);
                    break;
                case "thanks_page":
                    $templates["page"] = Templates::getTemplate($settings["thankyou_page_template_id"]);
                    break;
            }
        }

        // construct the list of pages that should appear in the navigation.
        $params = array(
            "view_tabs"                  => $view_tabs,
            "include_review_page"        => $include_review_page,
            "include_thanks_page_in_nav" => $include_thanks_page_in_nav,
            "review_page_title"          => $settings["review_page_title"],
            "thankyou_page_title"        => $settings["thankyou_page_title"]
        );
        $nav_pages = Forms::getNavPages($params);

        $smarty = General::createNewSmartyInstance();

        if (Core::isAPIAvailable()) {
            $smarty->assign("api", $api);
        }

        $smarty->assign("mode", $mode);
        $smarty->assign("template_set_id", $template_set_id);
        $smarty->assign("namespace", "form_builder_{$published_form_id}");
        $smarty->assign("published_form_id", $published_form_id);
        $smarty->assign("review_page_title", $settings["review_page_title"]);
        $smarty->assign("include_review_page", $include_review_page);
        $smarty->assign("include_thanks_page_in_nav", $include_thanks_page_in_nav);
        $smarty->assign("nav_pages", $nav_pages);
        $smarty->assign("templates", $templates);
        $smarty->assign("form_name", $form_info["form_name"]);
        $smarty->assign("form_id", $form_id);
        $smarty->assign("view_name", $view_info["view_name"]);
        $smarty->assign("view_id", $view_id);
        $smarty->assign("view_info", $view_info);
        $smarty->assign("num_form_pages", count($view_tabs));
        $smarty->assign("page_titles", $view_tabs);
        $smarty->assign("g_root_url", $root_url);
        $smarty->assign("g_root_dir", $root_dir);
        $smarty->assign("thankyou_page_content", $settings["thankyou_page_content"]);
        $smarty->assign("form_offline_page_content", $settings["form_offline_page_content"]);

        // submission ID is always set - it's just blank when building the form
        $smarty->assign("submission_id", $settings["submission_id"]);

        // TODO, only include codemirror if needed. It's a fringe case.
        $required_resources =<<< END
    <script>
    var g = {
        root_url:       "$root_url",
        error_colours:  ["ffbfbf", "ffb5b5"],
        notify_colours: ["c6e2ff", "97c7ff"]
    };
    </script>
    <script src="$root_url/global/scripts/jquery.js"></script>
    <link href="$root_url/themes/default/css/smoothness/jquery-ui-1.8.6.custom.css" rel="stylesheet" type="text/css"/>
    <script src="$root_url/themes/default/scripts/jquery-ui-1.8.6.custom.min.js"></script>
    <script src="$root_url/global/scripts/general.js"></script>
    <script src="$root_url/global/scripts/rsv.js"></script>
    <script src="$root_url/global/scripts/field_types.php"></script>
    <link rel="stylesheet" href="$root_url/global/css/field_types.php" type="text/css" />
    <script src="$root_url/global/codemirror/lib/codemirror.js"></script>
    <script src="$root_url/global/codemirror/mode/xml/xml.js"></script>
    <script src="$root_url/global/codemirror/mode/css/css.js"></script>
    <script src="$root_url/global/codemirror/mode/javascript/javascript.js"></script>
    <script src="$root_url/global/scripts/jquery-ui-timepicker-addon.js"></script>
    <link rel="stylesheet" href="$root_url/global/codemirror/lib/codemirror.css" type="text/css" />
    <script src="$root_url/global/fancybox/jquery.fancybox-1.3.4.pack.js"></script>
    <link rel="stylesheet" href="$root_url/global/fancybox/jquery.fancybox-1.3.4.css" type="text/css" media="screen" />
END;

        // shame, but we need to use buffering to capture the standalone hook outputs. Otherwise, we'd need
        // users to add the template hooks directly in their templates, which would be confusing
        ob_start();
        CoreHooks::processTemplateHookCalls("standalone_form_fields_head_top", $smarty->getTemplateVars(), array());
        $standalone_head_top = ob_get_contents();
        ob_end_clean();

        ob_start();
        CoreHooks::processTemplateHookCalls("standalone_form_fields_head_bottom", $smarty->getTemplateVars(), array());
        $standalone_head_bottom = ob_get_contents();
        ob_end_clean();

        $required_resources = $standalone_head_top . $required_resources . $standalone_head_bottom;
        $smarty->assign("required_resources", $required_resources);

        // add the template set resources
        $resources = Resources::getResources($template_set_id);
        $resource_placeholders = array();

        // simple cache prevention
        $now = date("U");

        // used when editing in the Form Builder. Placeholders info needs to be passed to the css.php generation file
        if ($mode == "preview") {
            $query_str = "&source=sessions";
        } else {
            $query_str = "&published_form_id={$settings["published_form_id"]}";
        }

        foreach ($resources as $resource_info) {
            $resource_id = $resource_info["resource_id"];
            $placeholder = $resource_info["placeholder"];
            $resource_type = $resource_info["resource_type"];

            if ($resource_type == "css") {
                $link = "<link type=\"text/css\" rel=\"stylesheet\" href=\"$root_url/modules/form_builder/form_resources/css.php?resource_id=$resource_id&nocache=$now{$query_str}\">";
            } else {
                $link = "<script src=\"$root_url/modules/form_builder/form_resources/js.php?resource_id=$resource_id&nocache=$now{$query_str}\"></script>";
            }
            $resource_placeholders[$placeholder] = $link;
        }
        $smarty->assign("R", $resource_placeholders);

        // add the Template Set placeholders
        $placeholders = Placeholders::getPlaceholders($template_set_id);
        $P = array();
        foreach ($placeholders as $placeholder_info) {
            $placeholder_id = $placeholder_info["placeholder_id"];
            $placeholder    = $placeholder_info["placeholder"];

            // this shouldn't ever occur
            if (!array_key_exists($placeholder_id, $settings["placeholders"])) {
                continue;
            }

            // TODO multi-select + checkboxes...
            $P[$placeholder] = (isset($settings["placeholders"][$placeholder_id])) ? $settings["placeholders"][$placeholder_id] : "";
        }
        $smarty->assign("P", $P);

        $validation_error = isset($settings["validation_error"]) && !empty($settings["validation_error"]) ? $settings["validation_error"] : "";
        $smarty->assign("validation_error", $validation_error);

        // now generate the page content
        $smarty->assign("num_pages", count($nav_pages));
        $smarty->assign("current_page", $current_page);
        $smarty->assign("eval_str", $templates["page_layout"]["content"]);
        $page_content = $smarty->fetch("../../modules/form_builder/smarty_plugins/eval.tpl");

        return $page_content;
    }


    /**
     * Processes a form submission from a Form Builder page. This is actually a simplified version of the ft_api_process_form_page()
     * function, with all unnecessary code removed. Since we're controlling the actual form creation, much of the code is no longer
     * necessary.
     *
     * @param array $params
     *
     *     Required keys:
     *        "submit_button": the "name" attribute value of the form submit button
     *        "form_data": the contents of $_POST (or $_GET, if "method" setting is set to "GET" ... )
     *        "file_data": the contents of $_FILES (only needed if your form contained file fields)
     *        "next_page": the URL (relative or absolute) of which page to redirect to (e.g. the next page
     *               in the form or the "thankyou" page).
     *        "finalize": this tells the function to finalize the submission. This prevents it being subsequently
     *               editable via this function and makes the submission appear in the Form Tools UI.
     *        "no_sessions_url": for multi-page forms it's a good idea to pass along this value. It should be the URL
     *               of a page (usually the FIRST page in the form sequence) where the user will be redirected to if
     *               they didn't start the form from the first page. It ensures the form submission gets created &
     *               submitted properly.
     *        "namespace": if you specified a custom namespace for ft_api_init_form_page, for where the form values will
     *               be stored temporarily in sessions, you need to pass that same value to this function - otherwise
     *               it won't be able to retrieve the form and submission ID
     *        "send_emails": (boolean). By default, Form Tools will trigger any emails that have been attached to the
     *               "on submission" event ONLY when the submission is finalized (finalize=true). This setting provides
     *               you with direct control over when the emails get sent. If not specified, will use the default
     *               behaviour.
     *
     * @return mixed ordinarily, this function will just redirect the user to whatever URL is specified in the
     *        "next_page" key. But if that value isn't set, it returns an array:
     *               [0] success / false
     *               [1] if failure, the API Error Code, otherwise blank
     */
    public static function processFormBuilderPage($params)
    {
        global $api;

        if (!isset($params["form_data"][$params["submit_button"]])) {
            return;
        }

        $namespace      = $params["namespace"];
        $submission_id  = $params["submission_id"];
        $page           = $params["page"];
        $page_type      = $params["page_type"];
        $form_id        = $params["form_id"];
        $view_id        = $params["view_id"];
        $form_data      = $params["form_data"];
        $next_page      = $params["next_page"];
        $finalize       = isset($params["finalize"]) ? $params["finalize"] : false;

        $failed_validation = false;
        $update_successful = true;
        $validation_error = "";
        if ($page_type == "form") {
            $tabs = ViewTabs::getViewTabs($view_id, true);
            $curr_page = $page;
            if (empty($tabs)) {
                $curr_page = "";
            }

            $view_fields = ViewFields::getGroupedViewFields($view_id, $curr_page);

            $field_ids = array();
            $editable_field_ids = array();
            foreach ($view_fields as $view_field_group) {
                foreach ($view_field_group["fields"] as $field_info) {
                    $field_id = $field_info["field_id"];
                    $field_ids[] = $field_id;
                    if ($field_info["is_editable"] == "yes") {
                        $editable_field_ids[] = $field_id;
                    }
                }
            }

            $form_data["view_id"] = $view_id;

            // this is awful. It's a problem with the Core, not so much this module.
            $form_data["field_ids"] = implode(",", $field_ids); // *sigh*
            $form_data["editable_field_ids"] = $editable_field_ids;

            list($update_successful, $validation_error) = Submissions::updateSubmission($form_id, $submission_id, $form_data);

            // if there was any problem udpating this submission, make a special note of it: we'll use that info to merge the current POST request
            // info with the original field values to ensure the page contains the latest data (i.e. for cases where they fail server-side validation)
            if (!$update_successful) {
                $failed_validation = true;
            }
        }

        // reCAPTCHA just adds a "g-recaptcha-response" key to the POST form values. Looking for that isn't sufficient
        // because a hacker could just simulate the request without the property and bypass it. So instead the {{captcha}}
        // function makes a note in sessions that the page contains a CAPTCHA - which can't be bypassed
        $has_captcha = isset($_SESSION[$namespace]["has_captcha"]) ? $_SESSION[$namespace]["has_captcha"] : false;

        // the reCAPTCHA can be placed on the form or review page only
        if ($has_captcha && ($page_type == "form" || $page_type == "review")) {
            $recaptcha_error_msg = "&bull;&nbsp; Sorry, you didn't enter the reCAPTCHA correctly. Please try again.";

            if (isset($params["form_data"]["g-recaptcha-response"])) {
                $resp = $api->validateRecaptcha($params["form_data"]["g-recaptcha-response"]);
                if ($resp->isSuccess()) {
                    $_SESSION[$namespace]["passed_captcha"] = true;
                } else {
                    // if the main update was successful, but they failed validation, just display the single "wrong captcha" error
                    if ($update_successful) {
                        $validation_error = $recaptcha_error_msg;
                    } else {
                        $br = (!empty($validation_error)) ? "<br />" : "";
                        $validation_error .= $br . $recaptcha_error_msg;
                    }
                    $failed_validation = true;
                }
            }
        }

        // always reset the has_captcha flag for the next page load
        $_SESSION[$namespace]["has_captcha"] = false;

        // this automatically sends any emails set to the on_submission trigger.
        if (!$failed_validation) {
            if ($finalize) {
                Submissions::finalizeSubmission($form_id, $submission_id);
            }

            if (!empty($next_page)) {
                if (!in_array($page, $_SESSION[$namespace]["form_tools_completed_pages"])) {
                    $_SESSION[$namespace]["form_tools_completed_pages"][] = $page;
                }
                header("location: $next_page");
                exit;
            }
        }

        return array(false, $validation_error);
    }


    /**
     * Initializes a Form Builder page. If the sessions are being newly created (i.e. they just arrived at the form)
     * the first return array index contains true. False otherwise.
     *
     * @param integer $form_id
     * @param integer $view_id
     * @param string $namespace
     * @return array [0] whether the sessions were just created or not
     *               [1] the current form data
     */
    public static function initFormBuilderPage($form_id, $view_id, $namespace)
    {
        $newly_created = false;

        if (!isset($_SESSION[$namespace]) || empty($_SESSION[$namespace])) {
            $newly_created = true;
            $_SESSION[$namespace] = array();
            $submission_id = Submissions::createBlankSubmission($form_id, $view_id, false, Core::$user->getAccountPlaceholders());

            $_SESSION[$namespace]["form_tools_form_id"]         = $form_id;
            $_SESSION[$namespace]["form_tools_view_id"]         = $view_id;
            $_SESSION[$namespace]["form_tools_submission_id"]   = $submission_id;
            $_SESSION[$namespace]["form_tools_completed_pages"] = array();
        }

        return array($newly_created, $_SESSION[$namespace]);
    }


    /**
     * Clears sessions after successfully completing a form.
     *
     * @param string $namespace (optional);
     */
    public static function clearFormBuilderFormSessions($namespace)
    {
        $_SESSION[$namespace] = "";
        unset($_SESSION[$namespace]);
    }


    /**
     * This is called on every published form. It checks that the form ID, View ID and all the template IDs
     * actually exist.
     *
     * @param array $config
     */
    public static function checkLiveFormConditions($config)
    {
        $db = Core::$db;

        if (!isset($config["form_id"]) || !CoreForms::checkFormExists($config["form_id"])) {
            return array("success" => false, "error_code" => "FB100");
        }
        if (!isset($config["view_id"]) || !Views::checkViewExists($config["view_id"])) {
            return array("success" => false, "error_code" => "FB101");
        }

        // check all the templates exist. Just do a count.
        $template_ids = array();
        foreach ($config["templates"] as $template_info) {
            $template_ids[] = $template_info["template_id"];
        }
        $num_templates = count($template_ids);
        $template_id_str = implode(",", $template_ids);

        $db->query("
            SELECT count(*)
            FROM {PREFIX}module_form_builder_templates
            WHERE template_id IN ($template_id_str)
        ");
        $db->execute();

        if ($num_templates != $db->fetch(PDO::FETCH_COLUMN)) {
            return array("success" => false, "error_code" => "FB102");
        }

        return "";
    }


    /**
     * Called on every published form page. This checks that the current page number being passed via the query
     * string is valid. It checks that (a) it wasn't hacked to contain any non-numeric content, (b) it's within
     * the range of pages in this form configuration and (c) they aren't skipping any pages: each page has
     * to be submitted in sequence.
     *
     * @param integer $page
     * @param array $pages
     */
    public static function verifyPageNumber($page, $pages, $namespace)
    {
        $num_pages = count($pages);
        if (!is_numeric($page) || $page < 1) {
            $page = 1;
        } else if ($page > $num_pages) {
            $page = $num_pages;
        }

        // now look to see what pages the user has already filled in
        $completed_pages = (isset($_SESSION[$namespace]["form_tools_completed_pages"])) ? $_SESSION[$namespace]["form_tools_completed_pages"] : array();

        // if the previous page isn't marked as complete, set the page to the highest completed page + 1
        if ($page != 1 && !in_array($page-1, $completed_pages)) {
            if (!empty($completed_pages)) {
                $page = max($completed_pages) + 1;
            } else {
                $page = 1;
            }
        }

        return $page;
    }


    /**
     * Called on every published form page. This handles the scenario where the form is no longer online, either via the schedule
     * or by the user explicitly setting the published form to offline.
     *
     * @param array $config
     * @param string $namespace
     */
    public static function checkFormOffline($config, $namespace)
    {
        $module_settings = Modules::getModuleSettings();
        self::maybeTakeScheduledFormOffline($config);

        // if the form is marked as offline
        if ($config["is_online"] == "yes") {
            return true;
        }

        // the user doesn't already have any sessions created (i.e. they weren't in the midst of submitting the
        // form!), we just display the Form Offline page
        if (!isset($_SESSION[$namespace]["form_tools_submission_id"])) {
            self::clearFormBuilderFormSessions($namespace);
            self::generateFormPage($config, 1);
            exit;
        } else {
            // here we have a special fringe case: the form just went offline but the user already started putting
            // through the form. How we handle THAT, depends on what they choose for the "Offline form behaviour" setting
            if ($module_settings["scheduled_offline_form_behaviour"] == "cutoff") {
                self::clearFormBuilderFormSessions($namespace);
                self::generateFormPage($config, 1);
                exit;
            } else {
                // do nothing! For this scenario, we just allow them to proceed (i.e. let the function return nothing). As soon
                // as they complete their form, sessions will be emptied and they won't be able to put through another
            }
        }
    }


    /**
     * This blithely checks a published form to figure out if it needs to be taken offline. This is called for every published
     * form on each page load, and on the Publish Tab in the admin section.
     *
     * @param array $config
     * @return boolean whether or not the form was just taken offline
     */
    public static function maybeTakeScheduledFormOffline($config)
    {
        $db = Core::$db;
        $just_taken_offline = false;


        // take the form offline if it passed the offline date
        if ($config["is_online"] == "yes" && !is_null($config["offline_date"])) {
            $default_timezone_offset = Settings::get("default_timezone_offset");

            $now = date("U");
            $now = $now + ($default_timezone_offset * 60 * 60);

            $offline_unixtime = CoreGeneral::convertDatetimeToTimestamp($config["offline_date"]);

            // is the offline datetime just passed, take the sucker offline
            if ($now > $offline_unixtime) {
                $published_form_id = $config["published_form_id"];
                $db->query("
                    UPDATE {PREFIX}module_form_builder_forms
                    SET    is_online = 'no',
                           offline_date = NULL
                    WHERE  published_form_id = :published_form_id
                ");
                $db->bind("published_form_id", $published_form_id);
                $db->execute();

                $just_taken_offline = true;
            }
        }

        return $just_taken_offline;
    }


    /**
     * Deletes all unfinalized submissions and any associated files that have been uploaded. For safety,
     * it only deletes incomplete submissions that are 24 hours old.
     *
     * @return integer the number of unfinalized submissions that were just deleted, or false if it failed.
     */
    public static function deleteUnfinalizedSubmissions($form_id)
    {
        $db = Core::$db;

        if (!CoreForms::checkFormExists($form_id)) {
            return false;
        }

        $db->query("
            SELECT *
            FROM   {PREFIX}form_{$form_id}
            WHERE  is_finalized = 'no' AND
              DATE_ADD(submission_date, INTERVAL 24 HOUR) < curdate()
        ");
        $db->execute();
        $submissions_to_delete = $db->fetchAll();

        if ($db->numRows() == 0) {
            return 0;
        }

        // find out which fields in this form are file fields
        $form_fields = Fields::getFormFields($form_id, array("include_field_type_info" => true));

        $file_field_info = array(); // a hash of col_name => file upload dir
        foreach ($form_fields as $field_info) {
            if ($field_info["is_file_field"] == "file") {
                $field_id = $field_info["field_id"];
                $col_name = $field_info["col_name"];
                $extended_settings = Fields::getExtendedFieldSettings($field_id);
                $file_field_info[$col_name] = $extended_settings["file_upload_dir"];
            }
        }

        // now delete the info
        foreach ($submissions_to_delete as $submission_info) {
            $submission_id = $submission_info["submission_id"];

            // delete any files associated with the submission
            foreach ($file_field_info as $col_name => $file_upload_dir) {
                if (!empty($submission_info[$col_name])) {
                    @unlink("{$file_upload_dir}/{$submission_info[$col_name]}");
                }
            }
            reset($file_field_info);

            $db->query("
                DELETE FROM {PREFIX}form_{$form_id}
                WHERE submission_id = :submission_id
            ");
            $db->bind("submission_id", $submission_id);
            $db->execute();
        }

        return count($submissions_to_delete);
    }
}
