Commit 2c6d7fdd authored by Kyle B. Johnson's avatar Kyle B. Johnson

Merge master into txnmail.

parents 69b789a3 656d5815
......@@ -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.7
Tested up to: 4.9
Stable tag: 3.2.16
Stable tag: 3.2.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.
......
......@@ -35,3 +35,8 @@
border-bottom: 1px solid black;
margin-bottom: 0;
}
.alignleft.actions input[type=text] {
height: 28px;
border-radius: 4px;
}
\ No newline at end of file
......@@ -10,4 +10,58 @@
border-bottom: 1px solid;
margin-bottom: 5px;
padding: 4px;
}
.nf-filter-input {
border: 1px solid #ddd;
border-radius: 5px;
box-shadow: inset 0 1px 2px rgba(0,0,0,.07);
padding: 0;
width: 100%;
margin-top: 10px;
margin-bottom: 10px;
position: relative;
}
.nf-filter-input .blocks-base-control {
margin: 0 0 0 0;
}
.nf-filter-input-el {
border: none !important;
box-shadow: none !important;
width: 95%;
position: relative;
display: inline-block;
}
.nf-filter-input-icon {
display: inline-block;
content: '▾';
color: #999;
font-size: 14px;
right: 8px;
position: absolute;
top: 3px;
}
.nf-filter-option-container {
display: none;
position: absolute;
background-color: #edeff0;
z-index: 10000;
padding: 4px;
width: 100%;
border-radius: 5px;
max-height: 200px;
overflow: scroll;
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12)!important;
}
.nf-filter-option {
list-style: none;
}
.nf-filter-option:hover {
background-color: #0073aa;
color: white;
}
\ No newline at end of file
jQuery(document).ready(function($) {
var working = false;
var message, container, messageBox, deleteInput, deleteMsgs, buttons, confirm, cancel, lineBreak;
container = document.createElement( 'div' );
messageBox = document.createElement( 'p' );
deleteInput = document.createElement( 'input' );
deleteInput.type = 'text';
deleteInput.id = 'confirmDeleteInput';
buttons = document.createElement( 'div' );
buttons.style.marginTop = '10px';
buttons.style.backgroundColor = '#f4f5f6';
confirm = document.createElement( 'div' );
confirm.style.padding = '8px';
confirm.style.cursor = 'default';
confirm.style.backgroundColor = '#d9534f';
confirm.style.borderColor = '#d9534f';
confirm.style.fontSize = '14pt';
confirm.style.fontWeight = 'bold';
confirm.style.color = '#ffffff';
confirm.style.borderRadius = '4px';
cancel = document.createElement( 'div' );
cancel.style.padding = '8px';
cancel.style.cursor = 'default';
cancel.style.backgroundColor = '#5bc0de';
cancel.style.borderColor = '#5bc0de';
cancel.style.fontSize = '14pt';
cancel.style.fontWeight = 'bold';
cancel.style.color = '#ffffff';
cancel.style.borderRadius = '4px';
lineBreak = document.createElement( 'br' );
container.classList.add( 'message' );
messageBox.innerHTML += 'This will DELETE all forms, form submissions,' +
' and deactivate Ninja Forms';
messageBox.appendChild( lineBreak );
messageBox.innerHTML += '<br>Type <span style="color:red;">DELETE</span>' +
' to' +
' confirm';
container.appendChild( messageBox );
container.appendChild( deleteInput );
container.appendChild( lineBreak );
deleteMsgs = document.createElement( 'div' );
deleteMsgs.id = 'progressMsg';
deleteMsgs.style.display = 'none';
deleteMsgs.style.color = 'red';
container.appendChild( deleteMsgs );
confirm.innerHTML = 'Delete';
confirm.classList.add( 'confirm', 'nf-button', 'primary' );
confirm.style.float = 'left';
cancel.innerHTML = 'Cancel';
cancel.classList.add( 'cancel', 'nf-button', 'secondary', 'cancel-delete-all' );
cancel.style.float = 'right';
buttons.appendChild( confirm );
buttons.appendChild( cancel );
buttons.classList.add( 'buttons' );
container.appendChild( buttons );
message = document.createElement( 'div' );
message.appendChild( container );
// set up delete model with all the elements created above
deleteAllDataModal = new jBox( 'Modal', {
width: 450,
addClass: 'dashboard-modal',
overlay: true,
closeOnClick: 'body'
} );
deleteAllDataModal.setContent( message.innerHTML );
deleteAllDataModal.setTitle( 'Delete All Ninja Forms Data?' );
// add event listener for cancel button
var btnCancel = deleteAllDataModal.container[0].getElementsByClassName('cancel')[0];
btnCancel.addEventListener('click', function() {
if( ! working ) {
deleteAllDataModal.close();
}
} );
var doAllDataDeletions = function( formIndex ) {
var last_form = 0;
// Gives the user confidence things are happening
$( '#progressMsg' ).html( 'Deleting submissions for '
+ nf_settings.forms[ formIndex ].title + "" + ' ( ID: '
+ nf_settings.forms[ formIndex ].id + ' )' );
$( '#progressMsg').show();
// notify php this is the last one so it delete data and deactivate NF
if( formIndex === nf_settings.forms.length - 1 ) {
last_form = 1;
}
// do this deletion thang
$.post(
nf_settings.ajax_url,
{
'action': 'nf_delete_all_data',
'form': nf_settings.forms[ formIndex ].id,
'security': nf_settings.nonce,
'last_form': last_form
}
).then (function( response ) {
formIndex = formIndex + 1;
response = JSON.parse( response );
// we expect success and then move to the next form
if( response.data.success ) {
if( formIndex < nf_settings.forms.length ) {
doAllDataDeletions( formIndex )
} else {
// if we're finished deleting data then redirect to plugins
if( response.data.plugin_url ) {
window.location = response.data.plugin_url;
}
}
}
} ).fail( function( xhr, status, error ) {
// writes error messages to console to help us debug
console.log( xhr.status + ' ' + error + '\r\n' +
'There was an error deleting submissions for '
+ nf_settings.forms[ formIndex ].title );
});
};
// Add event listener for delete button
var btnDelete = deleteAllDataModal.container[0].getElementsByClassName('confirm')[0];
btnDelete.addEventListener('click', function() {
var confirmVal = $('#confirmDeleteInput').val();
if (! working) {
working = true;
// Gotta type DELETE to play
if ('DELETE' === confirmVal) {
this.style.backgroundColor = '#9f9f9f';
this.style.borderColor = '#3f3f3f';
var cancelBtn = $( '.cancel-delete-all' );
cancelBtn.css( 'backgroundColor', '#9f9f9f' );
cancelBtn.css( 'borderColor', '#3f3f3f');
// this is the first one, so we'll start with index 0
doAllDataDeletions(0);
} else {
deleteAllDataModal.close();
working = false;
}
}
} );
$( '.js-delete-saved-field' ).click( function(){
var that = this;
......@@ -23,4 +167,15 @@ jQuery(document).ready(function($) {
event.preventDefault();
}
});
$( document ).on( 'click', '#delete_on_uninstall', function( e ) {
deleteAllDataModal.open();
} );
$( document ).on( 'click', '.nf-delete-on-uninstall-yes', function( e ) {
e.preventDefault();
$( "#delete_on_uninstall" ).attr( 'checked', true );
} );
});
......@@ -43,7 +43,7 @@ jQuery( document ).ready( function( $ ) {
}
});
$( '.nf-form-option' ).off( 'click' ).on( 'click', function() { console.log('hello');
$( '.nf-form-option' ).off( 'click' ).on( 'click', function() {
// on click get the value of the input
var val = $( this ).data( 'val' );
// nf_export_form is now a hidden field instead of select element
......
......@@ -17,11 +17,32 @@ define( [], function() {
nfRadio.channel( 'app' ).request( 'update:db' );
nfRadio.channel( 'app' ).request( 'update:setting', 'clean', true );
nfRadio.channel( 'app' ).request( 'close:drawer' );
this.dispatchClick();
},
undoSingle: function( change, undoAll ) {
nfRadio.channel( 'changes' ).request( 'undo:' + change.get( 'action' ), change, undoAll );
}
this.dispatchClick();
},
dispatchClick: function() {
// If we already have a cookie, exit.
if ( document.cookie.includes( 'nf_undo' ) ) return;
// Otherwise, prepare our cookie.
var cname = "nf_undo";
var d = new Date();
// Set expiration at 1 week.
d.setTime( d.getTime() + ( 7*24*60*60*1000 ) );
var expires = "expires="+ d.toUTCString();
// Bake the cookie.
document.cookie = cname + "=1;" + expires + ";path=/";
var data = {
action: 'nf_undo_click',
security: nfAdmin.ajaxNonce
}
// Make our AJAX call.
jQuery.post( ajaxurl, data );
}
});
......
......@@ -8,6 +8,7 @@
*/
define( [], function() {
var controller = Marionette.Object.extend( {
initialize: function() {
// Listen for the closing of the drawer and update when it's closed.
this.listenTo( nfRadio.channel( 'drawer' ), 'closed', this.updateDB );
......@@ -29,6 +30,7 @@ define( [], function() {
* @return void
*/
updateDB: function( action ) {
// If our app is clean, dont' update.
if ( nfRadio.channel( 'app' ).request( 'get:setting', 'clean' ) ) {
return false;
......@@ -42,6 +44,8 @@ define( [], function() {
var jsAction = 'nf_preview_update';
} else if ( 'publish' == action ) {
var jsAction = 'nf_save_form';
// now using a different ajax action
// var jsAction = 'nf_batch_process';
}
var formModel = nfRadio.channel( 'app' ).request( 'get:formModel' );
......@@ -205,6 +209,7 @@ define( [], function() {
// Turn our object into a JSON string.
data = JSON.stringify( data );
// Run anything that needs to happen before we update.
nfRadio.channel( 'app' ).trigger( 'before:updateDB', data );
......@@ -218,31 +223,141 @@ define( [], function() {
}
}
// Update
jQuery.post( ajaxurl, { action: jsAction, form: data, security: nfAdmin.ajaxNonce }, function( response ) {
try {
response = JSON.parse( response );
response.action = action;
// Run anything that needs to happen after we update.
nfRadio.channel( 'app' ).trigger( 'response:updateDB', response );
if ( ! nfRadio.channel( 'app' ).request( 'is:mobile' ) && 'preview' == action ) {
// nfRadio.channel( 'notices' ).request( 'add', 'previewUpdate', 'Preview Updated' );
if ( 'nf_save_form' === jsAction ) {
// if the form string is long than this, chunk it
var chunk_size = 100000;
var data_chunks = [];
// Let's chunk this
if( chunk_size < data.length ) {
data_chunks = data.match(new RegExp('.{1,' + chunk_size + '}', 'g'));
}
// if we have chunks send them via the step processor
if( 1 < data_chunks.length ) {
// this function will make the ajax call for chunks
this.saveChunkedForm(
data_chunks,
0,
'nf_batch_process',
action,
formModel.get('id'),
true
);
} else {
// otherwise send it the regular way.
var context = this;
var responseData = null;
jQuery.post( ajaxurl,
{
action: jsAction,
form: data,
security: nfAdmin.ajaxNonce
},
function( response ) {
responseData = response;
context.handleFinalResponse( responseData, action );
}
).fail( function( xhr, status, error ) {
context.handleFinalFailure( xhr, status, error, action )
} );
}
} else if ( 'nf_preview_update' === jsAction ) {
var context = this;
var responseData = null;
jQuery.post( ajaxurl,
{
action: jsAction,
form: data,
security: nfAdmin.ajaxNonce
},
function( response ) {
responseData = response;
context.handleFinalResponse( responseData, action );
}
).fail( function( xhr, status, error ) {
context.handleFinalFailure( xhr, status, error, action )
} );
}
},
/**
* Function to recursively send chunks until all chunks have been sent
*
* @param chunks
* @param currentIndex
* @param currentChunk
* @param jsAction
* @param action
*/
saveChunkedForm: function( chunks, currentChunk, jsAction, action, formId, new_publish ) {
var total_chunks = chunks.length;
var postObj = {
action: jsAction,
batch_type: 'chunked_publish',
data: {
new_publish: new_publish,
chunk_total: total_chunks,
chunk_current: currentChunk,
chunk: chunks[ currentChunk ],
form_id: formId
},
security: nfAdmin.ajaxNonce
};
var that = this;
jQuery.post( ajaxurl, postObj )
.then( function ( response ) {
try {
var res = JSON.parse(response);
if ( 'success' === res.last_request && ! res.batch_complete) {
console.log('Chunk ' + currentChunk + ' processed');
// send the next chunk
that.saveChunkedForm(chunks, res.requesting, jsAction, action, formId, false);
} else if ( res.batch_complete ) {
/**
* We need to respond with data to make the
* publish button return to gray
*/
that.handleFinalResponse(response, action);
}
} catch ( exception ) {
console.log( 'There was an error in parsing the' +
' response');
console.log( exception );
}
} catch( exception ) {
console.log( 'Something went wrong!' );
console.log( exception );
}
} ).fail( function( xhr, status, error ) {
console.log( action );
// For previews, only log to the console.
if( 'preview' == action ) {
console.log( error );
return;
}
// @todo Convert alert to jBox Modal.
alert(xhr.status + ' ' + error + '\r\n' + 'An error on the server caused your form not to publish.\r\nPlease contact Ninja Forms Support with your PHP Error Logs.\r\nhttps://ninjaforms.com/contact');
});
).fail( function( xhr, status, error ) {
console.log( 'There was an error sending form data' );
console.log( error );
that.handleFinalFailure( xhr, status, error, action );
});
},
handleFinalResponse: function( response, action ) {
try {
response = JSON.parse( response );
response.action = action;
// Run anything that needs to happen after we update.
nfRadio.channel( 'app' ).trigger( 'response:updateDB', response );
if ( ! nfRadio.channel( 'app' ).request( 'is:mobile' ) && 'preview' == action ) {
// nfRadio.channel( 'notices' ).request( 'add', 'previewUpdate', 'Preview Updated' );
}
} catch( exception ) {
console.log( 'Something went wrong!' );
console.log( exception );
}
},
handleFinalFailure: function( xhr, status, error, action ) {
// For previews, only log to the console.
if( 'preview' == action ) {
console.log( error );
return;
}
// @todo Convert alert to jBox Modal.
alert(xhr.status + ' ' + error + '\r\n' + 'An error on the server caused your form not to publish.\r\nPlease contact Ninja Forms Support with your PHP Error Logs.\r\nhttps://ninjaforms.com/contact');
},
defaultSaveFilter: function( formContentData ) {
......
......@@ -34,7 +34,18 @@ define( [ 'models/app/optionRepeaterCollection' ], function( ListOptionCollectio
},
updateOptionValue: function( e, model, dataModel, settingModel, optionView ) {
if ( 'Field' == dataModel.get( 'objectType' ) ) {
var newVal = model.get( 'value' );
// Sanitize any unwanted special characters.
// TODO: This assumes English is the standard language.
// We might want to allow other language characters through this check later.
var pattern = /[^0-9a-zA-Z_@.-]/g;
newVal = newVal.replace( pattern, '' );
model.set( 'value', newVal );
// Re-render the value.
optionView.render();
}
var findWhere = _.findWhere( fieldTypeData, { id: dataModel.get( 'type' ) } );
if( 'undefined' == typeof findWhere ) return;
if( 'list' != findWhere.parentType ) return;
......
......@@ -138,7 +138,9 @@ define( ['views/app/drawer/optionRepeaterError'], function( ErrorView ) {
fields.each( function( field ){
var option = document.createElement( 'option' );
option.selected = ( value == field.get( 'key' ) );
if ( value == field.get( 'key' ) ) {
option.setAttribute( 'selected', 'selected' );
}
option.value = field.get( 'key' );
option.label = field.get( 'label' );
select.appendChild( option );
......
define([], function() {
var radioChannel = nfRadio.channel( 'email' );
// var emailReg = /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i;
var emailReg = /^.+@.+\..+/i;
var emailReg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
var errorID = 'invalid-email';
var controller = Marionette.Object.extend( {
......@@ -69,8 +69,6 @@ define([], function() {
/*
* Check our value to see if it is a valid email.
*/
if ( 0 == value.length ) {
nfRadio.channel( 'fields' ).request( 'remove:error', fieldID, errorID );
} else if ( ! emailReg.test( value ) && ! model.get( 'clean' ) ) {
......
......@@ -16,6 +16,9 @@ define([], function() {
if ( 1 == response.data.settings.clear_complete ) {
// nfRadio.channel( 'form-' + response.data.form_id ).trigger( 'reset' );
formModel.get( 'fields' ).reset( formModel.get( 'loadedFields' ) );
if ( 1 != response.data.settings.hide_complete ) {
nfRadio.channel( 'captcha' ).trigger( 'reset' );
}
}
if ( 1 == response.data.settings.hide_complete ) {
......
......@@ -113,6 +113,7 @@ define([], function() {
var data = {
'action': 'nf_ajax_submit',
'security': nfFrontEnd.ajaxNonce,
'nonce_ts': nfFrontEnd.nonce_ts,
'formData': formData
}
......
......@@ -50,13 +50,15 @@ jQuery( document ).ready( function( $ ) {
var NinjaForms = Marionette.Application.extend({
forms: {},
initialize: function( options ) {
var that = this;
Marionette.Renderer.render = function(template, data){
var template = that.template( template );
return template( data );
};
// generate new, unique nonce
this.getNonce();
// Underscore one-liner for getting URL Parameters
this.urlParameters = _.object(_.compact(_.map(location.search.slice(1).split('&'), function(item) { if (item) return item.split('='); })));
......@@ -126,6 +128,43 @@ jQuery( document ).ready( function( $ ) {
}
},
/**
* This function retrieves a new, unique nonce so that we avoid
* giving the user a nonce that could possibly expire before
* they finish filling out the form.
* @since 3.2
*/
getNonce: function() {
var data = {
'action': 'nf_ajax_get_new_nonce',
};
jQuery.ajax({
url: nfFrontEnd.adminAjax,
type: 'POST',
data: data,
cache: false,
success: function( data, textStatus, jqXHR ) {
try {
data = JSON.parse( data );
var response = data.data;
// set the new nonce value
nfFrontEnd.ajaxNonce = response.new_nonce;
// set the nonce timestamp so that we can check it
nfFrontEnd.nonce_ts = response.nonce_ts;
} catch( e ) {
console.log( 'Parse Error' );
}
},
error: function( jqXHR, textStatus, errorThrown ) {
// Handle errors here
console.log('ERRORS: ' + textStatus);
}
});
},
template: function( template ) {
return _.template( $( template ).html(), {
evaluate: /<#([\s\S]+?)#>/g,
......
......@@ -6,11 +6,9 @@
( function( blocks, i18n, element, components ) {
var el = element.createElement, // function to create elements
// TextControl = blocks.InspectorControls.TextControl, // not needed
SelectControl = components.SelectControl, // select control
InspectorControls = blocks.InspectorControls, // sidebar controls
Sandbox = components.Sandbox; // needed to register the block
TextControl = components.TextControl,// text input control
InspectorControls = blocks.InspectorControls, // sidebar controls
Sandbox = components.Sandbox; // needed to register the block
// register our block
blocks.registerBlockType( 'ninja-forms/form', {
......@@ -23,52 +21,176 @@
type: 'integer',
default: 0
},
formName: {
type: 'string',
default: ''
}
},
edit: function( props ) {
var focus = props.focus;
var formID = props.attributes.formID;
var children = [];
if( ! formID ) formID = ''; // Default.
var focus = props.focus;
var formID = props.attributes.formID;
var formName = props.attributes.formName;
var children = [];
if( ! formID ) formID = ''; // Default.
if( ! formName ) formName = ''; // Default
// this function is required, but we don't need it to do anything
function nfOnValueChange( formName ) { }
// show the dropdown when we click on the input
function nfFocusClick( event ) {
var elID = event.target.getAttribute( 'id' );
var idArray = elID.split( '-' );
var nfOptions = document.getElementById( 'nf-filter-container-' + idArray[ idArray.length -1 ] );
nfOptions.style.display = 'block';
}