Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/ninja-forms/blocks/bootstrap.php
+++ b/ninja-forms/blocks/bootstrap.php
@@ -56,6 +56,24 @@
'editor_script' => 'ninja-forms/submissions-table/block',
'render_callback' => function ($attributes, $content) {
if (isset($attributes['formID']) && $attributes['formID']) {
+
+ // SECURITY: For non-published posts (draft previews, pending review, etc.),
+ // require submission-viewing capability before issuing a token.
+ // This prevents Contributor/Author users from obtaining tokens by adding
+ // a submissions-table block to a draft post and previewing it.
+ // Published pages intentionally issue tokens to all viewers (admin chose to
+ // display submissions publicly by placing the block on a published page).
+ $current_post = get_post();
+ if ( $current_post && 'publish' !== $current_post->post_status ) {
+ $views_capability = apply_filters(
+ 'ninja_forms_views_token_capability',
+ apply_filters( 'ninja_forms_admin_submissions_capabilities', 'manage_options' )
+ );
+ if ( ! current_user_can( $views_capability ) ) {
+ return '';
+ }
+ }
+
wp_enqueue_script('ninja-forms/submissions-table/render');
// Generate a token bound to THIS specific form ID only
--- a/ninja-forms/build/fields.asset.php
+++ b/ninja-forms/build/fields.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('react', 'react-jsx-runtime', 'wp-components', 'wp-element', 'wp-i18n'), 'version' => '64a0fe501fbd48d0be7b');
+<?php return array('dependencies' => array('react', 'react-jsx-runtime', 'wp-components', 'wp-element', 'wp-i18n'), 'version' => '8d5c3fdab9777bdcc175');
--- a/ninja-forms/build/sub-table-block.asset.php
+++ b/ninja-forms/build/sub-table-block.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('react', 'react-jsx-runtime', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n'), 'version' => '40e4d6388fcf0076cb79');
+<?php return array('dependencies' => array('react', 'react-jsx-runtime', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n'), 'version' => '51bd0efc68dcb72c6605');
--- a/ninja-forms/build/submissions.asset.php
+++ b/ninja-forms/build/submissions.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('lodash', 'react', 'react-jsx-runtime', 'wp-api-fetch', 'wp-components', 'wp-data', 'wp-dom-ready', 'wp-element', 'wp-i18n'), 'version' => 'd3e49e628d354dbe2a80');
+<?php return array('dependencies' => array('lodash', 'react', 'react-jsx-runtime', 'wp-api-fetch', 'wp-components', 'wp-data', 'wp-dom-ready', 'wp-element', 'wp-i18n'), 'version' => '78187cf16774a30784be');
--- a/ninja-forms/includes/Admin/Processes/ExpiredSubmissionCleanup.php
+++ b/ninja-forms/includes/Admin/Processes/ExpiredSubmissionCleanup.php
@@ -126,7 +126,7 @@
$sub = Ninja_Forms()->form( $form_id )->get_subs();
foreach( $sub as $sub_model ) {
// Get the sub date and change it to a UNIX time stamp.
- $sub_timestamp = strtotime( $sub_model->get_sub_date( 'Y-m-d') );
+ $sub_timestamp = strtotime( $sub_model->get_sub_date( 'Y-m-d H:i:s') );
// Compare our timestamps and any expired subs to the array.
if( $sub_timestamp <= $deletion_timestamp ) {
$expired_subs[] = $sub_model->get_id();
--- a/ninja-forms/includes/Admin/Processes/ExportSubmissions.php
+++ b/ninja-forms/includes/Admin/Processes/ExportSubmissions.php
@@ -42,6 +42,11 @@
protected $csvObject;
/**
+ * @var NinjaFormsIncludesHandlersSubmissionAggregateCsvExportAdapter
+ */
+ protected $submissionAdapter;
+
+ /**
* Aggregated submission keys in output order
*
* @var array
@@ -95,6 +100,7 @@
$submissionAggregateCsvAdapter = (new SubmissionAggregateFactory())->SubmissionAggregateCsvExportAdapter();
$submissionAggregateCsvAdapter->submissionAggregate->filterSubmissions($params);
+ $this->submissionAdapter = $submissionAggregateCsvAdapter;
$this->csvObject = (new NF_Exports_SubmissionCsvExport())->setUseAdminLabels(true)->setSubmissionAggregateCsvExportAdapter($submissionAggregateCsvAdapter);
$this->indexedLookup = $this->csvObject->reverseSubmissionOrder();
@@ -115,13 +121,32 @@
if (!$file = fopen($this->file_path, 'w')) {
$this->add_error('write_failure', esc_html__('Unable to write file.', 'ninja-forms'), 'fatal');
$this->batch_complete();
+ return;
}
- // Add headers to the file.
+ // Add headers to the file with extra columns from filter.
// We can only do this outside of the process method under the assumption that a single form ID is provided.
$labels = $this->csvObject->getLabels();
+
+ // Apply filter to get extra column headers (e.g., payment data columns)
+ $csv_array = [
+ 0 => [0 => $labels], // Headers
+ 1 => [0 => []] // Empty rows for header filter call
+ ];
+ $csv_array = apply_filters('nf_subs_csv_extra_values', $csv_array, [], $this->form);
+
+ // Defensive validation: ensure filter returned valid structure for headers
+ if (!is_array($csv_array) || !isset($csv_array[0][0]) || !is_array($csv_array[0][0])) {
+ error_log('Ninja Forms: nf_subs_csv_extra_values filter returned invalid data for headers');
+ $csv_array = [
+ 0 => [0 => $labels],
+ 1 => [0 => []]
+ ];
+ }
+ $filtered_labels = $csv_array[0][0];
+
$glue = $this->enclosure . $this->delimiter . $this->enclosure;
- $constructed = $this->enclosure . implode($glue, $labels) . $this->enclosure . $this->terminator;
+ $constructed = $this->enclosure . implode($glue, $filtered_labels) . $this->enclosure . $this->terminator;
fwrite($file, $constructed);
fclose($file);
}
@@ -141,11 +166,11 @@
* Function to loop over the batch.
*
* @since 3.5.0
- * @return void
+ * @return void
*/
public function process()
{
- if($this->currentPosition >= $this->sub_count-1){
+ if($this->currentPosition >= $this->sub_count){
$this->batch_complete();
return;
}
@@ -154,7 +179,6 @@
// Continue looping until end
$this->process();
-
}
/**
@@ -166,40 +190,73 @@
parent::batch_complete();
}
- public function writeBatch( ): void
+ public function writeBatch(): void
{
- if (!$file = fopen($this->file_path, 'a')) {
- $this->add_error('write_failure', esc_html__('Unable to write file.', 'ninja-forms'), 'fatal');
- $this->batch_complete();
- }
+ // Collect this batch of rows and submission objects for filter
+ $batch_rows = [];
+ $batch_subs = [];
- $glue = $this->enclosure . $this->delimiter . $this->enclosure;
-
- // for each submission within the step
for ($i = 0; $i < $this->subs_per_step; $i++) {
if (!isset($this->indexedLookup[$this->currentPosition])) {
- continue;
+ break;
}
$aggregatedKey = $this->indexedLookup[$this->currentPosition];
$row = $this->csvObject->constructRow($aggregatedKey);
- //Catch reference to an array or repeated fieldsets of repeater field to display each entry as a row
- if( array_key_exists('repeater', $row) && is_array($row['repeater']) ){
- foreach($row['repeater'] as $eachRow){
- $constructed = $this->enclosure . implode($glue, $eachRow) . $this->enclosure . $this->terminator;
- fwrite($file, $constructed);
+ // Handle repeater fields - they create multiple rows
+ if (array_key_exists('repeater', $row) && is_array($row['repeater'])) {
+ foreach ($row['repeater'] as $eachRow) {
+ $batch_rows[] = $eachRow;
}
- $this->currentPosition++;
} else {
- $constructed = $this->enclosure . implode($glue, $row) . $this->enclosure . $this->terminator;
- fwrite($file, $constructed);
-
- $this->currentPosition++;
+ $batch_rows[] = $row;
}
-
+ // Get submission object for filter
+ $submissionId = $this->submissionAdapter
+ ->submissionAggregate
+ ->getSubmissionValuesByAggregatedKey($aggregatedKey)
+ ->getSubmissionRecordId();
+ $batch_subs[] = Ninja_Forms()->form()->get_sub($submissionId);
+
+ $this->currentPosition++;
+ }
+
+ // Apply nf_subs_csv_extra_values filter to this batch
+ // Filter expects: $csv_array[0][0] = headers, $csv_array[1][0] = rows
+ $csv_array = [
+ 0 => [0 => $this->csvObject->getLabels()], // Headers
+ 1 => [0 => $batch_rows] // Batch rows
+ ];
+
+ $csv_array = apply_filters('nf_subs_csv_extra_values', $csv_array, $batch_subs, $this->form);
+
+ // Defensive validation: ensure filter returned valid structure for rows
+ if (!is_array($csv_array) || !isset($csv_array[1][0]) || !is_array($csv_array[1][0])) {
+ error_log('Ninja Forms: nf_subs_csv_extra_values filter returned invalid data for rows');
+ $csv_array = [
+ 0 => [0 => $this->csvObject->getLabels()],
+ 1 => [0 => $batch_rows]
+ ];
+ }
+
+ // Write filtered rows to file
+ $file = fopen($this->file_path, 'a');
+ if (!$file) {
+ $this->add_error('write_failure', esc_html__('Unable to write file.', 'ninja-forms'), 'fatal');
+ $this->batch_complete();
+ return;
}
+
+ $glue = $this->enclosure . $this->delimiter . $this->enclosure;
+
+ foreach ($csv_array[1][0] as $filtered_row) {
+ $constructed = $this->enclosure . implode($glue, $filtered_row) . $this->enclosure . $this->terminator;
+ fwrite($file, $constructed);
+ }
+
+ fclose($file);
}
/**
* Method that encodes $this->response and sends the data to the front-end.
--- a/ninja-forms/includes/Admin/Processes/ImportForm.php
+++ b/ninja-forms/includes/Admin/Processes/ImportForm.php
@@ -296,14 +296,16 @@
$insert_columns_types = array();
foreach ( $this->forms_db_columns as $column_name => $setting_name ) {
- // Make sure we don't try to set created_at to NULL.
- if( 'created_at' === $column_name && (!isset($this->form[ 'settings' ][ $setting_name ]) || is_null( $this->form[ 'settings' ][ $setting_name ] ) ) ) continue;
-
$formColumnName = null;
if(isset($this->form[ 'settings' ][ $setting_name ])){
$formColumnName = $this->form[ 'settings' ][ $setting_name ];
- }
+ }
+
+ // If created_at is not set or is NULL, set it to the current timestamp
+ if( 'created_at' === $column_name && (is_null( $formColumnName ) || empty( $formColumnName )) ) {
+ $formColumnName = current_time( 'mysql' );
+ }
$insert_columns[ $column_name ] = $formColumnName;
--- a/ninja-forms/includes/Database/Models/Form.php
+++ b/ninja-forms/includes/Database/Models/Form.php
@@ -309,11 +309,11 @@
// Duplicate the Form Object.
$wpdb->query( $wpdb->prepare(
"
- INSERT INTO {$wpdb->prefix}nf3_forms ( `title` )
- SELECT CONCAT( `title`, ' - ', %s )
- FROM {$wpdb->prefix}nf3_forms
+ INSERT INTO {$wpdb->prefix}nf3_forms ( `title`, `created_at` )
+ SELECT CONCAT( `title`, ' - ', %s ), %s
+ FROM {$wpdb->prefix}nf3_forms
WHERE id = %d;
- ", esc_html__( 'copy', 'ninja-forms' ), $form_id
+ ", esc_html__( 'copy', 'ninja-forms' ), current_time( 'mysql' ), $form_id
) );
$new_form_id = $wpdb->insert_id;
--- a/ninja-forms/includes/Database/SubmissionExpirationCron.php
+++ b/ninja-forms/includes/Database/SubmissionExpirationCron.php
@@ -74,7 +74,7 @@
$sub = Ninja_Forms()->form( $form_id )->get_subs();
foreach( $sub as $sub_model ) {
// Get the sub date and change it to a UNIX time stamp.
- $sub_timestamp = strtotime( $sub_model->get_sub_date( 'Y-m-d') );
+ $sub_timestamp = strtotime( $sub_model->get_sub_date( 'Y-m-d H:i:s') );
// Compare our timestamps and any expired subs to the array.
if( $sub_timestamp <= $deletion_timestamp ) {
$expired_subs[] = $sub_model->get_id();
--- a/ninja-forms/includes/Fields/Checkbox.php
+++ b/ninja-forms/includes/Fields/Checkbox.php
@@ -19,7 +19,7 @@
protected $_test_value = 0;
- protected $_settings = array( 'checkbox_default_value', 'checkbox_values', 'checked_calc_value', 'unchecked_calc_value' );
+ protected $_settings = array( 'checkbox_default_value', 'checkbox_values', 'checked_value', 'unchecked_value', 'checked_calc_value', 'unchecked_calc_value' );
protected $_settings_exclude = array( 'default', 'placeholder', 'input_limit_set' );
@@ -40,6 +40,8 @@
add_filter( 'ninja_forms_merge_tag_value_' . $this->_name, array( $this, 'filter_merge_tag_value' ), 10, 2 );
add_filter( 'ninja_forms_merge_tag_calc_value_' . $this->_name, array( $this, 'filter_merge_tag_value_calc' ), 10, 2 );
add_filter( 'ninja_forms_subs_export_field_value_' . $this->_type, array( $this, 'export_value' ), 10, 2 );
+ add_filter( 'ninja_forms_localize_field_' . $this->_name, array( $this, 'localize_field' ) );
+ add_filter( 'ninja_forms_localize_field_' . $this->_name . '_preview', array( $this, 'localize_field' ) );
}
/**
@@ -112,10 +114,27 @@
*/
public function filter_merge_tag_value( $value, $field )
{
- // If value is true, return checked value setting.
- if( $field[ 'settings' ][ 'value' ] ) return $field[ 'settings' ][ 'checked_value' ];
- // Else return unchecked value setting.
- return $field[ 'settings' ][ 'unchecked_value' ];
+ $default_checked = esc_html__( 'Checked', 'ninja-forms' );
+ $default_unchecked = esc_html__( 'Unchecked', 'ninja-forms' );
+
+ // Check for display values first, fall back to calc values
+ if( $value ) {
+ // Checkbox is checked
+ if ( isset( $field[ 'settings' ][ 'checked_value' ] ) && ! empty( $field[ 'settings' ][ 'checked_value' ] ) && $field[ 'settings' ][ 'checked_value' ] !== $default_checked ) {
+ return $field[ 'settings' ][ 'checked_value' ];
+ } elseif ( isset( $field[ 'settings' ][ 'checked_calc_value' ] ) && $field[ 'settings' ][ 'checked_calc_value' ] !== '' ) {
+ return $field[ 'settings' ][ 'checked_calc_value' ];
+ }
+ return $default_checked;
+ } else {
+ // Checkbox is unchecked
+ if ( isset( $field[ 'settings' ][ 'unchecked_value' ] ) && ! empty( $field[ 'settings' ][ 'unchecked_value' ] ) && $field[ 'settings' ][ 'unchecked_value' ] !== $default_unchecked ) {
+ return $field[ 'settings' ][ 'unchecked_value' ];
+ } elseif ( isset( $field[ 'settings' ][ 'unchecked_calc_value' ] ) && $field[ 'settings' ][ 'unchecked_calc_value' ] !== '' ) {
+ return $field[ 'settings' ][ 'unchecked_calc_value' ];
+ }
+ return $default_unchecked;
+ }
}
/**
@@ -130,7 +149,7 @@
public function filter_merge_tag_value_calc( $value, $field )
{
// If checkbox is checked (using same logic as filter_merge_tag_value)...
- if ( ! empty( $field[ 'settings' ][ 'value' ] ) ) {
+ if ( ! empty( $value ) ) {
// ...return the checked calc value of the field model.
return $field[ 'checked_calc_value' ];
} else {
@@ -181,4 +200,40 @@
return esc_html__( 'unchecked', 'ninja-forms' );
}
}
+
+ /**
+ * Localize Field
+ * Pass checkbox display values to JavaScript for front-end merge tag replacement.
+ * Exposes resolved checked/unchecked values at the top level so
+ * fieldModel.get('checked_value') works in JavaScript.
+ *
+ * @since 3.14.2
+ *
+ * @param array $field
+ * @return array
+ */
+ public function localize_field( $field )
+ {
+ $default_checked = esc_html__( 'Checked', 'ninja-forms' );
+ $default_unchecked = esc_html__( 'Unchecked', 'ninja-forms' );
+
+ // Check for display values first, fall back to calc values if not set
+ if ( isset( $field['settings']['checked_value'] ) && ! empty( $field['settings']['checked_value'] ) && $field['settings']['checked_value'] !== $default_checked ) {
+ $field['checked_value'] = $field['settings']['checked_value'];
+ } elseif ( isset( $field['settings']['checked_calc_value'] ) && $field['settings']['checked_calc_value'] !== '' ) {
+ $field['checked_value'] = $field['settings']['checked_calc_value'];
+ } else {
+ $field['checked_value'] = $default_checked;
+ }
+
+ if ( isset( $field['settings']['unchecked_value'] ) && ! empty( $field['settings']['unchecked_value'] ) && $field['settings']['unchecked_value'] !== $default_unchecked ) {
+ $field['unchecked_value'] = $field['settings']['unchecked_value'];
+ } elseif ( isset( $field['settings']['unchecked_calc_value'] ) && $field['settings']['unchecked_calc_value'] !== '' ) {
+ $field['unchecked_value'] = $field['settings']['unchecked_calc_value'];
+ } else {
+ $field['unchecked_value'] = $default_unchecked;
+ }
+
+ return $field;
+ }
}
--- a/ninja-forms/includes/Fields/Textarea.php
+++ b/ninja-forms/includes/Fields/Textarea.php
@@ -38,6 +38,20 @@
public function filter_csv_value( $field_value, $field ) {
+ // Decode HTML entities for Rich Text Editor fields
+ // This ensures formatted text displays correctly in CSV exports
+ $textarea_rte = false;
+
+ if ( is_object( $field ) ) {
+ $textarea_rte = $field->get_setting( 'textarea_rte' );
+ } elseif ( is_array( $field ) && isset( $field['settings']['textarea_rte'] ) ) {
+ $textarea_rte = $field['settings']['textarea_rte'];
+ }
+
+ if ( $textarea_rte ) {
+ $field_value = html_entity_decode( $field_value, ENT_QUOTES | ENT_HTML5, 'UTF-8' );
+ }
+
$field_value = WPN_Helper::maybe_escape_csv_column( $field_value );
return $field_value;
--- a/ninja-forms/includes/MergeTags/Fields.php
+++ b/ninja-forms/includes/MergeTags/Fields.php
@@ -231,7 +231,7 @@
$list_fields_types = array('listcheckbox', 'listmultiselect', 'listradio', 'listselect');
- if (is_array($field['value']) && $field['type'] !== "repeater") $field['value'] = implode(',', $field['value']);
+ if (is_array($field['value']) && $field['type'] !== "repeater" && $field['type'] !== 'file_upload') $field['value'] = implode(',', $field['value']);
$field['value'] = $this->stripShortcodesMaybeFieldset($field_id,$field['value']);
--- a/ninja-forms/includes/Routes/Submissions.php
+++ b/ninja-forms/includes/Routes/Submissions.php
@@ -12,7 +12,7 @@
/**
* Class NF_Routes_SubmissionsActions
*/
-final class NF_Routes_Submissions extends NF_Abstracts_Routes
+class NF_Routes_Submissions extends NF_Abstracts_Routes
{
/**
@@ -565,28 +565,50 @@
'fields' => [],
'fields_by_key' => [],
'settings'=>$formSettings,
- 'form_id'=>$data->formID
+ 'form_id'=>$data->formID,
+ 'extra' => []
];
- foreach($field_values as $index => $field_value){
+ // Reconstruct field data and process each field through its type handler
+ foreach($field_values as $index => $field_value){
$id = str_replace('_field_', '', $index);
$model = Ninja_Forms()->form($data->formID)->get_field( $id );
$settings = $model->get_settings();
+
+ // Get the RAW field value from post meta (not imploded)
+ // This preserves arrays for checkbox lists, etc.
+ $raw_value = get_post_meta($sub->get_id(), '_field_' . $id, TRUE);
+
+ // Use raw value if it exists, otherwise use the retrieved value
+ $field_value = $raw_value !== '' ? $raw_value : $field_value;
+
+ // Create field array with proper structure
+ $field = array(
+ 'id' => $id,
+ 'settings' => $settings
+ );
+
+ // Add value to both top level and settings
+ $field['value'] = $field_value;
+ $field['settings']['value'] = $field_value;
+
+ // Flatten the field array (merge settings to top level)
+ $field = array_merge( $field, $field['settings'] );
+
+ // Process the field through its type-specific handler
+ $this->process_retriggered_field( $field, $data_send );
+
+ // Store processed field data
if($id === $index){
- $data_send['fields_by_key'][$id] = $settings;
- $data_send['fields_by_key'][$id]['settings'] = $settings;
- $data_send['fields_by_key'][$id]['value'] = $field_value;
- $data_send['fields_by_key'][$id]['settings']['value'] = $field_value;
+ $data_send['fields_by_key'][$id] = $field;
} else {
- $data_send['fields'][$id] = $settings;
- $data_send['fields'][$id]['settings'] = $settings;
- $data_send['fields'][$id]['value'] = $field_value;
- $data_send['fields'][$id]['settings']['value'] = $field_value;
+ $data_send['fields'][$id] = $field;
+ $data_send['fields_by_key'][$field['key']] = $field;
}
}
-
- //Process Merge tags
- $action_settings = $this->process_merge_tags( $data->action_settings, $data->formID, $sub );
+
+ //Process Merge tags (calculations will be processed inside this method)
+ $action_settings = $this->process_merge_tags( $data->action_settings, $data->formID, $sub, $data_send, $formSettings );
//Process Email Action
$email_action = new NF_Actions_Email();
$result = $email_action->process( (array) $action_settings, $data->formID, $data_send );
@@ -599,38 +621,141 @@
}
/**
+ * Process a field through its type-specific handler
+ *
+ * Similar to process_field() in AJAX/Controllers/Submission.php
+ * but adapted for email retriggering context
+ *
+ * @since 3.7.0
+ * @param array $field_settings Field settings array
+ * @param array $form_data Form data array (for reference compatibility)
+ * @return void
+ */
+ protected function process_retriggered_field( $field_settings, &$form_data ) {
+ if( ! is_string( $field_settings['type'] ) ) return;
+
+ if( ! isset( Ninja_Forms()->fields[ $field_settings['type'] ] ) ) return;
+
+ $field_class = Ninja_Forms()->fields[ $field_settings['type'] ];
+
+ // If $field_class is not object or string, return without checking for method_exists
+ if(!is_object($field_class) && !is_string($field_class)){
+ return;
+ }
+
+ if( ! method_exists( $field_class, 'process' ) ) return;
+
+ if( $data = $field_class->process( $field_settings, $form_data ) ){
+ $form_data = $data;
+ }
+ }
+
+ /**
* Process Merge tags for a given Value
- *
+ *
* @since 3.4.33
- *
+ * @param object $data Action settings data
+ * @param int $form_id Form ID
+ * @param object $sub Submission object
+ * @param array $form_data Optional form data with processed fields
+ * @param array $formSettings Optional form settings (for calculation processing)
+ *
* @return object of Email Action Model with merge tags settingsprocessed
- *
+ *
*/
- public function process_merge_tags( $data, $form_id, $sub) {
-
+ public function process_merge_tags( $data, $form_id, $sub, $form_data = null, $formSettings = null) {
+
+ // Get form settings for merge tag initialization
+ if( ! $formSettings ) {
+ $formSettings = Ninja_Forms()->form($form_id)->get_settings();
+ }
+
+ // Init Form Merge Tags
+ $form_merge_tags = Ninja_Forms()->merge_tags[ 'form' ];
+ $form_merge_tags->set_form_id( $form_id );
+ $form_merge_tags->set_form_title( $formSettings['title'] );
+
// Init Field Merge Tags.
$fields_merge_tag_object = Ninja_Forms()->merge_tags[ 'fields' ];
$fields_merge_tag_object->set_form_id($form_id);
-
+
+ // Init Calc Merge Tags
+ $calcs_merge_tags = Ninja_Forms()->merge_tags[ 'calcs' ];
+
//Process Fields Merge Tags
- $fields = Ninja_Forms()->form( $form_id )->get_fields();
- $fields = new NF_Adapters_SubmissionsSubmission( $fields, $form_id, $sub );
- foreach( $fields as $field ){
- $fields_merge_tag_object->add_field( $field );
+ // If we have processed form data, use those fields directly (they're already processed)
+ if( $form_data && isset( $form_data['fields'] ) ) {
+ foreach( $form_data['fields'] as $field ){
+ $fields_merge_tag_object->add_field( $field );
+ }
+ } else {
+ // Fallback to adapter method (for backward compatibility)
+ $fields = Ninja_Forms()->form( $form_id )->get_fields();
+ $fields = new NF_Adapters_SubmissionsSubmission( $fields, $form_id, $sub );
+ foreach( $fields as $field ){
+ $fields_merge_tag_object->add_field( $field );
+ }
}
//Add All Fields merge tags
$fields_merge_tag_object->include_all_fields_merge_tags();
//include fields to the {all_fields_table} and {fields_table} mrerge tags
- foreach( $fields as $field ){
- $fields_merge_tag_object->add_field( $field );
+ if( $form_data && isset( $form_data['fields'] ) ) {
+ foreach( $form_data['fields'] as $field ){
+ $fields_merge_tag_object->add_field( $field );
+ }
+ } else {
+ // Fallback to adapter method
+ $fields = isset($fields) ? $fields : new NF_Adapters_SubmissionsSubmission( Ninja_Forms()->form( $form_id )->get_fields(), $form_id, $sub );
+ foreach( $fields as $field ){
+ $fields_merge_tag_object->add_field( $field );
+ }
}
+
+ // Process calculations AFTER field merge tags are initialized
+ // This allows calculation equations to use field merge tags
+ if( $form_data && isset( $formSettings['calculations'] ) && !empty( $formSettings['calculations'] ) ) {
+ $form_data['extra']['calculations'] = [];
+
+ foreach( $formSettings['calculations'] as $calc ){
+ // Apply filter which replaces merge tags in the equation
+ $eq = apply_filters( 'ninja_forms_calc_setting', $calc['eq'] );
+
+ // Scrub unmerged tags (ie deleted/non-existent fields/calcs, etc).
+ $eq = preg_replace( '/{([a-zA-Z0-9]|:|_|-)*}/', 0, $eq);
+
+ // PHP doesn't evaluate empty strings to numbers. Check for decimal place string
+ $dec = ( isset( $calc['dec'] ) && '' != $calc['dec'] ) ? $calc['dec'] : 2;
+
+ // Initialize calc merge tags with processed equation
+ $calcs_merge_tags->set_merge_tags(
+ $calc['name'],
+ $eq,
+ $dec,
+ isset($formSettings['decimal_point']) ? $formSettings['decimal_point'] : '.',
+ isset($formSettings['thousands_sep']) ? $formSettings['thousands_sep'] : ','
+ );
+
+ // Store calculation data
+ $form_data['extra']['calculations'][ $calc['name'] ] = array(
+ 'raw' => $calc['eq'],
+ 'parsed' => $eq,
+ 'value' => $calcs_merge_tags->get_formatted_calc_value(
+ $calc['name'],
+ $dec,
+ isset($formSettings['decimal_point']) ? $formSettings['decimal_point'] : '.',
+ isset($formSettings['thousands_sep']) ? $formSettings['thousands_sep'] : ','
+ )
+ );
+ }
+ }
+
//Loop through Action settings and apply merge tags
$array_data = (array) $data;
foreach( $array_data as $ind => $value ){
if( !empty($value) && is_string($value) ){
//Merge tag
$data->$ind = apply_filters( 'ninja_forms_merge_tags', $value );
- }
+ }
}
return $data;
--- a/ninja-forms/ninja-forms.php
+++ b/ninja-forms/ninja-forms.php
@@ -3,7 +3,7 @@
Plugin Name: Ninja Forms
Plugin URI: http://ninjaforms.com/?utm_source=WordPress&utm_medium=readme
Description: Ninja Forms is a webform builder with unparalleled ease of use and features.
-Version: 3.14.1
+Version: 3.14.2
Author: Saturday Drive
Author URI: http://ninjaforms.com/?utm_source=Ninja+Forms+Plugin&utm_medium=Plugins+WP+Dashboard
Text Domain: ninja-forms
@@ -43,7 +43,7 @@
* @since 3.0
*/
- const VERSION = '3.14.1';
+ const VERSION = '3.14.2';
/**
* @since 3.4.0
--- a/ninja-forms/vendor/composer/installed.php
+++ b/ninja-forms/vendor/composer/installed.php
@@ -1,9 +1,9 @@
<?php return array(
'root' => array(
'name' => 'saturday-drive/ninja-forms',
- 'pretty_version' => 'dev-c6cdcdc02d670ebeebc34bd3025b804b17e57d75',
- 'version' => 'dev-c6cdcdc02d670ebeebc34bd3025b804b17e57d75',
- 'reference' => 'c6cdcdc02d670ebeebc34bd3025b804b17e57d75',
+ 'pretty_version' => 'dev-967b2552a0c9b6be68ab12a9d8c6d6b2b289f54b',
+ 'version' => 'dev-967b2552a0c9b6be68ab12a9d8c6d6b2b289f54b',
+ 'reference' => '967b2552a0c9b6be68ab12a9d8c6d6b2b289f54b',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -11,9 +11,9 @@
),
'versions' => array(
'saturday-drive/ninja-forms' => array(
- 'pretty_version' => 'dev-c6cdcdc02d670ebeebc34bd3025b804b17e57d75',
- 'version' => 'dev-c6cdcdc02d670ebeebc34bd3025b804b17e57d75',
- 'reference' => 'c6cdcdc02d670ebeebc34bd3025b804b17e57d75',
+ 'pretty_version' => 'dev-967b2552a0c9b6be68ab12a9d8c6d6b2b289f54b',
+ 'version' => 'dev-967b2552a0c9b6be68ab12a9d8c6d6b2b289f54b',
+ 'reference' => '967b2552a0c9b6be68ab12a9d8c6d6b2b289f54b',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),