diff --git a/change_log.txt b/change_log.txt index 4dcc23a..071b3e9 100644 --- a/change_log.txt +++ b/change_log.txt @@ -1,5 +1,56 @@ +------------------------------------------------------------------------------------------------------------------- +Version 1.8.18 + + - Added filters on entry detail to support editing payment related data in the payment info box. + - Added the parameter name as a third parameter to the "gform_field_value" filter. + - Added "gform_entry_detail_grid_display_empty_fields" filter to allow displaying empty fields even if option is not checked (i.e. print view). + add_filter( 'gform_entry_detail_grid_display_empty_fields', '__return_true' ); + - Added filter to get_form_meta() to allow forms to be filtered globally. + - Updated localization of certain strings. + - Updated POT file. + - Updated the order of marking an entry as spam so that it is done before the gform_entry_created and gform_entry_post_save hooks. + + - Fixed issue with PayPal fulfillment not going through when entry was marked as Paid in the entry detail page. + - Fixed issue with trial period amount (fixed amount entered on subscription feed) for currencies other than Dollar. + - Fixed issue with Chrome on Android for drop downs with conditional logic. + - Fixed an issue with the field filters on the entry list, export entries and results pages where product fields couldn't be filtered. + - Fixed issue with complex fields not being properly loaded into array. + - Fixed issue where List fields in notifications sometimes displayed incorrectly due to max line size being exceeded. + - Fixed issue with quantity fields not defaulting to correct value after being hidden by conditional logic. + - Fixed an issue with the single file upload field where validation fails if the max file size is set higher than 2047MB. + + - AF: Added check to framework to prevent sending spam entries to non-payment feeds. This will take effect in feeds as they are migrated to the Add-On Framework. + - AF: Fixed issue with feeds not getting executed when configured for delayed payment and the payment amount ends up being $0.00. + - AF: Fixed issue with feed addons not preserving current feed when a new feed was getting created. + +------------------------------------------------------------------------------------------------------------------- +Version 1.8.17 + - Added form specific version of 'gform_entry_is_spam' filter. + - Added "gform_entry_is_spam" filter. + add_filter( 'gform_entry_is_spam', 'it_is_all_spam', 10, 3 ); + function it_is_all_spam( $is_spam, $form, $entry ) { + return true; + } + - Updated entry list and detail pages to display spam features if gform_entry_is_spam hook is used when Akismet integration is disabled. + - Updated is_duplicate check to work for "long" values as well. + - Added 'gform_disable_view_counter' filter to disable counting of form views. Both globally and by form id. Views column remains displayed on the Forms page. + add_filter( 'gform_disable_view_counter', '__return_true' ); + add_filter( 'gform_disable_view_counter_12', '__return_false' ); + - Fixed XSS vulnerability. + - Fixed a notice on the WordPress updates page. + - Fixed issue with Add-On manager displaying error when installing Add-Ons. + - Fixed notice when $form['pagination']['display_progressbar_on_confirmation'] was not set. + - Fixed issue with entry list page payment status drop down containing "Approved" instead of "Paid". + - Fixed issue where setting an input-based field value to empty would fail to save. + - AF: Added get_feeds_by_slug function. + - AF: Added is_delayed function to check whether a feed is delayed processing with PayPal Standard. + - AF: Updated maybe_process_feed function to handle processing when add-on is set as delayed in PayPal Standard feed setup. + - AF: Updated logging statements to be clearer. + - AF: Removed unused function get_feed_by_entry. + ------------------------------------------------------------------------------------------------------------------- Version 1.8.16 + - Fixed some strings that weren't localized and added localization context to others - Fixed issue with datepicker to prevent user being returned to start of form when tabbing after selecting a date. - Fixed a notice on the WordPress updates page @@ -9,11 +60,13 @@ Version 1.8.16 ------------------------------------------------------------------------------------------------------------------- Version 1.8.15 + - Fixed an issue with the multi-file upload field while uploading multiple files all selected at the same time in the file dialog. If one of the uploads fails due to an HTTP error then the next file in the list will appear as 100% complete but it will be removed from the form submission. - AF: Fixed issue with checkboxes no retaining their values ------------------------------------------------------------------------------------------------------------------- Version 1.8.14 + - Fixed a potential security vulnerability for some servers which could allow code to be parsed via the file upload field. - Fixed a security issue to prevent code injection - Fixed an issue with the file upload field that allows malicious form submissions to bypass the validation for the maximum file size setting. @@ -23,6 +76,7 @@ Version 1.8.14 ------------------------------------------------------------------------------------------------------------------- Version 1.8.13 + - Added additional check plus user feedback for failed multi-file uploads. - Fixed a potential security vulnerability for some server configurations which could allow code to be executed via the file upload field. - Fixed issue with form export/import setting inactive notifications to active. @@ -37,6 +91,7 @@ Version 1.8.13 ------------------------------------------------------------------------------------------------------------------- Version 1.8.12 + - Added PHP version of the "gform_calculation_result" filter. add_filter( 'gform_calculation_result', 'my_calc_result', 10, 5 ); function my_calc_result( $result, $formula, $field, $form, $entry ) { @@ -139,6 +194,7 @@ Version 1.8.10 ------------------------------------------------------------------------------------------------------------------- Version 1.8.9 + - Added "password" to the list of fields which allow HTML input. - Added 'gform_field_container' filter to allow modifying the field container markup. add_action( 'gform_field_container', 'my_field_container', 10, 6 ); @@ -396,6 +452,7 @@ Version 1.8.5 ------------------------------------------------------------------------------------------------------------------- Version 1.8.4.1 + - Fixed issue with tooltips not working on Add On pages. Included font-awesome.css to Add On pages to fix the issue. - Fixed issue where old inputs were not removed when adding new choices via bulk add functionality for Post Custom Fields with a "checkbox" field type - Fixed an issue with entry export which may result in an empty export file for forms with a large number of entries @@ -470,6 +527,7 @@ Version 1.8.4 ------------------------------------------------------------------------------------------------------------------- Version 1.8.3 + - Added new filter "gform_post_category_choices" to alter post category choices sort order. Both globally and form id + field id specific. add_filter("gform_post_category_choices", "set_sort", 10, 3); function set_sort($choices, $field, $form_id){ diff --git a/common.php b/common.php index 6bf5acb..c5db4c7 100644 --- a/common.php +++ b/common.php @@ -3700,7 +3700,11 @@ public static function get_field_input($field, $value="", $lead_id=0, $form_id=0 $upload .= sprintf('', $id, esc_attr($value)); } else { - $upload = sprintf("", $max_upload_size); + $upload = ''; + if ( $max_upload_size <= 2047 * 1048576 ) { + // MAX_FILE_SIZE > 2048MB fails. The file size is checked anyway once uploaded, so it's not necessary. + $upload = sprintf( "", $max_upload_size ); + } $upload .= sprintf("", $id, $field_id, esc_attr($class), $disabled_text); } @@ -4695,48 +4699,48 @@ public static function get_lead_field_display($field, $value, $currency="", $use default : if($media == "email"){ - $list = ""; + $list = "
\n"; //reading columns from entry data foreach($columns as $column){ - $list .= ""; + $list .= "\n"; } - $list .= ""; + $list .= "\n"; $list .= ""; foreach($value as $item){ $list .= ""; foreach($columns as $column){ $val = rgar($item, $column); - $list .= ""; + $list .= "\n"; } - $list .=""; + $list .="\n"; } - $list .="
" . esc_html($column) . "" . esc_html($column) . "
{$val}{$val}
"; + $list .="\n"; } else{ - $list = ""; + $list = "
\n"; //reading columns from entry data foreach($columns as $column){ - $list .= ""; + $list .= "\n"; } - $list .= ""; + $list .= "\n"; $list .= ""; foreach($value as $item){ $list .= ""; foreach($columns as $column){ $val = rgar($item, $column); - $list .= ""; + $list .= "\n"; } - $list .=""; + $list .="\n"; } - $list .="
" . esc_html($column) . "" . esc_html($column) . "
{$val}{$val}
"; + $list .="\n"; } break; } @@ -4921,6 +4925,12 @@ public static function gform_do_shortcode($content){ return do_shortcode($content); } + public static function spam_enabled( $form_id ) { + $spam_enabled = self::akismet_enabled( $form_id ) || has_filter( 'gform_entry_is_spam' ) || has_filter( "gform_entry_is_spam_{$form_id}" ); + + return $spam_enabled; + } + public static function has_akismet(){ $akismet_exists = function_exists('akismet_http_post') || function_exists('Akismet::http_post'); return $akismet_exists; @@ -5638,11 +5648,17 @@ public static function get_field_filter_settings($form) { foreach ($fields as $field) { + $input_type = GFFormsModel::get_input_type( $field ); + $field_type = GFFormsModel::get_input_type($field); $operators = isset($operators_by_field_type[$field_type]) ? $operators_by_field_type[$field_type] : $operators_by_field_type["default"]; - if (!isset($field["choices"]) && !in_array("contains", $operators)) - $operators[] = "contains"; + + if ( $field['type'] == 'product' && in_array( $input_type, array( 'radio', 'select' ) ) ) { + $operators = array( 'is' ); + } elseif (!isset($field["choices"]) && !in_array("contains", $operators)){ + $operators[] = "contains"; + } $field_filter = array(); $key = $field["id"]; @@ -5764,8 +5780,8 @@ public static function get_entry_info_filter_columns($get_users = true){ "operators" => array( "is", "isnot"), "values" => array( array( - "text" => "Approved", - "value" => "Approved" + "text" => "Paid", + "value" => "Paid" ), array( "text" => "Failed", @@ -5825,7 +5841,7 @@ public static function get_entry_meta_filter_settings($form_id) { } - public static function get_field_filters_from_post(){ + public static function get_field_filters_from_post($form){ $field_filters = array(); $filter_fields = rgpost("f"); if (is_array($filter_fields)) { @@ -5846,6 +5862,15 @@ public static function get_field_filters_from_post(){ $val = $key_array[1] . ":" . $val; } $field_filter["key"] = $key; + + $field = GFFormsModel::get_field( $form, $key ); + if ( $field ) { + $input_type = GFFormsModel::get_input_type( $field ); + if ( $field['type'] == 'product' && in_array( $input_type, array( 'radio', 'select' ) ) ) { + $operator = 'contains'; + } + } + $field_filter["operator"] = $operator; $field_filter["value"] = $val; $field_filters[] = $field_filter; diff --git a/entry_detail.php b/entry_detail.php index bffa0b3..130c514 100644 --- a/entry_detail.php +++ b/entry_detail.php @@ -401,7 +401,7 @@ function toggleNotificationOverride(isInit) { @@ -965,39 +968,42 @@ class="hndle">
- : + : +
- : + : +
- : + : +
- : + : +
$key, - "operator" => rgempty("operator", $_GET) ? "is" : rgget("operator"), + "operator" => $filter_operator, "value" => $val ); } @@ -736,7 +748,7 @@ function initSelectAllEntries(){
  • " href="?page=gf_entries&view=entries&id=&filter=unread"> () |
  • " href="?page=gf_entries&view=entries&id=&filter=star"> () |
  • " href="?page=gf_entries&view=entries&id=&filter=spam"> () |
  • " href=""> @@ -1310,7 +1322,7 @@ function initSelectAllEntries(){ ", $form), $form); if($display_title || $display_description){ @@ -697,11 +708,10 @@ public static function get_form($form_id, $display_title=true, $display_descript //check admin setting for whether the progress bar should start at zero $start_at_zero = rgars($form, "pagination/display_progressbar_on_confirmation"); - //check for filter $start_at_zero = apply_filters("gform_progressbar_start_at_zero", $start_at_zero, $form); //show progress bar on confirmation - if($start_at_zero && $has_pages && !IS_ADMIN && ($form["confirmation"]["type"] == "message" && $form["pagination"]["type"] == "percentage") && $form["pagination"]["display_progressbar_on_confirmation"]) + if( $start_at_zero && $has_pages && ! IS_ADMIN && ( $form["confirmation"]["type"] == "message" && $form["pagination"]["type"] == "percentage" ) ) { $progress_confirmation = self::get_progress_bar($form, $form_id,$confirmation_message); if($ajax) @@ -717,7 +727,7 @@ public static function get_form($form_id, $display_title=true, $display_descript } else { - $progress_confirmation = $confirmation_message; + $progress_confirmation = $confirmation_message; } } @@ -951,53 +961,58 @@ private static function validate_honeypot($form){ return rgempty("input_{$honeypot_id}"); } - public static function handle_submission($form, &$lead, $ajax=false){ + public static function handle_submission($form, &$lead, $ajax=false){ - $lead_id = apply_filters("gform_entry_id_pre_save_lead{$form["id"]}", apply_filters("gform_entry_id_pre_save_lead", null, $form), $form); + $lead_id = apply_filters("gform_entry_id_pre_save_lead{$form["id"]}", apply_filters("gform_entry_id_pre_save_lead", null, $form), $form); - if(!empty($lead_id)){ - if(empty($lead)) - $lead = array(); - $lead["id"] = $lead_id; - } + if(!empty($lead_id)){ + if(empty($lead)) + $lead = array(); + $lead["id"] = $lead_id; + } - //creating entry in DB - RGFormsModel::save_lead($form, $lead); + //creating entry in DB + RGFormsModel::save_lead($form, $lead); - //reading entry that was just saved - $lead = RGFormsModel::get_lead($lead["id"]); + //reading entry that was just saved + $lead = RGFormsModel::get_lead($lead["id"]); $lead = GFFormsModel::set_entry_meta($lead, $form); - do_action('gform_entry_created', $lead, $form); - $lead = apply_filters('gform_entry_post_save', $lead, $form); + //if Akismet plugin is installed, run lead through Akismet and mark it as Spam when appropriate + $is_spam = GFCommon::akismet_enabled($form['id']) && GFCommon::is_akismet_spam($form, $lead); + $is_spam = apply_filters( 'gform_entry_is_spam', $is_spam, $form, $lead ); + $is_spam = apply_filters( "gform_entry_is_spam_{$form['id']}", $is_spam, $form, $lead ); - RGFormsModel::set_current_lead($lead); + GFCommon::log_debug("Checking for spam..."); + GFCommon::log_debug("Is entry considered spam? {$is_spam}."); - //if Akismet plugin is installed, run lead through Akismet and mark it as Spam when appropriate - $is_spam = GFCommon::akismet_enabled($form['id']) && GFCommon::is_akismet_spam($form, $lead); + if( $is_spam ){ - GFCommon::log_debug("Checking for spam..."); - GFCommon::log_debug("Is entry considered spam? {$is_spam}."); + //marking entry as spam + RGFormsModel::update_lead_property($lead["id"], "status", "spam", false, true); + $lead["status"] = "spam"; - if(!$is_spam){ - GFCommon::create_post($form, $lead); - //send notifications - GFCommon::send_form_submission_notifications($form, $lead); - } - else { - //marking entry as spam - RGFormsModel::update_lead_property($lead["id"], "status", "spam", false, true); - $lead["status"] = "spam"; - } + } - self::clean_up_files($form); + do_action('gform_entry_created', $lead, $form); + $lead = apply_filters('gform_entry_post_save', $lead, $form); - //display confirmation message or redirect to confirmation page - return self::handle_confirmation($form, $lead, $ajax); - } + RGFormsModel::set_current_lead($lead); + + if(!$is_spam){ + GFCommon::create_post($form, $lead); + //send notifications + GFCommon::send_form_submission_notifications($form, $lead); + } + + self::clean_up_files($form); + + //display confirmation message or redirect to confirmation page + return self::handle_confirmation($form, $lead, $ajax); + } - public static function clean_up_files($form){ + public static function clean_up_files($form){ $unique_form_id = rgpost("gform_unique_id"); if(!ctype_alnum($unique_form_id)) return false; @@ -1911,7 +1926,7 @@ private static function get_conditional_logic($form, $field_values = array()){ } if(rgar($choice,"isSelected") && $input_type == "select"){ - $val = $is_pricing_field ? $choice["value"] . "|" . GFCommon::to_number($choice["price"]) : $choice["value"]; + $val = $is_pricing_field && $field['type'] != 'quantity' ? $choice["value"] . "|" . GFCommon::to_number($choice["price"]) : $choice["value"]; $default_values[$field["id"]] = $val; } else if(rgar($choice,"isSelected")){ diff --git a/forms_model.php b/forms_model.php index a2dbb40..dc11d25 100644 --- a/forms_model.php +++ b/forms_model.php @@ -321,6 +321,8 @@ public static function get_form_meta($form_id){ //load notifications to legacy structure to maintain backward compatibility with legacy hooks and functions $form = self::load_notifications_to_legacy($form); + $form = apply_filters('gform_form_post_get_meta', $form); + // cached form meta for cheaper retrieval on subsequent requests self::$_current_forms[$form_id] = $form; @@ -1759,7 +1761,7 @@ public static function get_parameter_value($name, $field_values, $field){ } } - return apply_filters("gform_field_value_$name", apply_filters("gform_field_value", $value, $field), $field); + return apply_filters( "gform_field_value_$name", apply_filters( 'gform_field_value', $value, $field, $name ), $field, $name ); } public static function get_default_value($field, $input_id){ @@ -2049,8 +2051,9 @@ public static function prepare_value($form, $field, $value, $input_name, $lead_i $value = ""; } - } else + } else { $value = self::get_fileupload_value($form_id, $input_name); + } break; case "number" : @@ -2696,11 +2699,10 @@ public static function save_input($form, $field, &$lead, $current_fields, $input $value = self::prepare_value($form, $field, $value, $input_name, rgar($lead, "id")); //ignore fields that have not changed - if($lead != null && $value === rgget($input_id, $lead)){ + if( $lead != null && $value === rgget( (string) $input_id, $lead ) ){ return; } - $lead_detail_id = self::get_lead_detail_id($current_fields, $input_id); self::update_lead_field_value($form, $lead, $field, $lead_detail_id, $input_id, $value); @@ -2967,31 +2969,39 @@ public static function insert_form_view($form_id, $ip){ public static function is_duplicate($form_id, $field, $value){ global $wpdb; + $lead_detail_table_name = self::get_lead_details_table_name(); - $lead_table_name = self::get_lead_table_name(); - $sql_comparison = "ld.value=%s"; + $lead_table_name = self::get_lead_table_name(); + $lead_detail_long = self::get_lead_details_long_table_name(); + $is_long = ! is_array( $value ) && strlen( $value ) > GFORMS_MAX_FIELD_LENGTH - 10; - switch(RGFormsModel::get_input_type($field)){ - case "time" : + $sql_comparison = $is_long ? '( ld.value = %s OR ldl.value = %s )' : 'ld.value = %s'; + + switch( GFFormsModel::get_input_type( $field ) ) { + case 'time': $value = sprintf("%02d:%02d %s", $value[0], $value[1], $value[2]); break; - case "date" : + case 'date': $value = self::prepare_date(rgar($field, "dateFormat"), $value); break; - case "number" : + case 'number': $value = GFCommon::clean_number($value, rgar($field, 'numberFormat')); break; - case "phone" : + case 'phone': $value = str_replace(array(")", "(", "-", " "), array("", "", "", ""), $value); - $sql_comparison = 'replace(replace(replace(replace(ld.value, ")", ""), "(", ""), "-", ""), " ", "") = %s'; + $sql_comparison = 'replace( replace( replace( replace( ld.value, ")", "" ), "(", "" ), "-", "" ), " ", "" ) = %s'; break; - } + } + $inner_sql_template = "SELECT %s as input, ld.lead_id + FROM {$lead_detail_table_name} ld + INNER JOIN {$lead_table_name} l ON l.id = ld.lead_id\n"; - $inner_sql_template = " SELECT %s as input, ld.lead_id - FROM $lead_detail_table_name ld - INNER JOIN $lead_table_name l ON l.id = ld.lead_id - WHERE l.form_id=%d AND ld.form_id=%d + if( $is_long ) { + $inner_sql_template .= "INNER JOIN {$lead_detail_long} ldl ON ldl.lead_detail_id = ld.id\n"; + } + + $inner_sql_template .= "WHERE l.form_id=%d AND ld.form_id=%d AND ld.field_number between %s AND %s AND status='active' AND {$sql_comparison}"; @@ -3002,11 +3012,11 @@ public static function is_duplicate($form_id, $field, $value){ $input_count = sizeof($field["inputs"]); foreach($field["inputs"] as $input){ $union = empty($inner_sql) ? "" : " UNION ALL "; - $inner_sql .= $union . $wpdb->prepare($inner_sql_template, $input["id"], $form_id, $form_id, $input["id"] - 0.001, $input["id"] + 0.001, $value[$input["id"]]); + $inner_sql .= $union . $wpdb->prepare($inner_sql_template, $input["id"], $form_id, $form_id, $input["id"] - 0.001, $input["id"] + 0.001, $value[ $input['id'] ], $value[ $input['id'] ] ); } } else{ - $inner_sql = $wpdb->prepare($inner_sql_template, $field["id"], $form_id, $form_id, doubleval($field["id"]) - 0.001, doubleval($field["id"]) + 0.001, $value); + $inner_sql = $wpdb->prepare($inner_sql_template, $field["id"], $form_id, $form_id, doubleval($field["id"]) - 0.001, doubleval($field["id"]) + 0.001, $value, $value ); } $sql .= $inner_sql . " @@ -3337,13 +3347,14 @@ public static function build_lead_array($results, $use_long_values = true){ $field_value = $result->value; //using long values if specified - if($use_long_values && strlen($field_value) >= (GFORMS_MAX_FIELD_LENGTH-10)){ - $field = RGFormsModel::get_field($form, $result->field_number); - $long_text = RGFormsModel::get_field_value_long($lead, $result->field_number, $form, false); + $field_number = (string)$result->field_number; + if($use_long_values && strlen($field_value) >= (GFORMS_MAX_FIELD_LENGTH-10)){ + $field = RGFormsModel::get_field($form, $field_number ); + $long_text = RGFormsModel::get_field_value_long($lead, $field_number, $form, false); $field_value = !empty($long_text) ? $long_text : $field_value; } - $lead[$result->field_number] = $field_value; + $lead[$field_number] = $field_value; $prev_lead_id = $result->id; } } @@ -4457,6 +4468,17 @@ public static function is_encrypted_field( $entry_id, $field_id ) { return in_array( $field_id, $encrypted_fields ); } + public static function delete_password( $entry, $form ) { + $password_fields = GFCommon::get_fields_by_type( $form, array( 'password' ) ); + if ( is_array( $password_fields ) ) { + foreach ( $password_fields as $password_field ) { + $entry[$password_field['id']] = ''; + } + } + GFAPI::update_entry( $entry ); + + return $entry; + } } class RGFormsModel extends GFFormsModel { } diff --git a/gravityforms.php b/gravityforms.php index aeefc57..375370e 100644 --- a/gravityforms.php +++ b/gravityforms.php @@ -3,7 +3,7 @@ Plugin Name: Gravity Forms Plugin URI: http://www.gravityforms.com Description: Easily create web forms and manage form entries within the WordPress admin. -Version: 1.8.16 +Version: 1.8.18 Author: rocketgenius Author URI: http://www.rocketgenius.com Text Domain: gravityforms @@ -107,7 +107,7 @@ class GFForms { - public static $version = '1.8.16'; + public static $version = '1.8.18'; public static function loaded(){ do_action( 'gform_loaded' ); @@ -1727,8 +1727,9 @@ public static function addons_page(){ } public static function get_addon_info($api, $action, $args){ - if($action == "plugin_information" && empty($api) && $args->slug == 'gravityforms'){ - $raw_response = GFCommon::post_to_manager("api.php", "op=get_plugin&slug=gravityforms", array()); + + if($action == "plugin_information" && empty($api) && ( !rgempty("rg", $_GET) || $args->slug == 'gravityforms')){ + $raw_response = GFCommon::post_to_manager("api.php", "op=get_plugin&slug={$args->slug}", array()); if ( is_wp_error( $raw_response ) || $raw_response['response']['code'] != 200) return false; @@ -1739,6 +1740,7 @@ public static function get_addon_info($api, $action, $args){ $api->name = $plugin["title"]; $api->version = $plugin["version"]; $api->download_link = $plugin["download_url"]; + $api->tested = '10.0'; } return $api; } diff --git a/includes/addon/class-gf-addon.php b/includes/addon/class-gf-addon.php index c2b56ba..3dc5d50 100644 --- a/includes/addon/class-gf-addon.php +++ b/includes/addon/class-gf-addon.php @@ -2873,7 +2873,7 @@ public function plugin_settings_page() { } public function plugin_settings_title(){ - return $this->get_short_title() . ' ' . __( "Settings", "gravityforms" ); + return sprintf( __( "%s Settings", "gravityforms" ), $this->get_short_title() ); } protected function plugin_settings_icon(){ diff --git a/includes/addon/class-gf-feed-addon.php b/includes/addon/class-gf-feed-addon.php index ea7c506..9c768ec 100644 --- a/includes/addon/class-gf-feed-addon.php +++ b/includes/addon/class-gf-feed-addon.php @@ -21,6 +21,7 @@ abstract class GFFeedAddOn extends GFAddOn { */ private $_feed_version = "0.11"; private $_feed_settings_fields = array(); + private $_current_feed_id = false; public function init_frontend() { @@ -118,37 +119,67 @@ protected function uninstall(){ //-------- Front-end methods --------------------------- - public function maybe_process_feed( $entry, $form, $is_delayed = false ) { + public function maybe_process_feed( $entry, $form ) { - //Getting all active feeds for current addon + //Getting all feeds for current add-on $feeds = $this->get_feeds( $form['id'] ); - - //Aborting if delayed payment is configured - if ( ! empty( $feeds ) ) { - $is_delayed_payment_configured = $this->is_delayed_payment( $entry, $form, $is_delayed ); - if ( $is_delayed_payment_configured ) { - $this->log_debug( "Feed processing delayed pending PayPal payment received for entry {$entry['id']}" ); + if ( empty( $feeds ) ){ + //no feeds to process + return $entry; + } + + if ( $entry['status'] == 'spam' ){ + $this->log_debug('Entry #' . $entry['id'] . ' is marked as spam.' ); + return $entry; + } - return $entry; + //get paypal feed to pass for delay check, must be done per add-on + $paypal_feeds = $this->get_feeds_by_slug( 'gravityformspaypal', $form['id'] ); + $active_paypal_feed = ''; + //loop through paypal feeds to get active one for this form submission, needed to see if add-on processing should be delayed + foreach ( $paypal_feeds as $paypal_feed ){ + if ( $paypal_feed['is_active'] && $this->is_feed_condition_met( $paypal_feed, $form, $entry ) ){ + $active_paypal_feed = $paypal_feed; + break; } } + $is_delayed = false; + if ( ! empty( $active_paypal_feed ) && $this->is_delayed( $active_paypal_feed ) && $this->has_paypal_payment( $active_paypal_feed, $form, $entry ) ) { + $this->log_debug( 'Feed processing is delayed pending payment, not processing feed for entry #' . $entry['id'] . ' for ' . $this->_slug ); + $is_delayed = true; + } + //Processing feeds $processed_feeds = array(); foreach ( $feeds as $feed ) { - if ( $feed['is_active'] && $this->is_feed_condition_met( $feed, $form, $entry ) ) { - $this->process_feed( $feed, $entry, $form ); - $processed_feeds[] = $feed['id']; - } else { - $this->log_debug( 'Opt-in condition not met or feed is inactive, not processing feed for entry #' . $entry['id'] . ". Feed Status: " . $feed['is_active'] ); - } + if ( ! $feed['is_active'] ) { + $this->log_debug( 'Feed is inactive, not processing feed for entry #' . $entry['id'] . ' for ' . $this->_slug ); + continue; + } + if ( ! $this->is_feed_condition_met( $feed, $form, $entry ) ){ + $this->log_debug( 'Feed condition not met, not processing feed for entry #' . $entry['id'] . ' for ' . $this->_slug ); + continue; + } + + $processed_feeds[] = $feed['id']; + + //process feed if not delayed + if ( !$is_delayed ) { + //all requirements met, process feed + $this->process_feed( $feed, $entry, $form ); + //should the add-on fulfill be done here???? + $this->log_debug( 'Marking entry ' . $entry['id'] . ' as fulfilled for ' . $this->_slug ); + gform_update_meta( $entry['id'], "{$this->_slug}_is_fulfilled", true ); + } + } //Saving processed feeds if( ! empty( $processed_feeds ) ){ $meta = gform_get_meta( $entry['id'], 'processed_feeds' ); - if( empty($meta) ) { + if( empty( $meta ) ) { $meta = array(); } @@ -160,9 +191,11 @@ public function maybe_process_feed( $entry, $form, $is_delayed = false ) { return $entry; } - public function get_feed_by_entry( $entry_id ) { - return gform_update_meta( $entry["id"], "processed_feeds", $meta ); - } + public function is_delayed( $paypal_feed ){ + //look for delay in paypal feed specific to add-on + $delay = rgar( $paypal_feed['meta'], 'delay_' . $this->_slug ); + return $delay; + } public function process_feed( $feed, $entry, $form ) { @@ -199,8 +232,70 @@ public function get_paypal_feed( $form_id, $entry ) { return $feed; } + public function has_paypal_payment( $feed, $form, $entry ){ + + $products = GFCommon::get_product_fields( $form, $entry ); + + $payment_field = $feed['meta']['transactionType'] == 'product' ? $feed['meta']['paymentAmount'] : $feed['meta']['recurringAmount']; + $setup_fee_field = rgar( $feed['meta'], 'setupFee_enabled' ) ? $feed['meta']['setupFee_product'] : false; + $trial_field = rgar( $feed['meta'], 'trial_enabled' ) ? rgars( $feed, 'meta/trial_product' ) : false; + + $amount = 0; + $line_items = array(); + $discounts = array(); + $fee_amount = 0; + $trial_amount = 0; + foreach ( $products['products'] as $field_id => $product ) { + + $quantity = $product['quantity'] ? $product['quantity'] : 1; + $product_price = GFCommon::to_number( $product['price'] ); + + $options = array(); + if ( is_array( rgar( $product, 'options' ) ) ) { + foreach ( $product['options'] as $option ) { + $options[] = $option['option_name']; + $product_price += $option['price']; + } + } + + $is_trial_or_setup_fee = false; + + if ( ! empty( $trial_field ) && $trial_field == $field_id ) { - //-------- Feed data methods ------------------------- + $trial_amount = $product_price * $quantity; + $is_trial_or_setup_fee = true; + + } else if ( ! empty( $setup_fee_field ) && $setup_fee_field == $field_id ) { + + $fee_amount = $product_price * $quantity; + $is_trial_or_setup_fee = true; + } + + //Do not add to line items if the payment field selected in the feed is not the current field. + if ( is_numeric( $payment_field ) && $payment_field != $field_id ) { + continue; + } + + //Do not add to line items if the payment field is set to "Form Total" and the current field was used for trial or setup fee. + if ( $is_trial_or_setup_fee && ! is_numeric( $payment_field ) ){ + continue; + } + + $amount += $product_price * $quantity; + + } + + + if ( ! empty( $products['shipping']['name'] ) && ! is_numeric( $payment_field ) ) { + $line_items[] = array( 'id' => '', 'name' => $products['shipping']['name'], 'description' => '', 'quantity' => 1, 'unit_price' => GFCommon::to_number( $products['shipping']['price'] ), 'is_shipping' => 1 ); + $amount += $products['shipping']['price']; + } + + return $amount > 0; + } + + + //-------- Feed data methods ------------------------- public function get_feeds( $form_id = null ){ global $wpdb; @@ -218,13 +313,37 @@ public function get_feeds( $form_id = null ){ return $results; } + public function get_feeds_by_slug ( $slug, $form_id = null ){ + global $wpdb; + + $form_filter = is_numeric( $form_id ) ? $wpdb->prepare( 'AND form_id=%d', absint( $form_id ) ) : ''; + + $sql = $wpdb->prepare("SELECT * FROM {$wpdb->prefix}gf_addon_feed + WHERE addon_slug=%s {$form_filter}", $slug); + + $results = $wpdb->get_results( $sql, ARRAY_A ); + foreach( $results as &$result ){ + $result['meta'] = json_decode( $result['meta'], true ); + } + + return $results; + } + public function get_current_feed(){ $feed_id = $this->get_current_feed_id(); return empty($feed_id) ? false : $this->get_feed( $feed_id ); } public function get_current_feed_id(){ - return rgempty('gf_feed_id') ? rgget("fid") : rgpost('gf_feed_id'); + if ( $this->_current_feed_id ){ + return $this->_current_feed_id; + } + else if ( ! rgempty('gf_feed_id') ){ + return rgpost('gf_feed_id'); + } + else{ + return rgget('fid'); + } } public function get_feed( $id ) { @@ -339,7 +458,7 @@ public function form_settings_header(){ } public function form_settings_title(){ - return $this->_title . " " . __("Feeds", "gravityforms"); + return sprintf( __("%s Feeds", "gravityforms"), $this->_title ); } protected function feed_edit_page($form, $feed_id) { @@ -347,6 +466,8 @@ protected function feed_edit_page($form, $feed_id) { // Save feed if appropriate $feed_id = $this->maybe_save_feed_settings( $feed_id, $form['id'] ); + $this->_current_feed_id = $feed_id; + ?>

    feed_settings_title() ?>

    @@ -358,6 +479,7 @@ protected function feed_edit_page($form, $feed_id) { $feed = $this->get_feed( $feed_id ); $this->set_settings( $feed['meta'] ); + GFCommon::display_admin_message(); $this->render_settings( $this->get_feed_settings_fields($form) ); @@ -421,7 +543,7 @@ public function get_feed_table( $form ) { public function feed_list_title(){ $url = add_query_arg(array("fid" => "0")); - return $this->get_short_title() . " " . __("Feeds", "gravityforms") . " " . __("Add New", "gravityforms") . ""; + return sprintf( __("%s Feeds", "gravityforms"), $this->get_short_title() ) . " " . __("Add New", "gravityforms") . ""; } protected function maybe_save_feed_settings( $feed_id, $form_id ){ @@ -440,19 +562,16 @@ protected function maybe_save_feed_settings( $feed_id, $form_id ){ $is_valid = $this->validate_settings( $sections, $settings ); $result = false; - if( $is_valid ) - $result = $this->save_feed_settings( $feed_id, $form_id, $settings ); + if( $is_valid ) { + $feed_id = $this->save_feed_settings( $feed_id, $form_id, $settings ); + } - if( $result ) { + if( $feed_id ) { GFCommon::add_message( $this->get_save_success_message($sections) ); } else { GFCommon::add_error_message( $this->get_save_error_message($sections) ); } - // if no $feed_id is passed, assume that a new feed was created and return new $feed_id - if( ! $feed_id ) - $feed_id = $result; - return $feed_id; } @@ -480,7 +599,7 @@ protected function save_feed_settings( $feed_id, $form_id, $settings ) { if( $feed_id ) { $this->update_feed_meta( $feed_id, $settings ); - $result = true; + $result = $feed_id; } else { $result = $this->insert_feed( $form_id, true, $settings ); } @@ -691,12 +810,13 @@ protected function add_delayed_payment_support( $options ) { $this->delayed_payment_integration = $options; if( is_admin() ) { - add_action( 'gform_paypal_action_fields', array( $this, 'add_paypal_settings' ), 10, 2); + + add_action( 'gform_paypal_action_fields', array( $this, 'add_paypal_settings' ), 10, 2); add_filter( 'gform_paypal_save_config', array( $this, 'save_paypal_settings' ) ); - } else { - add_action( 'gform_paypal_fulfillment', array( $this, 'paypal_fulfillment' ), 10, 4 ); + } + add_action( 'gform_paypal_fulfillment', array( $this, 'paypal_fulfillment' ), 10, 4 ); } public function add_paypal_settings( $feed, $form ) { @@ -755,52 +875,36 @@ public function save_paypal_settings( $feed ) { return $feed; } - public function paypal_fulfillment( $entry, $config, $transaction_id, $amount ) { - - self::log_debug( "Checking PayPal fulfillment for transaction {$transaction_id}" ); + public function paypal_fulfillment( $entry, $paypal_config, $transaction_id, $amount ) { + $this->log_debug( 'Checking PayPal fulfillment for transaction ' . $transaction_id . ' for ' . $this->_slug ); $is_fulfilled = gform_get_meta( $entry['id'], "{$this->_slug}_is_fulfilled" ); + if( $is_fulfilled ){ + $this->log_debug( 'Entry ' . $entry['id'] . ' is already fulfilled for ' . $this->_slug . '. No action necessary.' ); + return false; + } - if ( !$is_fulfilled ) { - - self::log_debug( "Entry {$entry['id']} has not been fulfilled." ); - $form = RGFormsModel::get_form_meta( $entry['form_id'] ); - $this->maybe_process_feed( $entry, $form, true ); - - // updating meta to indicate this entry has been fulfilled for the current add-on - self::log_debug( "Marking entry {$entry['id']} as fulfilled" ); - gform_update_meta( $entry['id'], "{$this->_slug}_is_fulfilled", true ); + $form = RGFormsModel::get_form_meta( $entry['form_id'] ); - } else { - self::log_debug( "Entry {$entry['id']} is already fulfilled." ); - } + $feed_to_process = ''; + $feeds = $this->get_feeds( $entry['form_id'] ); + foreach ( $feeds as $feed ){ + if ( $feed['is_active'] && $this->is_feed_condition_met( $feed, $form, $entry ) ){ + $feed_to_process = $feed; + break; + } + } + if ( empty( $feed_to_process ) ){ + $this->log_debug( 'No active feeds found or feeds did not meet conditional logic for ' . $this->_slug . '. No fulfillment necessary.' ); + return false; + } + $this->process_feed( $feed_to_process, $entry, $form ); + // updating meta to indicate this entry has been fulfilled for the current add-on + $this->log_debug( 'Marking entry ' . $entry['id'] . ' as fulfilled for ' . $this->_slug ); + gform_update_meta( $entry['id'], "{$this->_slug}_is_fulfilled", true ); } - public static function get_paypal_payment_amount($form, $entry, $paypal_config){ - - $products = GFCommon::get_product_fields($form, $entry, true); - $recurring_field = rgar($paypal_config["meta"], "recurring_amount_field"); - $total = 0; - foreach($products["products"] as $id => $product){ - - if($paypal_config["meta"]["type"] != "subscription" || $recurring_field == $id || $recurring_field == "all"){ - $price = GFCommon::to_number($product["price"]); - if(is_array(rgar($product,"options"))){ - foreach($product["options"] as $option){ - $price += GFCommon::to_number($option["price"]); - } - } - - $total += $price * $product['quantity']; - } - } - - if($recurring_field == "all" && !empty($products["shipping"]["price"])) - $total += floatval($products["shipping"]["price"]); - - return $total; - } protected function has_feed( $form_id, $meets_conditional_logic = null ) { @@ -824,22 +928,6 @@ protected function has_feed( $form_id, $meets_conditional_logic = null ) { //does not require that feed meets conditional logic. return true since there are feeds return true; } - - protected function is_delayed_payment( $entry, $form, $is_delayed ) { - if ( $this->_slug == 'gravityformspaypal' ) { - return false; - } - - $paypal_feed = $this->get_paypal_feed( $form['id'], $entry ); - if ( ! $paypal_feed ) { - return false; - } - - $has_payment = self::get_paypal_payment_amount( $form, $entry, $paypal_feed ) > 0; - - return rgar( $paypal_feed['meta'], "delay_{$this->_slug}" ) && $has_payment && ! $is_delayed; - } - } diff --git a/includes/addon/class-gf-payment-addon.php b/includes/addon/class-gf-payment-addon.php index d009923..14265f8 100644 --- a/includes/addon/class-gf-payment-addon.php +++ b/includes/addon/class-gf-payment-addon.php @@ -619,7 +619,7 @@ protected function get_order_data($feed, $form, $entry){ } if ($trial_field == "enter_amount"){ - $trial_amount = rgar($feed["meta"], "trial_amount") ? rgar($feed["meta"], "trial_amount") : 0; + $trial_amount = rgar($feed["meta"], "trial_amount") ? GFCommon::to_number(rgar($feed["meta"], "trial_amount")) : 0; } if(!empty($products["shipping"]["name"]) && !is_numeric($payment_field)){ @@ -828,7 +828,7 @@ public function add_pending_payment( $entry, $action ){ return true; } - public function complete_payment( $entry, $action ) { + public function complete_payment( &$entry, $action ) { if ( ! rgar($action, 'payment_status') ) { $action['payment_status'] = 'Paid'; @@ -1150,7 +1150,7 @@ public function feed_settings_fields() { array("label" => __("Products and Services", "gravityforms"), "value" => "product"), array("label" => __("Subscription", "gravityforms"), "value" => "subscription") ), - "tooltip" => "
    " . __("Transaction Type", "gravityforms") . "
    " . __("Select a transaction type") + 'tooltip' => '
    ' . __( 'Transaction Type', 'gravityforms' ) . '
    ' . __( 'Select a transaction type', 'gravityforms' ) ) ) ), @@ -1193,7 +1193,7 @@ public function feed_settings_fields() { "label" => __("Trial", "gravityforms"), "type" => "trial", "hidden" => $this->get_setting("setupFee_enabled"), - "tooltip" => "
    " . __("Trial Period", "gravityformspaypal") . "
    " . __("Enable a trial period. The users recurring payment will not begin until after this trial period.", "gravityforms") + "tooltip" => "
    " . __("Trial Period", "gravityforms") . "
    " . __("Enable a trial period. The users recurring payment will not begin until after this trial period.", "gravityforms") ) ) ), @@ -1466,8 +1466,8 @@ protected function get_payment_choices($form){ public function get_results_page_config() { return array( - "title" => "Sales", - "search_title" => "Filter", + "title" => _x( 'Sales', 'toolbar label', 'gravityforms' ), + "search_title" => _x( 'Filter', 'metabox title', 'gravityforms' ), "capabilities" => array("gravityforms_view_entries"), "callbacks" => array( "fields" => array($this, "results_fields"), @@ -1867,7 +1867,7 @@ public function results_filter_ui($filter_ui, $form_id, $page_title, $gf_page, $ $payment_method_markup = "