golden hour
/home/phakp/public_html/formtools/global/api
⬆️ Go Up
Upload
File/Folder
Size
Actions
API.class.php
57.29 KB
Del
OK
README.md
3.08 KB
Del
OK
api.php
3.55 KB
Del
OK
examples
-
Del
OK
index.html
40 B
Del
OK
index.php
62 B
Del
OK
recaptcha
-
Del
OK
recaptchalib.php
9.67 KB
Del
OK
Edit: API.class.php
<?php namespace FormTools; require_once(realpath(__DIR__ . "/../library.php")); use PDO, Exception; class API { private static $version = "2.0.0"; private static $releaseDate = "20180113"; private static $systemErrors = array(304); /** * API constructor. Single param with the following: * "init_core" => true (default). This prevents the constructor doing anything. * "start_sessions" => false (default) * @param array $settings */ public function __construct ($settings = array()) { $init_core = isset($settings["init_core"]) ? $settings["init_core"] : true; $start_sessions = isset($settings["start_sessions"]) ? $settings["start_sessions"] : true; if ($init_core) { Core::init(array( "start_sessions" => $start_sessions )); } } public static function getVersion() { return self::$version; } public static function getReleaseDate() { return self::$releaseDate; } /** * This function lets you display the content generated from an export type into a webpage. You can * use this function on any export type in the database - even the ones that have been marked as "hidden". Note: * this function requires the Export Manager to be installed and enabled. * * @param integer $form_id * @param integer $view_id * @param integer $export_type_id * @param integer $page_num (defaults to 1) * @param array $options optional parameter that lets you configure the appearance of the data in a variety of ways. * * num_per_page - (integer) by default, it returns the number of results per page specified by * the View. This setting overrides that. * submission_ids - (integer or array of integers) this limits the results returned to the submission ID * or submission IDs specified in this field * order - (string) the database column name with a -DESC or -ASC suffix (e.g. col_1-ASC). * page_num_identifier - (string) passed via the query string to denote what page it's on (default: "page") * show_columns_only - (boolean) limits the fields that are displayed to those fields marked as "Column" * in the View. Defaults to false. * return_as_string - (boolean) if this value is set to true, instead of outputting the result it returns * the HTML as a string. * pagination_theme - (string) the pagination links (<< 1 2 3 ...) HTML is generated by the pagination.tpl * template, found in each of the theme folders. Generally this file is the same for * all themes, but in case it isn't, this setting lets you choose the theme folder with * which to render the HTML. * pagination_location - (string) accepts the values "top" (the default), "bottom", "both" or "none". This * determines where (if anywhere) the pagination links should appear. By default it only * appears at the top of the page, but you can set this value to either "both" or "bottom" * to have it appear there instead / as well. * * @return mixed the return value of this function depends on the API settings & the options passed to it. Namely: * * If error: * if $api_debug == true, the error page will be displayed displaying the error code. * if $api_debug == false, it returns an array with two indexes: * [0] false * [1] the API error code * If successful: * if "return_as_string" option key is set, it returns an array with two indexes: * [0] true * [1] the HTML content * if "return_as_string" not set, it just prints the HTML to the page (the default behaviour) */ public function showSubmissions($form_id, $view_id, $export_type_id, $page_num = 1, $options = array()) { $db = Core::$db; $root_dir = Core::getRootDir(); $smarty = Templates::getPageRenderSmarty("default"); if (!Modules::checkModuleEnabled("export_manager")) { return self::processError(400); } // check the form ID, View ID and export ID are valid $db->query("SELECT count(*) FROM {PREFIX}forms WHERE form_id = :form_id"); $db->bind("form_id", $form_id); $db->execute(); if ($db->fetch(PDO::FETCH_COLUMN) === 0) { return self::processError(401); } $db->query(" SELECT count(*) FROM {PREFIX}views WHERE form_id = :form_id AND view_id = :view_id "); $db->bindAll(array( "form_id" => $form_id, "view_id" => $view_id )); $db->execute(); if ($db->fetch(PDO::FETCH_COLUMN) == 0) { return self::processError(402); } $db->query("SELECT count(*) FROM {PREFIX}module_export_types WHERE export_type_id = :export_type_id"); $db->bind("export_type_id", $export_type_id); $db->execute(); if ($db->fetch(PDO::FETCH_COLUMN) == 0) { return self::processError(403); } // okay, now lets figure out what needs to be displayed & rendered $export_manager = Modules::instantiateModule("export_manager"); $smarty->addPluginsDir(array("$root_dir/modules/export_manager/smarty_plugins")); $form_info = Forms::getForm($form_id); $form_fields = Fields::getFormFields($form_id, array("include_field_type_info" => true, "include_field_settings" => true)); $view_info = Views::getView($view_id); $export_type_info = $export_manager->getExportType($export_type_id); $export_group_id = $export_type_info["export_group_id"]; $export_group_info = $export_manager->getExportGroup($export_group_id); // number of submissions per page (an integer or "all") $num_per_page = $view_info["num_submissions_per_page"]; if (isset($options["num_per_page"])) { $num_per_page = $options["num_per_page"]; } $order = "{$view_info["default_sort_field"]}-{$view_info["default_sort_field_order"]}"; if (isset($options["order"])) { $order = $options["order"]; } $display_fields = array(); $columns = "all"; if (isset($options["show_columns_only"]) && $options["show_columns_only"]) { $columns = array(); foreach ($view_info["columns"] as $view_field_info) { $curr_field_id = $view_field_info["field_id"]; foreach ($form_fields as $form_field_info) { if ($form_field_info["field_id"] != $curr_field_id) { continue; } $display_fields[] = array_merge($form_field_info, $view_field_info); $columns[] = $form_field_info["col_name"]; } } } else { foreach ($view_info["fields"] as $view_field_info) { $curr_field_id = $view_field_info["field_id"]; foreach ($form_fields as $form_field_info) { if ($form_field_info["field_id"] != $curr_field_id) { continue; } $display_fields[] = array_merge($form_field_info, $view_field_info); } } } $submission_ids = array(); if (isset($options["submission_ids"])) { if (is_numeric($options["submission_ids"])) { $submission_ids[] = $options["submission_ids"]; } else { if (is_array($submission_ids)) { $submission_ids = $options["submission_ids"]; } } } // perform the almighty search query $results_info = Submissions::searchSubmissions($form_id, $view_id, $num_per_page, $page_num, $order, $columns, array(), $submission_ids); $search_num_results = $results_info["search_num_results"]; $settings = Settings::get(); // now build the list of information we're going to send to the export type smarty template $placeholders = $export_manager->getExportFilenamePlaceholderHash(); $placeholders["export_group_id"] = $export_group_id; $placeholders["export_type_id"] = $export_type_id; $placeholders["export_group_results"] = "all"; $placeholders["same_page"] = General::getCleanPhpSelf(); $placeholders["display_fields"] = $display_fields; $placeholders["submissions"] = $results_info["search_rows"]; $placeholders["num_results"] = $results_info["search_num_results"]; $placeholders["view_num_results"] = $results_info["view_num_results"]; $placeholders["form_info"] = $form_info; $placeholders["view_info"] = $view_info; $placeholders["field_types"] = FieldTypes::get(true); $placeholders["settings"] = $settings; $placeholders["date_format"] = $settings["default_date_format"]; $placeholders["timezone_offset"] = $settings["timezone_offset"]; // pull out a few things into top level placeholders for easy use $placeholders["form_name"] = $form_info["form_name"]; $placeholders["form_id"] = $form_id; $placeholders["form_url"] = $form_info["form_url"]; $placeholders["view_name"] = $view_info["view_name"]; $placeholders["view_id"] = $view_id; $placeholders["export_group_name"] = General::createSlug(General::evalSmartyString($export_group_info["group_name"])); $placeholders["export_group_type"] = General::createSlug(General::evalSmartyString($export_type_info["export_type_name"])); $placeholders["filename"] = General::evalSmartyString($export_type_info["filename"], $placeholders, $smarty->getPluginsDir()); $template = $export_type_info["export_type_smarty_template"]; $placeholders["export_type_name"] = $export_type_info["export_type_name"]; $export_type_smarty_template = General::evalSmartyString($template, $placeholders, $smarty->getPluginsDir()); // if we're not displaying all results on the single page, generate the pagination HTML $pagination = ""; if ($num_per_page != "all") { $page_num_identifier = isset($options["page_num_identifier"]) ? $options["page_num_identifier"] : "page"; $theme = isset($options["pagination_theme"]) ? $options["pagination_theme"] : $settings["default_theme"]; $pagination = General::getPageNav($search_num_results, $num_per_page, $page_num, "", $page_num_identifier, $theme); } $pagination_location = (isset($options["pagination_location"])) ? $options["pagination_location"] : "top"; switch ($pagination_location) { case "top": $html = $pagination . $export_type_smarty_template; break; case "both": $html = $pagination . $export_type_smarty_template . $pagination; break; case "bottom": $html = $export_type_smarty_template . $pagination; break; case "none": $html = $export_type_smarty_template; break; // in case the user entered an invalid value default: return self::processError(404); break; } if (isset($options["return_as_string"]) && $options["return_as_string"]) { return array(true, $html); } else { echo $html; } } /** * Displays an individual form submission. Note: this function is NOT compatible with tabs - it won't render the * information with whatever tabs were defined. * * @param integer $form_id * @param integer $submission_id * @param integer $view_id * @param integer $submission_id */ public function showSubmission($form_id, $view_id, $export_type_id, $submission_id) { $options = array( "submission_ids" => $submission_id, "num_per_page" => "all" // prevents the pagination from appearing ); $this->showSubmissions($form_id, $view_id, $export_type_id, "", $options); } /** * Displays the total number of submissions in a form or form View. * * @param integer $form_id * @param integer $view_id */ public function showSubmissionCount($form_id, $view_id = "") { return Submissions::getSubmissionCount($form_id, $view_id); } /** * Creates a new, blank, unfinalized form submission and returns the new submission ID. * * @param integer $form_id * @param boolean $finalized whether or not the submission should be finalized. False by default. * @param array $default_values a hash of Database Columns => Values, letting you init the submission with those * values entered. * @return mixed $submission_id returns the new submission ID if successful. If there's an error, it either * redirects to an error page or returns the error info. This depends on your $g_api_debug setting in your * config.php file. */ public function createSubmission($form_id, $finalized = false, $values = array()) { $db = Core::$db; if (!Forms::checkFormExists($form_id)) { return self::processError(500); } $now = General::getCurrentDatetime(); $values["is_finalized"] = ($finalized) ? "yes" : "no"; $values["ip_address"] = $_SERVER["REMOTE_ADDR"]; $values["submission_date"] = $now; $values["last_modified_date"] = $now; list ($cols_str, $placeholders_str) = $db->getInsertStatementParams($values); try { $db->query(" INSERT INTO {PREFIX}form_{$form_id} ($cols_str) VALUES ($placeholders_str) "); $db->bindAll($values); $db->execute(); } catch (Exception $e) { return self::processError(501, array("debugging" => $e->getMessage())); } return $db->getInsertId(); } /** * This function was written to *significantly* simplify the job of integrating code-submission forms with Form * Tools. This, when used in conjunction with ft_api_form_process_page(), effectively does away with the need * to embed any special PHP logic in your forms to ensure the data gets submitted to Form Tools properly. * * The function does the following: * * - starts sessions (used to store the form data as the user progresses through the form) * - get / returns the unique submission ID. It creates a unique submission ID record in the database for * this submission; but it only shows up in the Form Tools UI if you explicitly tell Form Tools that the * submission is complete * - returns all values already submitted in the form, to let you pre-populate the fields if you want * * @param integer $form_id this field is only required for the FIRST page in your form. * @param string $namespace - a hash key to defined where in sessions the form information should be stored. Most * users probably won't care about this; it's for programmers who want a little more control over the content * of sessions (it's stored in: $_SESSION["form_tools_form"] by default). Note: if you choose to define your * own namespace, make sure you pass in the "namespace" setting in the final $settings parameter for the * ft_api_form_process_page() function - otherwise it won't know what submission or form to process! * @return array [0] the submission ID * [1] a hash of form values */ public function initFormPage($form_id = "", $mode = "live", $namespace = "form_tools_form") { Core::startSessions("api_form"); if (!isset($_SESSION[$namespace]) || empty($_SESSION[$namespace])) { $_SESSION[$namespace] = array(); // here, form_id should have been set: this is the FIRST page of (potentially) a multi-page form switch ($mode) { case "test": $_SESSION[$namespace]["form_tools_form_id"] = "test"; $_SESSION[$namespace]["form_tools_submission_id"] = "test"; break; case "initialize": // if form ID is blank here, chances are a user just put through their test submission an has returned // to a multi-page form page. In this situation, the sessions have been emptied, but this function is // called PRIOR to ft_api_process_form, which does the job of auto-redirecting to whatever page is // specified by the user if (empty($form_id)) { return $_SESSION[$namespace]; } $_SESSION[$namespace]["form_tools_form_id"] = $form_id; $_SESSION[$namespace]["form_tools_submission_id"] = "initializing"; $_SESSION[$namespace]["form_tools_initialize_form"] = 1; break; case "live": // if form ID is blank here, chances are a user is just returning to a multi-page form page // after putting through the submission. In this situation, the sessions have been emptied, but // this function is called PRIOR to ft_api_process_form, which does the job of auto-redirecting // to whatever page is specified by the user if (empty($form_id)) { return $_SESSION[$namespace]; } $submission_id = self::createSubmission($form_id); $_SESSION[$namespace]["form_tools_form_id"] = $form_id; $_SESSION[$namespace]["form_tools_submission_id"] = $submission_id; break; default: return self::processError(200); break; } } return $_SESSION[$namespace]; } /** * Clears sessions after succesfully completing a form. * * @param string $namespace (optional); */ public function clearFormSessions($namespace = "form_tools_form") { $_SESSION[$namespace] = ""; unset($_SESSION[$namespace]); } /** * Processes a form submission, either for a single page of a multi-page form or the entire form itself. If the * "submit_button_name key exists in $params (i.e. if the user just submitted the form), it updates the database for * the submission ID. * * Assumption: the initFormPage() function has been called on the page prior to calling this function. * * @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) * * Optional keys: * "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. * "may_update_finalized_submissions": true / false (true by default) * "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. * "has_captcha": (boolean). This needs to be set to true if the POST contains a captcha. * * @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 function processFormSubmission($params) { $multi_val_delimiter = Core::getMultiFieldValDelimiter(); $LANG = Core::$L; $db = Core::$db; // the form data parameter must ALWAYS be defined if (!isset($params["form_data"])) { return self::processError(306); } // special case: if "form_tools_delete_image_field__[fieldname]" exists, the user is just deleting an image // already uploaded through the form using the HTML generated by the ft_api_display_image_field function. // In this case, we process the page normally - even though the form data wasn't submitted & the page may // contain nothing in $form_data $is_deleting_file = false; $namespace = isset($params["namespace"]) ? $params["namespace"] : "form_tools_form"; $form_id = isset($_SESSION[$namespace]["form_tools_form_id"]) ? $_SESSION[$namespace]["form_tools_form_id"] : ""; $submission_id = isset($_SESSION[$namespace]["form_tools_submission_id"]) ? $_SESSION[$namespace]["form_tools_submission_id"] : ""; while (list($key, $value) = each($params["form_data"])) { if (preg_match("/form_tools_delete_image_field__(.*)$/", $key, $matches)) { $file_field_to_delete = $matches[1]; $is_deleting_file = true; $field_col = Fields::getFieldColByFieldName($form_id, $file_field_to_delete); try { $db->query(" UPDATE {PREFIX}form_{$form_id} SET $field_col = :val WHERE submission_id = :submission_id "); $db->bindAll(array( "val" => "", "submission_id" => $submission_id )); $db->execute(); $path = $_SESSION[$namespace][$file_field_to_delete]["file_upload_dir"]; $filename = $_SESSION[$namespace][$file_field_to_delete]["filename"]; @unlink("$path/$filename"); unset($_SESSION[$namespace][$file_field_to_delete]); unset($params["form_data"][$key]); } catch (Exception $e) { } } } // check the submission exists if (is_numeric($form_id) && is_numeric($submission_id) && !Submissions::checkSubmissionExists($form_id, $submission_id)) { return self::processError(305, array("debugging" => "{$LANG["phrase_submission_id"]}: $submission_id")); } // extract the submission ID and form ID from sessions $form_data = $params["form_data"]; $form_id = isset($_SESSION[$namespace]["form_tools_form_id"]) ? $_SESSION[$namespace]["form_tools_form_id"] : ""; $submission_id = isset($_SESSION[$namespace]["form_tools_submission_id"]) ? $_SESSION[$namespace]["form_tools_submission_id"] : ""; $has_captcha = isset($params["has_captcha"]) ? $params["has_captcha"] : false; $no_sessions_url = isset($params["no_sessions_url"]) ? $params["no_sessions_url"] : false; if (!isset($_GET["ft_sessions_url_override"]) && (empty($form_id) || empty($submission_id))) { if (!empty($no_sessions_url)) { header("location: $no_sessions_url"); exit; } else { return self::processError(300); } } // if the user is neither deleting a file or making a regular form submission, it means they've just // arrived at the page. Cool! Do nothing! if (!$is_deleting_file && !isset($params["form_data"][$params["submit_button"]])) { return; } $next_page = isset($params["next_page"]) ? $params["next_page"] : ""; $finalize = isset($params["finalize"]) ? $params["finalize"] : false; $namespace = isset($params["namespace"]) ? $params["namespace"] : "form_tools_form"; $may_update_finalized_submissions = isset($params["may_update_finalized_submissions"]) ? $params["may_update_finalized_submissions"] : true; // if we're in test mode, we don't do anything with the database - just store the fields in // sessions to emulate if ($form_id == "test" || $submission_id == "test") { reset($form_data); while (list($field_name, $value) = each($form_data)) { $_SESSION[$namespace][$field_name] = $value; } } else { if (isset($_SESSION[$namespace]["form_tools_initialize_form"])) { // only process the form if this submission is being set to be finalized if ($finalize) { // if the user is just putting through a test submission and we've reached the finalization step, // overwrite $form_data with ALL the $all_form_data = array_merge($_SESSION[$namespace], $form_data); Forms::initializeForm($all_form_data); } reset($form_data); while (list($field_name, $value) = each($form_data)) { $_SESSION[$namespace][$field_name] = $value; } } // otherwise it's a standard form submission for a fully set up form, with - ostensibly - a valid // submission ID and form ID. Update the submission for whatever info is in $form_data else { // check the form ID is valid if (!Forms::checkFormExists($form_id)) { return self::processError(301); } // check the submission ID isn't finalized if (!$may_update_finalized_submissions && Submissions::checkSubmissionFinalized($form_id, $submission_id)) { return self::processError(302, array("debugging" => "{$LANG["phrase_submission_id"]}: $submission_id")); } $form_info = Forms::getForm($form_id); // check to see if this form has been disabled if ($form_info["is_active"] == "no") { if (isset($form_data["form_tools_inactive_form_redirect_url"])) { header("location: {$form_data["form_tools_inactive_form_redirect_url"]}"); exit; } return self::processError(303); } extract(Hooks::processHookCalls("start", compact("form_info", "form_id", "form_data"), array("form_data")), EXTR_OVERWRITE); // get a list of the custom form fields (i.e. non-system) for this form $form_fields = Fields::getFormFields($form_id, array("include_field_type_info" => true)); $custom_form_fields = array(); $file_fields = array(); foreach ($form_fields as $field_info) { $field_id = $field_info["field_id"]; $is_system_field = $field_info["is_system_field"]; $field_name = $field_info["field_name"]; // ignore system fields if ($is_system_field == "yes") { continue; } if ($field_info["is_file_field"] == "no") { $custom_form_fields[$field_name] = array( "field_id" => $field_id, "col_name" => $field_info["col_name"], "field_title" => $field_info["field_title"], "include_on_redirect" => $field_info["include_on_redirect"], "field_type_id" => $field_info["field_type_id"], "is_date_field" => $field_info["is_date_field"] ); } else { $file_fields[] = array( "field_id" => $field_id, "field_info" => $field_info ); } } // now examine the contents of the POST/GET submission and get a list of those fields // which we're going to update $valid_form_fields = array(); while (list($form_field, $value) = each($form_data)) { if (array_key_exists($form_field, $custom_form_fields)) { $curr_form_field = $custom_form_fields[$form_field]; $cleaned_value = $value; if (is_array($value)) { if ($form_info["submission_strip_tags"] == "yes") { for ($i = 0; $i < count($value); $i++) { $value[$i] = strip_tags($value[$i]); } } $cleaned_value = implode("$multi_val_delimiter", $value); } else { if ($form_info["submission_strip_tags"] == "yes") { $cleaned_value = strip_tags($value); } } $valid_form_fields[$curr_form_field["col_name"]] = $cleaned_value; } } $now = General::getCurrentDatetime(); $is_finalized = ($finalize) ? "yes" : "no"; $valid_form_fields["last_modified_date"] = $now; $valid_form_fields["ip_address"] = $_SERVER["REMOTE_ADDR"]; $set_statements = $db->getUpdateStatements($valid_form_fields); // update the database submission info & upload files. Note: we don't do ANYTHING if the // form_tools_ignore_submission key is set in the POST data if (!isset($form_data["form_tools_ignore_submission"])) { // construct our query. Note that we do TWO queries: one if there was no CAPTCHA sent with this // post (which automatically finalizes the result), and one if there WAS. For the latter, the submission // is finalized later if ($has_captcha && $finalize) { $db->query(" UPDATE {PREFIX}form_$form_id SET $set_statements WHERE submission_id = :submission_id "); $db->bindAll($valid_form_fields); $db->bind("submission_id", $submission_id); } else { // only update the is_finalized setting if $may_update_finalized_submissions === false if (!$finalize && $may_update_finalized_submissions) { $is_finalized_clause = ""; } else { $is_finalized_clause = ", is_finalized = :is_finalized"; } $db->query(" UPDATE {PREFIX}form_$form_id SET $set_statements $is_finalized_clause WHERE submission_id = :submission_id "); $db->bindAll($valid_form_fields); $db->bind("submission_id", $submission_id); if (!empty($is_finalized_clause)) { $db->bind("is_finalized", $is_finalized); } } // only process the query if the form_tools_ignore_submission key isn't defined try { $db->execute(); } catch (Exception $e) { return self::processError(304, array( "debugging" => "Failed query in <b>" . __FUNCTION__ . ", " . __FILE__ . "</b>, line " . __LINE__ . ": " . $e->getMessage() )); } // used for uploading files. The error handling is incomplete here, like previous versions. Although the hooks // are permitted to return values, they're not used extract(Hooks::processHookCalls("manage_files", compact("form_id", "submission_id", "file_fields", "namespace"), array("success", "message")), EXTR_OVERWRITE); } // store all the info in sessions reset($form_data); while (list($field_name, $value) = each($form_data)) { $_SESSION[$namespace][$field_name] = $value; } } } // was there a reCAPTCHA response? If so, a recaptcha was just submitted, check it was entered correctly $passes_captcha = true; if ($has_captcha) { $passes_captcha = false; $resp = $this->validateRecaptcha($params["form_data"]["g-recaptcha-response"]); if ($resp->isSuccess()) { $passes_captcha = true; // if the developer wanted the submission to be finalized at this step, do so - it wasn't earlier! if ($finalize) { $db->query(" UPDATE {PREFIX}form_$form_id SET is_finalized = 'yes' WHERE submission_id = :submission_id "); $db->bind("submission_id", $submission_id); $db->execute(); } } else { // register the recaptcha response as a global, which can be picked up silently by ft_api_display_captcha to // let them know they entered it wrong $GLOBALS["g_api_recaptcha_error"] = $resp->getErrorCodes(); } } if ($passes_captcha && !empty($next_page) && !$is_deleting_file) { // if the user wasn't putting through a test submission or initializing the form, we can send safely // send emails at this juncture, but ONLY if it was just finalized OR if the send_emails parameter // allows for it if ($form_id != "test" && $submission_id != "test" && !isset($_SESSION[$namespace]["form_tools_initialize_form"]) && !isset($form_data["form_tools_ignore_submission"])) { // send any emails attached to the on_submission trigger if (isset($params["send_emails"]) && $params["send_emails"] === true) { Emails::sendEmails("on_submission", $form_id, $submission_id); } else { if (isset($is_finalized) && $is_finalized == "yes" && (!isset($params["send_emails"]) || $params["send_emails"] !== false)) { Emails::sendEmails("on_submission", $form_id, $submission_id); } } } header("location: $next_page"); exit; } return array(true, ""); } /** * This function saves you the effort of writing the PHP needed to display an image that's been uploaded through * your form. The function is used in forms that include file upload fields - and specifically, file upload fields * where users are uploading *images only*. It displays any images already uploaded through the form field, * and (by default) a "delete" button to let them delete the image. * * * @param hash $params a hash with the following keys: * Required keys * "field_name" - the name of the file input field * * Optional keys * "width" - adds a "width" attribute to the image, with this value * "height" - adds a "width" attribute to the image, with this value * "namespace" - only required if you specified a custom namespace in the original initFormPage method. * "hide_delete_button" - by default, whenever the field already has an image uploaded, this function will * display the image as well as a "Delete" button. If this value is passed & set to true, the delete * button will be removed (handy for "Review" pages). * "delete_button_label" - by default, "Delete file" */ public function displayImageField($params) { if (empty($params["field_name"])) { return; } $field_name = $params["field_name"]; $namespace = isset($params["namespace"]) ? $params["namespace"] : "form_tools_form"; // if an image hasn't already been uploaded through this field, do nothing if (!isset($_SESSION[$namespace][$field_name]) || !is_array($_SESSION[$namespace][$field_name]) || !isset($_SESSION[$namespace][$field_name]["filename"])) { return; } $file_upload_url = $_SESSION[$namespace][$field_name]["file_upload_url"]; $filename = $_SESSION[$namespace][$field_name]["filename"]; $width = isset($params["width"]) ? "width=\"{$params["width"]}\" " : ""; $height = isset($params["height"]) ? "height=\"{$params["height"]}\" " : ""; echo "<div><img src=\"$file_upload_url/$filename\" {$width}{$height}/></div>"; // if required, add the "Delete" if (!isset($params["hide_delete_button"]) || !$params["hide_delete_button"]) { $delete_file_label = (isset($params["delete_button_label"])) ? $params["delete_button_label"] : "Delete file"; echo "<div><input type=\"submit\" name=\"form_tools_delete_image_field__$field_name\" value=\"$delete_file_label\" /></div>"; } } /** * Just a wrapper function for General::loadField - renamed for consistency with the API. Plus it's good * to draw attention to this function with the additional documentation. * * @param string $field_name * @param string $session_name * @param string $default_value */ public function loadField($field_name, $session_name, $default_value) { return General::loadField($field_name, $session_name, $default_value); } /** * This function lets you log a client or administrator in programmatically. By default, it logs the user in * and redirects them to whatever login page they specified in their user account. However, you can override * this in two ways: either specify a custom URL where they should be directed to, or avoid redirecting at * all. If you choose the latter, make sure you've initiated SESSIONS on the calling page - otherwise the * login account information (needed to be stored in sessions) is lost. * * @param array $info a hash with the following possible parameters: * "username" - the username * "password" - the password * "auto_redirect_after_login" - (boolean, defaulted to false) determines whether or not the user should * be automatically redirected to a URL after a successful login. * "login_url" - the URL to redirect to (if desired). If this isn't set, but auto_redirect_after_login IS, * it will log the user in normally, to whatever login page they've specified in their account. */ public function login($info) { $password = isset($info["password"]) ? $info["password"] : ""; if (empty($password)) { return self::processError(1000); } $account_info = Accounts::getAccountByUsername($info["username"]); if (empty($account_info)) { return self::processError(1004); } if ($account_info["account_status"] == "disabled") { return self::processError(1001); } if ($account_info["account_status"] == "pending") { return self::processError(1002); } if (General::encode($password) != $account_info["password"]) { return self::processError(1003); } return Core::$user->login($info); } /** * Creates a client account in the database. * * @param array $account_info this has has 4 required keys: first_name, last_name, user_name, password * * The password is automatically encrypted by this function. * * It also accepts the following optional keys: * account_status: "active", "disabled", "pending" * ui_language: (should only be one of the languages currently supported by the script, e.g. "en_us") * timezone_offset: +- an integer value, for each hour * sessions_timeout: * date_format: * login_page: * logout_url: * theme: * menu_id: * * @return array [0] true / false * [1] an array of error codes (if false) or the new account ID */ public function createClientAccount($account_info) { $api_debug = Core::isAPIDebugEnabled(); $error_codes = array(); // check all the valid fields if (!isset($account_info["first_name"]) || empty($account_info["first_name"])) { $error_codes[] = 700; } if (!isset($account_info["last_name"]) || empty($account_info["last_name"])) { $error_codes[] = 701; } if (!isset($account_info["email"]) || empty($account_info["email"])) { $error_codes[] = 702; } if (!General::isValidEmail($account_info["email"])) { $error_codes[] = 703; } if (!isset($account_info["username"]) || empty($account_info["username"])) { $error_codes[] = 704; } else { if (preg_match('/[^A-Za-z0-9]/', $account_info["username"])) { $error_codes[] = 705; } if (!Accounts::isValidUsername($account_info["username"])) { $error_codes[] = 706; } } if (!isset($account_info["password"]) || empty($account_info["password"])) { $error_codes[] = 707; } else { if (preg_match('/[^A-Za-z0-9]/', $account_info["password"])) { $error_codes[] = 708; } } if (!empty($error_codes)) { if ($api_debug) { $page_vars = array("message_type" => "error", "error_codes" => $error_codes); Themes::displayPage("error.tpl", $page_vars); exit; } else { return array(false, $error_codes); } } // to pass validation $account_info["password_2"] = $account_info["password"]; list ($success, $message, $new_user_id) = Administrator::addClient($account_info); if (!$success) { return self::processError(709, array( "debugging" => "Failed query in <b>" . __FUNCTION__ . "</b>: " . $message )); } return array(true, $new_user_id); } /** * Updates a client account with whatever values are in $info. * * @param integer $account_id * @param array $info an array of keys to update, corresponding to the columns in the accounts table. */ public function updateClientAccount($account_id, $info) { $db = Core::$db; // check the account ID is valid $account_info = Accounts::getAccountInfo($account_id); // check the account ID was valid (i.e. the account exists) and that it's a CLIENT account if (!isset($account_info["account_id"])) { return self::processError(900); } if ($account_info["account_type"] != "client") { return self::processError(901); } // the list of DB columns that can be updated $valid_columns = array( "account_status", "ui_language", "timezone_offset", "sessions_timeout", "date_format", "login_page", "logout_url", "theme", "menu_id", "first_name", "last_name", "email", "username", "password" ); $columns_to_update = array(); while (list($key, $value) = each($info)) { // if something passed by the user isn't a valid column name, ignore it if (!in_array($key, $valid_columns)) { continue; } // if this is the password field, encrypt it if ($key == "password") { $value = General::encode($value); } $columns_to_update[$key] = $value; } if (empty($columns_to_update)) { return array(true, ""); } $update_lines = $db->getUpdateStatements($columns_to_update); try { $db->query(" UPDATE {PREFIX}accounts SET $update_lines WHERE account_id = :account_id "); $db->bindAll($columns_to_update); $db->bind("account_id", $account_id); $db->execute(); } catch (Exception $e) { return self::processError(902, array( "Failed query in <b>" . __FUNCTION__ . ", " . __FILE__ . "</b>, line " . __LINE__ . ": " . $e->getMessage() )); } return array(true, ""); } /** * Completely deletes a client account from the database. * * @param integer $account_id * @return mixed * if success: * returns array with two indexes: [0] true, [1] empty string * * if error: * if $api_debug == true * the error page will be displayed displaying the error code. * * if $api_debug == false, it returns an array with two indexes: * [0] false * [1] the API error code */ public function deleteClientAccount($account_id) { $account_info = Accounts::getAccountInfo($account_id); // check the account ID was valid (i.e. the account exists) and that it's a CLIENT account if (!isset($account_info["account_id"])) { return self::processError(800); } if ($account_info["account_type"] != "client") { return self::processError(801); } Clients::deleteClient($account_id); return array(true, ""); } /** * Deletes all unfinalized submissions and any associated files that have been uploaded. * * @param boolean $delete_all this deletes ALL unfinalized submissions. False by default. Normally it just * deletes all unfinalized submissions made 2 hours and older. This wards against accidentally deleting * those submissions currently being put through. * * @return mixed the number of unfinalized submissions that were just deleted, or error. */ public function deleteUnfinalizedSubmissions($form_id, $delete_all = false) { $db = Core::$db; if (!Forms::checkFormExists($form_id)) { return self::processError(650); } $time_clause = (!$delete_all) ? "AND DATE_ADD(submission_date, INTERVAL 2 HOUR) < curdate()" : ""; $db->query(" SELECT * FROM {PREFIX}form_{$form_id} WHERE is_finalized = 'no' $time_clause "); $db->execute(); $submissions = $db->fetchAll(); $num_rows = $db->numRows(); if ($num_rows == 0) { return 0; } // find out which of this form are file fields $form_fields = Fields::getFormFields($form_id); $file_type_id = FieldTypes::getFieldTypeIdByIdentifier("file"); $file_field_info = array(); // a hash of col_name => file upload dir foreach ($form_fields as $field_info) { if ($field_info["field_type_id"] == $file_type_id) { $field_id = $field_info["field_id"]; $col_name = $field_info["col_name"]; $field_settings = Fields::getFieldSettings($field_id); $file_field_info[$col_name] = $field_settings["folder_path"]; } } // now delete the info foreach ($submissions 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]}"); } } $db->query("DELETE FROM {PREFIX}form_{$form_id} WHERE submission_id = :submission_id"); $db->bind("submission_id", $submission_id); $db->execute(); } return $num_rows; } /** * Displays a captcha in your form pages, using the recaptcha service (http://recaptcha.net). Requires * you to have set up an account with them for this current website. * * @return mixed generally, this function just displays the CAPTCHA in your page. But in case of error: * * if $api_debug == true, the error page will be displayed displaying the error code. * if $api_debug == false, it returns an array with two indexes: * [0] false * [1] the API error code */ public function displayCaptcha($show_error = true) { $recaptcha_site_key = Core::getApiRecaptchaSiteKey(); $recaptcha_secret_key = Core::getApiRecaptchaSecretKey(); $recaptcha_lang = Core::getApiRecaptchaLang(); // check the two recaptcha keys have been defined if (empty($recaptcha_site_key) || empty($recaptcha_secret_key)) { self::displayInPageErrorBlock(600); exit; } $this->includeRecaptchaLib(); if (isset($GLOBALS["g_api_recaptcha_error"]) && $show_error) { echo <<< END <div class="form_tools_recaptcha_error"> There was a reCAPTCHA error. </div> END; } echo <<< END <div class="g-recaptcha" data-sitekey="{$recaptcha_site_key}"></div> <script type="text/javascript" src="https://www.google.com/recaptcha/api.js?hl={$recaptcha_lang}"></script> END; } /** * This function checks to see if a submission is unique - based on whatever criteria you require * for your test case. * * @param integer $form_id * @param array $criteria a hash of whatever criteria is need to denote uniqueness, where the key is the * database column name and the value is the current value being tested. For instance, if you wanted to check * that no-one has submitted a form with a particular email address, you could pass * array("email" => "myemail@whatever.com) as the second parameter (where "email" is the database column name). * @param integer $current_submission_id if this value is set, the function ignores that submission when doing * a comparison. */ public function checkSubmissionIsUnique($form_id, $criteria, $current_submission_id = "") { $db = Core::$db; // confirm the form is valid if (!Forms::checkFormExists($form_id)) { return self::processError(550); } if (!is_array($criteria)) { return self::processError(551); } $where_clauses = array(); $placeholders = array(); while (list($col_name, $value) = each($criteria)) { if (empty($col_name)) { return self::processError(552); } $where_clauses[] = "$col_name = :$col_name"; $placeholders[$col_name] = $value; } if (empty($where_clauses)) { return self::processError(553); } if (!empty($current_submission_id)) { $where_clauses[] = "submission_id != :submission_id"; $placeholders["submission_id"] = $current_submission_id; } $where_clause_str = "WHERE " . join(" AND ", $where_clauses); try { $db->query(" SELECT count(*) FROM {PREFIX}form_{$form_id} $where_clause_str "); $db->bindAll($placeholders); $db->execute(); } catch (Exception $e) { return self::processError(554); } return $db->fetch(PDO::FETCH_COLUMN) === 0; } /** * This function was provided to allow POST form users to include a reCAPTCHA in their form. This is * used to display an error message in the event of a failed attempt. * * @param string $message the message to output if there was a problem with the CAPTCHA contents. */ public function displayPostFormCaptchaError($message = "") { if (!isset($_SESSION["form_tools_form_data"])) { return; } if (isset($_SESSION["form_tools_form_data"]["api_recaptcha_error"]) && !empty($_SESSION["form_tools_form_data"]["api_recaptcha_error"])) { if ($message) { echo $message; } else { echo "Sorry, the CAPTCHA (image verification) was entered incorrectly. Please try again."; } } $_SESSION["form_tools_form_data"]["api_recaptcha_error"] = ""; } /** * Returns all information about a submission. N.B. Would have been nice to have made this just a * wrapper for ft_get_submission_info, but that function contains hooks. Need to revise all core * code to allow external calls to optionally avoid any hook calls. * * @param integer $form_id * @param integer $submission_id */ public function getSubmission($form_id, $submission_id) { $db = Core::$db; // confirm the form is valid if (!Forms::checkFormExists($form_id)) { return self::processError(405); } if (!is_numeric($submission_id)) { return self::processError(406); } // get the form submission info $db->query(" SELECT * FROM {PREFIX}form_{$form_id} WHERE submission_id = :submission_id "); $db->bind("submission_id", $submission_id); $db->execute(); return $db->fetch(); } public function includeRecaptchaLib() { require_once(__DIR__ . "/recaptcha/autoload.php"); } public function validateRecaptcha($g_recaptcha_response) { $secret_key = Core::getAPIRecaptchaSecretKey(); $this->includeRecaptchaLib(); $recaptcha = new \ReCaptcha\ReCaptcha($secret_key); return $recaptcha->verify($g_recaptcha_response, $_SERVER['REMOTE_ADDR']); } /** * Called for all API errors. If `$g_api_debug = true;` is set in the users' config.php file, this method * redirects the user to a webpage containing details about the error, plus a link * @param $error_code * @param array $extra_info * @return array */ private static function processError($error_code, $extra_info = array()) { $api_debug = Core::isAPIDebugEnabled(); if ($api_debug) { self::displayError($error_code, $extra_info); exit; } else { return array(false, $error_code); } } private static function displayError($error_code, $extra_info = array()) { $is_system_error = in_array($error_code, self::$systemErrors); $content = array_merge(array( "error_code" => $error_code, "message_type" => "error", "error_type" => $is_system_error ? "system" : "user" ), $extra_info); Themes::displayPage("error.tpl", $content); } private static function displayInPageErrorBlock($error_code) { $LANG = Core::$L; echo <<< END <div style="padding: 8px; margin: 8px; background-color: #f2dede; border: 1px solid #ebccd1; border-radius: 4px; color: #a94442; display: inline-block; font-family: arial; font-size: 13px; "> <b>Form Tools API Error:</b> $error_code - <a href="https://docs.formtools.org/api/v2/error_codes/#{$error_code}">{$LANG["phrase_error_learn_more"]}</a> </div> END; } }
Save