Commit c7cb0518 authored by Kevin Stover's avatar Kevin Stover

Merge branch 'develop'

parents d7afc5c3 1bcc4071
......@@ -4,7 +4,7 @@
Tags: form, forms, contact form, custom form, form builder, form creator, form manager, form creation, contact forms, custom forms, forms builder, forms creator, forms manager, forms creation, form administration,
Requires at least: 4.5
Tested up to: 4.7
Stable tag: 3.0.20
Stable tag: 3.0.21
License: GPLv2 or later
With a simple drag and drop interface you can create contact forms, email subscription forms, order forms, payment forms, and any other type of form for your WordPress site.
......
......@@ -758,6 +758,12 @@ All styles used within the drawer
content: "\f147";
font: 400 30px/1 dashicons; }
.ninja-forms-app .jBox-wrapper.import-options {
padding: 12px 20px 0px !important; }
.ninja-forms-app .jBox-wrapper.import-options.jBox-pointerPosition-top.jBox-closeButton-box:before {
right: 10px; }
.ninja-forms-app .nf-list-options {
padding-bottom: 20px; }
.ninja-forms-app .nf-list-options .nf-add-new {
......@@ -802,6 +808,18 @@ All styles used within the drawer
color: #999;
margin-top: 10px; }
.ninja-forms-app .options {
position: relative; }
.ninja-forms-app .options legend .nf-open-import-tooltip {
background: #EBEDEE;
color: #1EA9EA;
font-size: 16px;
padding: 6px 15px;
text-decoration: none;
position: absolute;
right: 15px;
top: 0; }
.nf-drawer-buttons {
display: none;
margin: 0 -20px;
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -111,6 +111,9 @@ define( [], function() {
var name = model.get( 'name' );
var value = dataModel.get( name );
if( ! value ) return;
var rubble = value.split( ':' );
if( 'addField' != rubble[0] ) return;
......
......@@ -136,7 +136,7 @@ define( [], function() {
}
for( var setting in settings ){
if( null === settings[ setting ] || '' == settings[ setting ] ) {
if( null === settings[ setting ] || '' === settings[ setting ] ) {
delete settings[setting];
}
}
......
......@@ -11,7 +11,7 @@ define( [ 'models/app/optionRepeaterCollection' ], function( ListOptionCollectio
initialize: function() {
this.listenTo( nfRadio.channel( 'option-repeater-option-label' ), 'update:option', this.updateOptionLabel );
this.listenTo( nfRadio.channel( 'option-repeater-option-value' ), 'update:option', this.updateOptionValue );
/*
* When we init our model, convert our options from an array of objects to a collection of models.
*/
......
define( ['views/app/drawer/optionRepeaterOption', 'views/app/drawer/optionRepeaterEmpty', 'models/app/optionRepeaterCollection'], function( listOptionView, listEmptyView, listOptionCollection ) {
var view = Marionette.CompositeView.extend( {
template: '#tmpl-nf-edit-setting-wrap',
template: '#tmpl-nf-edit-setting-option-repeater-wrap',
childView: listOptionView,
emptyView: listEmptyView,
reorderOnSort: false,
......@@ -79,6 +79,34 @@ define( ['views/app/drawer/optionRepeaterOption', 'views/app/drawer/optionRepeat
* Send out a radio message.
*/
nfRadio.channel( 'setting-' + this.model.get( 'name' ) ).trigger( 'render:setting', this.model, this.dataModel, this );
},
onAttach: function() {
var importLink = jQuery( this.el ).find( '.nf-open-import-tooltip' );
var jBox = jQuery( importLink ).jBox( 'Tooltip', {
title: '<h3>Please enter your options below:</h3>',
content: jQuery( this.el ).find( '.nf-import-options' ),
trigger: 'click',
closeOnClick: 'body',
closeButton: 'box',
offset: { x: 20, y: 0 },
addClass: 'import-options',
onOpen: function() {
var that = this;
setTimeout( function() { jQuery( that.content ).find( 'textarea' ).focus(); }, 200 );
}
} );
jQuery( this.el ).find( '.nf-import' ).on( 'click', { view: this, jBox: jBox }, this.clickImport );
/*
* Send out a radio message.
*/
nfRadio.channel( 'setting-' + this.model.get( 'name' ) ).trigger( 'attach:setting', this.model, this.dataModel, this );
nfRadio.channel( 'setting-type-' + this.model.get( 'type' ) ).trigger( 'attach:setting', this.model, this.dataModel, this );
},
templateHelpers: function () {
......@@ -149,13 +177,76 @@ define( ['views/app/drawer/optionRepeaterOption', 'views/app/drawer/optionRepeat
},
events: {
'click .nf-add-new': 'clickAddOption'
'click .nf-add-new': 'clickAddOption',
'click .extra': 'clickExtra'
},
clickAddOption: function( e ) {
nfRadio.channel( 'option-repeater' ).trigger( 'click:addOption', this.collection, this.dataModel );
jQuery( this.children.findByIndex(this.children.length - 1).el ).find( '[data-id="label"]' ).focus();
}
},
clickExtra: function( e ) {
nfRadio.channel( 'option-repeater' ).trigger( 'click:extra', e, this.collection, this.dataModel );
nfRadio.channel( 'option-repeater-' + this.model.get( 'name' ) ).trigger( 'click:extra', e, this.model, this.collection, this.dataModel );
},
clickImport: function( e ) {
var textarea = jQuery( e.data.jBox.content ).find( 'textarea' );
var value = textarea.val().trimLeft().trimRight();
/*
* Return early if we have no strings.
*/
if ( 0 == value.length ) {
e.data.jBox.close();
return false;
}
/*
* Split our value based on new lines.
*/
var lines = value.split(/\n/);
if ( _.isArray( lines ) ) {
/*
* Loop over
*/
_.each( lines, function( line ) {
var row = line.split( ',' );
var label = row[0];
var value = row[1] || jQuery.slugify( label, { separator: '-' } );
var calc = row[2] || '';
label = label.trimLeft().trimRight();
value = value.trimLeft().trimRight();
calc = calc.trimLeft().trimRight();
/*
* Add our row to the collection
*/
var model = e.data.view.collection.add( { label: row[0], value: value, calc: calc } );
// Add our field addition to our change log.
var label = {
object: 'field',
label: row[0],
change: 'Option Added',
dashicon: 'plus-alt'
};
nfRadio.channel( 'changes' ).request( 'register:change', 'addListOption', model, null, label );
nfRadio.channel( 'option-repeater-' + e.data.view.model.get( 'name' ) ).trigger( 'add:option', model );
nfRadio.channel( 'option-repeater' ).trigger( 'add:option', model );
nfRadio.channel( 'app' ).trigger( 'update:setting', model );
}, this );
/*
* Set our state to unclean so that the user can publish.
*/
} else {
/*
* TODO: Error Handling Here
*/
}
textarea.val( '' );
e.data.jBox.close();
},
} );
return view;
......
define( [], function() {
var view = Marionette.ItemView.extend({
tagName: 'div',
template: '#tmpl-nf-import-options',
initialize: function() {
this.listenTo( nfRadio.channel( 'setting-type-options' ), 'click:extra', this.maybeOpenImport );
},
onAttach: function() {
console.log( jQuery( this.el ).parent().parent() );
},
renderImportButton: function() {
},
onBeforeDestroy: function() {
// this.model.off( 'change:addSavedLoading', this.render );
},
events: {
'click .nf-button': 'clickImportOptions'
},
clickImportOptions: function( e ) {
nfRadio.channel( 'drawer' ).trigger( 'click:ImportOptions', e, this.model );
},
maybeOpenImport: function( e, settingModel, DataModel, settingView ) {
console.log( e );
}
});
return view;
} );
......@@ -55,7 +55,7 @@ define( [], function() {
opacity: 0.95,
grid: [ 5, 5 ],
// scroll: false,
appendTo: '#nf-builder',
appendTo: '#nf-main',
scrollSensitivity: 10,
receive: function( e, ui ) {
......
......@@ -332,6 +332,7 @@ define(['models/calcCollection'], function( CalcCollection ) {
changeCalc: function( calcModel, targetCalcModel ) {
var eqValues = this.replaceAllKeys( calcModel );
eqValues = eqValues.replace( '[', '' ).replace( ']', '' );
calcModel.set( 'value', math.eval( eqValues ) );
}
});
......
define([], function() {
var controller = Marionette.Object.extend( {
initialize: function() {
this.listenTo( nfRadio.channel( 'listselect' ), 'init:model', this.register );
this.listenTo( nfRadio.channel( 'liststate' ), 'init:model', this.register );
this.listenTo( nfRadio.channel( 'listcountry' ), 'init:model', this.register );
this.listenTo( nfRadio.channel( 'listmultiselect' ), 'init:model', this.register );
this.listenTo( nfRadio.channel( 'fields' ), 'init:model', function( model ){
if( 'list' == model.get( 'parentType' ) ) this.register( model );
}, this );
nfRadio.channel( 'listselect' ).reply( 'get:calcValue', this.getCalcValue, this );
nfRadio.channel( 'listmultiselect' ).reply( 'get:calcValue', this.getCalcValue, this );
},
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -106,6 +106,12 @@
}
}
.ninja-forms-app {
.jBox-wrapper.import-options {
padding: 12px 20px 0px !important;
}
.jBox-wrapper.import-options.jBox-pointerPosition-top.jBox-closeButton-box:before {
right: 10px;
}
.nf-list-options {
padding-bottom: 20px;
.nf-add-new {
......@@ -178,6 +184,21 @@
}
}
}
.options {
position: relative;
legend {
.nf-open-import-tooltip {
background: #EBEDEE;
color: $cta_color;
font-size: 16px;
padding: 6px 15px;
text-decoration: none;
position: absolute;
right: 15px;
top: 0;
}
}
}
}
.nf-drawer-buttons {
display: none;
......
......@@ -17,7 +17,6 @@ function ninja_forms_register_field_recaptcha() {
'edit_custom_class' => false,
'edit_help' => false,
'edit_meta' => false,
'sidebar' => 'template_fields',
'edit_conditional' => true,
'conditional' => array(
'action' => array(
......@@ -51,15 +50,22 @@ function ninja_forms_field_recaptcha_display( $field_id, $data, $form_id = '' )
$lang = $settings['recaptcha_lang'];
$siteKey = $settings['recaptcha_site_key'];
$field_class = ninja_forms_get_field_class( $field_id, $form_id );
$rand = wp_rand(0, 99999);
wp_enqueue_script(
'g-recaptcha',
'https://www.google.com/recaptcha/api.js?onload=ninja_forms_grecaptcha_explicit_render&render=explicit&hl='.$lang,
['ninja-forms-display'] );
if ( !empty( $siteKey ) ) { ?>
<input id="ninja_forms_field_<?php echo $field_id;?>" name="ninja_forms_field_<?php echo $field_id;?>" type="hidden" class="<?php echo $field_class;?>" value="" rel="<?php echo $field_id;?>" />
<div class="g-recaptcha" data-callback="nf_recaptcha_set_field_value" data-sitekey="<?php echo $siteKey; ?>"></div>
<script type="text/javascript" src="https://www.google.com/recaptcha/api.js?hl=<?php echo $lang; ?>"> </script>
<input id="ninja_forms_field_<?php echo $rand;?>" name="ninja_forms_field_<?php echo
$field_id;?>" type="hidden" class="<?php echo $field_class;?>" value="" rel="<?php echo $field_id;?>"
/>
<div class="g-recaptcha" data-callback="nf_recaptcha_set_field_value"
data-sitekey="<?php echo $siteKey; ?>"></div>
<script type="text/javascript">
function nf_recaptcha_set_field_value(inpval){
jQuery("#ninja_forms_field_<?php echo $field_id;?>").val(inpval)
}
</script>
function nf_recaptcha_set_field_value(inpval){
jQuery("#ninja_forms_field_<?php echo $rand;?>").val(inpval);
}
</script>
<?php
}
}
......
......@@ -999,3 +999,11 @@ function ninja_forms_var_operator(op) {
}
}
}
function ninja_forms_grecaptcha_explicit_render(){
jQuery('.g-recaptcha').each(function(){
grecaptcha.render(this, {
'sitekey':jQuery(this).data('sitekey')
});
});
}
\ No newline at end of file
......@@ -265,7 +265,7 @@ class Ninja_Forms {
// Plugin version
if ( ! defined( 'NF_PLUGIN_VERSION' ) )
define( 'NF_PLUGIN_VERSION', '3.0.20' );
define( 'NF_PLUGIN_VERSION', '3.0.21' );
// Plugin Folder Path
if ( ! defined( 'NF_PLUGIN_DIR' ) )
......
......@@ -270,7 +270,7 @@ class NF_AJAX_Controllers_Submission extends NF_Abstracts_Controller
if( ! method_exists( $action_class, 'process' ) ) continue;
if( $data = $action_class->process($action[ 'settings' ], $this->_form_id, $this->_data ) ){
$this->_data = $data;
$this->_data = apply_filters( 'ninja_forms_post_run_action_type_' . $action[ 'settings' ][ 'type' ], $data );
}
// $this->_data[ 'actions' ][ $type ][] = $action;
......
......@@ -47,6 +47,8 @@ abstract class NF_Abstracts_MergeTags
foreach( $this->merge_tags as $merge_tag ){
if( ! in_array( $merge_tag[ 'tag' ], $matches[0] ) ) continue;
if( ! isset($merge_tag[ 'callback' ])) continue;
$replace = ( is_callable( array( $this, $merge_tag[ 'callback' ] ) ) ) ? $this->{$merge_tag[ 'callback' ]}() : '';
$subject = str_replace( $merge_tag[ 'tag' ], $replace, $subject );
......
......@@ -220,18 +220,38 @@ class NF_Abstracts_Model
// If the ID is not set, then we cannot pull settings from the Database.
if( ! $this->_id ) return $this->_settings;
$form_cache = get_option( 'nf_form_' . $this->_parent_id );
if( $form_cache ){
if( ! $this->_settings && 'field' == $this->_type ) {
global $wpdb;
$results = $wpdb->get_results(
"
SELECT Meta.key, Meta.value
FROM $this->_table_name as Object
JOIN $this->_meta_table_name as Meta
ON Object.id = Meta.parent_id
WHERE Object.id = '$this->_id'
"
, ARRAY_A );
foreach( $results as $result ) {
$key = $result[ 'key' ];
$this->_settings[ $key ] = $result[ 'value' ];
}
}
if( ! $this->_settings ) {
$form_cache = get_option('nf_form_' . $this->_parent_id);
if ($form_cache) {
if( 'field'== $this->_type ) {
if ('field' == $this->_type) {
if (isset($form_cache[ 'fields' ])) {
if (isset($form_cache['fields'])) {
foreach ($form_cache[ 'fields' ] as $object) {
if ($this->_id != $object[ 'id' ]) continue;
foreach ($form_cache['fields'] as $object) {
if ($this->_id != $object['id']) continue;
$this->update_settings($object['settings']);
break;
$this->update_settings($object['settings']);
break;
}
}
}
}
......
......@@ -273,6 +273,7 @@ class NF_Abstracts_ModelFactory
$actions = $model_shell->find($form_id, $where);
foreach ($actions as $action) {
$action->get_setting( 'type' ); // Pre-load the type of action for usort()
$this->_actions[$action->get_id()] = $action;
}
}
......
......@@ -47,6 +47,8 @@ final class NF_Actions_Email extends NF_Abstracts_Action
public function process( $action_settings, $form_id, $data )
{
$errors = $this->check_for_errors( $action_settings );
$headers = $this->_get_headers( $action_settings );
$attachments = $this->_get_attachments( $action_settings, $data );
......@@ -59,22 +61,48 @@ final class NF_Actions_Email extends NF_Abstracts_Action
$message = apply_filters( 'ninja_forms_action_email_message', $message, $data, $action_settings );
$sent = wp_mail(
$action_settings['to'],
$action_settings['email_subject'],
$message,
$headers,
$attachments
);
try {
$sent = wp_mail($action_settings['to'], $action_settings['email_subject'], $message, $headers, $attachments);
} catch ( Exception $e ){
$sent = false;
$errors[ 'email_sent' ] = $e->getMessage();
}
$data[ 'actions' ][ 'email' ][ 'to' ] = $action_settings['to'];
$data[ 'actions' ][ 'email' ][ 'sent' ] = $sent;
$data[ 'actions' ][ 'email' ][ 'headers' ] = $headers;
$data[ 'actions' ][ 'email' ][ 'attachments' ] = $attachments;
if( $errors ){
$data[ 'errors' ][ 'form' ] = $errors;
}
return $data;
}
protected function check_for_errors( $action_settings )
{
$errors = array();
$email_address_settings = array( 'to', 'from_address', 'reply_to', 'cc', 'bcc' );
foreach( $email_address_settings as $setting ){
if( ! isset( $action_settings[ $setting ] ) ) continue;
if( ! $action_settings[ $setting ] ) continue;
$email_addresses = is_array( $action_settings[ $setting ] ) ? $action_settings[ $setting ] : explode( ',', $action_settings[ $setting ] );
foreach( (array) $email_addresses as $email ){
$email = trim( $email );
if( ! is_email( $email ) ) {
$errors[ 'email_' . $email ] = sprintf( __( 'Your email action "%s" has an invalid value for the "TO" setting. Please check this setting and try again.', 'ninja-forms'), $action_settings[ 'label' ] );
}
}
}
return $errors;
}
private function _get_headers( $settings )
{
$headers = array();
......
......@@ -79,17 +79,13 @@ class NF_Admin_CPT_DownloadAllSubmissions extends NF_Step_Processing {
$myfile = fopen( $file_path, 'a' ) or die( 'Unable to open file!' );
$x = 0;
$export = '';
foreach ( $subs_results as $sub ) {
$sub_export = NF_Database_Models_Submission::export( $this->args['form_id'], array( $sub->ID ), TRUE );
if ( $x > 0 || $this->step > 1 ) {
$sub_export = substr( $sub_export, strpos( $sub_export, "\n" ) + 1 );
}
if ( ! in_array( $sub->ID, $exported_subs ) ) {
$export .= $sub_export;
$exported_subs[] = $sub->ID;
}
$x++;
$sub_ids = array();
foreach( $subs_results as $result ){
$sub_ids[] = $result->ID;
}
$export .= NF_Database_Models_Submission::export( $this->args['form_id'], $sub_ids, TRUE );
fwrite( $myfile, $export );
fclose( $myfile );
}
......
......@@ -115,14 +115,13 @@ class NF_Admin_CPT_Submission
public function post_row_actions( $actions, $sub )
{
if( $this->cpt_slug == get_post_type() ){
if ( $this->cpt_slug == get_post_type() ){
unset( $actions[ 'view' ] );
unset( $actions[ 'inline hide-if-no-js' ] );
$export_url = add_query_arg( array( 'action' => 'export', 'post[]' => $sub->ID ) );
$actions[ 'export' ] = sprintf( '<a href="%s">%s</a>', $export_url, __( 'Export', 'ninja-forms' ) );
}
$export_url = add_query_arg( array( 'action' => 'export', 'post[]' => $sub->ID ) );
$actions[ 'export' ] = sprintf( '<a href="%s">%s</a>', $export_url, __( 'Export', 'ninja-forms' ) );
return $actions;
}
......@@ -132,6 +131,10 @@ class NF_Admin_CPT_Submission
$form_id = absint( $_GET[ 'form_id' ] );
static $columns;
if( $columns ) return $columns;
$columns = array(
'cb' => '<input type="checkbox" />',
'id' => __( '#', 'ninja-forms' ),
......@@ -178,7 +181,12 @@ class NF_Admin_CPT_Submission
if( is_numeric( $column ) ){
$value = $sub->get_field_value( $column );
$field = Ninja_Forms()->form()->get_field( $column );
static $fields;
if( ! isset( $fields[ $column ] ) ) {
$fields[$column] = Ninja_Forms()->form()->get_field($column);
}
$field = $fields[$column];
echo apply_filters( 'ninja_forms_custom_columns', $value, $field, $sub_id );
}
......
......@@ -77,6 +77,10 @@ final class NF_Admin_Menus_Submissions extends NF_Abstracts_Submenu
if( ! $form_id ) return array();
static $cols;
if( $cols ) return $cols;
$cols = array(
'cb' => '<input type="checkbox" />',
'seq_num' => __( '#', 'ninja-forms' ),
......
......@@ -150,7 +150,7 @@ return apply_filters( 'ninja_forms_field_settings', array(
'options' => array(
'name' => 'options',
'type' => 'option-repeater',
'label' => __( 'Options', 'ninja-forms' ) . ' <a href="#" class="nf-add-new">' . __( 'Add New', 'ninja-forms' ) . '</a>',
'label' => __( 'Options', 'ninja-forms' ) . ' <a href="#" class="nf-add-new">' . __( 'Add New', 'ninja-forms' ) . '</a> <a href="#" class="extra nf-open-import-tooltip"><i class="fa fa-sign-in" aria-hidden="true"></i> ' . __( 'Import', 'ninja-forms' ) . '</a>',
'width' => 'full',
'group' => 'primary',
// 'value' => 'option-repeater',
......
......@@ -302,6 +302,9 @@ final class NF_Database_Models_Submission
$hidden_field_types = apply_filters( 'nf_sub_hidden_field_types', array() );
$fields_order_by = array();
$field_type_filters = array();
$i = 0;
foreach( $fields as $field ){
if( in_array( $field->get_setting( 'type' ), $hidden_field_types ) ) continue;
......@@ -310,9 +313,14 @@ final class NF_Database_Models_Submission
} else {
$field_labels[ $field->get_id() ] = $field->get_setting( 'label' );
}
}
$fields_order_by[] = "'_field_{$field->get_id()}'";
if( has_filter( 'ninja_forms_subs_export_field_value_' . $field->get_setting( 'type' ) ) ){
// $i represents the relative field order for later reference when running filters on a specific value.
$field_type_filters[ $i ] = $field->get_setting( 'type' );
}
$i++;
}
/*
* Submissions
......@@ -329,22 +337,53 @@ final class NF_Database_Models_Submission
$value[ '_seq_num' ] = $sub->get_seq_num();
$value[ '_date_submitted' ] = $sub->get_sub_date( $date_format );
foreach( $field_labels as $field_id => $label ){
if( has_filter( 'nf_subs_export_pre_value' ) || has_filter( 'ninja_forms_subs_export_pre_value' ) ) {
/*
* DEPRECATED - Individual value filters are inefficient.
*/
foreach ($field_labels as $field_id => $label) {
if (!is_int($field_id)) continue;
if( ! is_int( $field_id ) ) continue;
$field_value = $sub->get_field_value($field_id);
$field_value = apply_filters('nf_subs_export_pre_value', $field_value, $field_id);
$field_value = apply_filters('ninja_forms_subs_export_pre_value', $field_value, $field_id, $form_id);
$field_value = $sub->get_field_value( $field_id );
$field_value = apply_filters( 'nf_subs_export_pre_value', $field_value, $field_id );
$field_value = apply_filters( 'ninja_forms_subs_export_pre_value', $field_value, $field_id, $form_id );
if (is_array($field_value)) {
$field_value = implode(' | ', $field_value);
}
if( is_array( $field_value ) ){
$field_value = implode( ' | ', $field_value );
$value[$field_id] = $field_value;
}
$value[ $field_id ] = $field_value;
}
$value_array[] = $value;
} else {
$value_array[] = $value;
/*
* OPTIMIZED
*/