Commit ac0cd4f4 authored by KR Moorhouse's avatar KR Moorhouse

Merge branch 'develop' into cleanup-process

parents 14f4927f cb189eae
This diff is collapsed.
......@@ -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.18
Stable tag: 3.3.1
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.
......
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This diff is collapsed.
......@@ -178,4 +178,43 @@ jQuery(document).ready(function($) {
$( "#delete_on_uninstall" ).attr( 'checked', true );
} );
// If we're allowed to track site data...
if ( '1' == nf_settings.allow_telemetry ) {
// Show the optout button.
$( '#nfTelOptin' ).addClass( 'hidden' );
$( '#nfTelOptout' ).removeClass( 'hidden' );
} // Otherwise...
else {
// Show the optin button.
$( '#nfTelOptout' ).addClass( 'hidden' );
$( '#nfTelOptin' ).removeClass( 'hidden' );
}
// If optin is clicked...
$( '#nfTelOptin' ).click( function( e ) {
// Hide the button.
$( '#nfTelOptin' ).addClass( 'hidden' );
$( '#nfTelSpinner' ).css( 'display', 'inline-block' );
// Hit AJAX endpoint and opt-in.
$.post( ajaxurl, { action: 'nf_optin', ninja_forms_opt_in: 1 },
function( response ) {
$( '#nfTelOptout' ).removeClass( 'hidden' );
$( '#nfTelSpinner' ).css( 'display', 'none' );
} );
} );
// If optout is clicked...
$( '#nfTelOptout' ).click( function( e ) {
// Hide the button.
$( '#nfTelOptout' ).addClass( 'hidden' );
$( '#nfTelSpinner' ).css( 'display', 'inline-block' );
// Hit AJAX endpoint and opt-out.
$.post( ajaxurl, { action: 'nf_optin', ninja_forms_opt_in: 0 },
function( response ) {
$( '#nfTelOptin' ).removeClass( 'hidden' );
$( '#nfTelSpinner' ).css( 'display', 'none' );
} );
} );
});
......@@ -37,7 +37,7 @@ define( [], function( settingCollection ) {
/*
* We have changed a calculation. Make sure that 'calc' is our payment total type.
*/
if ( 'calculation' != this.get( 'payment_total_type' ) ) {
if ( 'calc' != this.get( 'payment_total_type' ) ) {
return
}
......
/**
* Handles actions related to our toggle field.
* When we change the toggle, the setting value will be 'on' or ''.
* We need to change this to 1 or 0.
*
* @package Ninja Forms builder
* @subpackage Fields - Edit Field Drawer
* @copyright (c) 2015 WP Ninjas
* @since 3.0
*/
define( [], function() {
var controller = Marionette.Object.extend( {
initialize: function() {
// We don't want the RTE setting to re-render when the value changes.
nfRadio.channel( 'setting-type-button-toggle' ).reply( 'renderOnChange', function(){ return false; } );
// Respond to requests for field setting filtering.
nfRadio.channel( 'button-toggle' ).reply( 'before:updateSetting', this.updateSetting, this );
},
/**
* Return either 1 or 0, depending upon the toggle position.
*
* @since 3.0
* @param Object e event
* @param backbone.model fieldModel field model
* @param string name setting name
* @param backbone.model settingTypeModel field type model
* @return int 1 or 0
*/
updateSetting: function( e, fieldModel, name, settingTypeModel ) {
return e.target.value;
}
});
return controller;
} );
\ No newline at end of file
......@@ -93,17 +93,25 @@ define( ['models/app/optionRepeaterModel', 'models/app/optionRepeaterCollection'
new: true,
options: {}
};
var limit = collection.settingModel.get( 'max_options' );
if( 0 !== limit && collection.models.length >= limit ) {
return;
}
_.each( collection.settingModel.get( 'columns' ), function( col, key ) {
modelData[ key ] = col.default;
if( 'undefined' != typeof col.options ){
modelData.options[ key ] = col.options;
/**
* If we don't actually have a 'settingModel' duplicated fields
* can't add options until publish and the builder is reloaded.
* If we ignore the code if we don't have settingsModel, then it
* works.
*/
if ( 'undefined' !== typeof collection.settingModel ) {
var limit = collection.settingModel.get( 'max_options' );
if ( 0 !== limit && collection.models.length >= limit ) {
return;
}
} );
_.each( collection.settingModel.get( 'columns' ), function ( col, key ) {
modelData[ key ] = col.default;
if ( 'undefined' != typeof col.options ) {
modelData.options[ key ] = col.options;
}
});
}
var model = new listOptionModel( modelData );
collection.add( model );
......@@ -116,7 +124,10 @@ define( ['models/app/optionRepeaterModel', 'models/app/optionRepeaterCollection'
};
nfRadio.channel( 'changes' ).request( 'register:change', 'addListOption', model, null, label );
nfRadio.channel( 'option-repeater-' + collection.settingModel.get( 'name' ) ).trigger( 'add:option', model );
if ( 'undefined' !== typeof collection.settingModel ) {
nfRadio.channel('option-repeater-' + collection.settingModel.get('name')).trigger('add:option', model);
}
nfRadio.channel( 'option-repeater' ).trigger( 'add:option', model );
nfRadio.channel( 'option-repeater' ).trigger( 'added:option', collection );
this.triggerDataModel( model, dataModel );
......
define( [], function() {
var controller = Marionette.Object.extend({
initialize: function () {
// Respond to requests for field setting filtering.
console.log( nfRadio.channel( 'radio' ) );
nfRadio.channel('radio').reply( 'before:updateSetting', this.updateSetting, this);
},
updateSetting: function( e, fieldModel, name, settingTypeModel ) {
console.log( 'test' );
}
});
return controller;
} );
\ No newline at end of file
......@@ -39,7 +39,7 @@ define( [ 'models/app/optionRepeaterCollection' ], function( ListOptionCollectio
// 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;
var pattern = /[^0-9a-zA-Z _@.-]/g;
newVal = newVal.replace( pattern, '' );
model.set( 'value', newVal );
// Re-render the value.
......
......@@ -59,6 +59,9 @@ define( [], function() {
var d = new Date();
var n = d.valueOf();
var key = this.slugify( model.get( 'label' ) + '_' + n );
// If our slug didn't setup correctly...
// Force a valid entry.
if ( -1 == key.indexOf( '_' ) ) key = 'field_' + key;
model.set( 'key', key );
}
},
......
......@@ -34,6 +34,8 @@ define(
'controllers/app/changeSettingDefault',
'controllers/app/fieldset',
'controllers/app/toggleSetting',
'controllers/app/buttonToggleSetting',
'controllers/app/radioSetting',
'controllers/app/itemControls',
'controllers/app/mergeTags',
'controllers/app/mergeTagBox',
......@@ -137,6 +139,8 @@ define(
ChangeSettingDefault,
Fieldset,
ToggleSetting,
ButtonToggleSetting,
RadioSetting,
ItemControls,
MergeTags,
MergeTagsBox,
......@@ -271,6 +275,8 @@ define(
new MainContentFieldsSortable();
new ChangeSettingDefault();
new ToggleSetting();
new ButtonToggleSetting();
new RadioSetting();
new DrawerSettingChildView();
new FieldsEditActive();
new FieldSettings();
......
......@@ -22,7 +22,10 @@ define( ['models/app/optionRepeaterModel'], function( listOptionModel ) {
changeCollection: function() {
// Trigger a 'sort:options' event so that our field model can update
nfRadio.channel( 'option-repeater' ).trigger( 'sort:options', this );
nfRadio.channel( 'option-repeater-' + this.settingModel.get( 'name' ) ).trigger( 'sort:options', this );
if ('undefined' !== typeof this.settingModel ) {
nfRadio.channel('option-repeater-' + this.settingModel.get('name')).trigger('sort:options', this);
}
},
addOption: function( model, collection ) {
......
......@@ -130,6 +130,7 @@ define( ['views/app/drawer/optionRepeaterError'], function( ErrorView ) {
initialOption = document.createElement( 'option' );
initialOption.value = '';
initialOption.label = '--';
initialOption.innerHTML = '--';
select = document.createElement( 'select' );
select.classList.add( 'setting' );
......@@ -138,8 +139,11 @@ 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.innerHTML = field.get( 'label' );
option.label = field.get( 'label' );
select.appendChild( option );
});
......
......@@ -43,7 +43,7 @@ define( ['views/app/itemControls'], function( itemControlsView ) {
templateHelpers: function () {
return {
renderClasses: function() {
var classes = 'nf-field-wrap';
var classes = 'nf-field-wrap ' + this.type;
if ( this.editActive ) {
classes += ' active';
}
......
define([], function() {
var radioChannel = nfRadio.channel( 'date' );
var errorID = 'invalid-date';
var controller = Marionette.Object.extend( {
initialize: function() {
this.listenTo( radioChannel, 'change:modelValue', this.onChangeModelValue );
this.listenTo( radioChannel, 'keyup:field', this.dateKeyup );
this.listenTo( radioChannel, 'blur:field', this.onBlurField );
},
onChangeModelValue: function( model ) {
this.dateChange( model );
},
onBlurField: function( el, model ) {
this.dateChange( model );
},
dateChange: function( model ) {
var fieldID = model.get( 'id' );
var value = model.get( 'value' );
var format = model.get( 'date_format' );
if( 'default' === format) {
format = nfi18n.dateFormat;
}
if ( 0 < value.length ) {
// use moment's isValid to check against the fields format setting
if( moment( value, format ).isValid() ) {
nfRadio.channel( 'fields' ).request( 'remove:error', fieldID, errorID );
} else {
var fieldModel = nfRadio.channel( 'fields' ).request( 'get:field', fieldID );
var formModel = nfRadio.channel( 'app' ).request( 'get:form', fieldModel.get( 'formID' ) );
nfRadio.channel( 'fields' ).request( 'add:error', fieldID, errorID, formModel.get( 'settings' ).changeDateErrorMsg );
}
} else {
nfRadio.channel( 'fields' ).request( 'remove:error', fieldID, errorID );
}
},
/**
* When a user types inside of an dat field, track their keypresses
* and add the appropriate class.
* If the value validates as an date, add a class of nf-pass
* If the value does not validate as date, add a class of nf-fail
*
* @since 3.0
* @param {object} el Element that triggered the keyup event.
* @param {object} model Model connected to the element that triggered the event
* @return {void}
*/
dateKeyup: function( el, model, keyCode ) {
/*
* If we pressed the 'tab' key to get to this field, return false.
*/
if ( 9 == keyCode ) {
return false;
}
/*
* Get the current value from our element.
*/
var value = jQuery( el ).val();
/*
* Get our current ID
*/
var fieldID = model.get( 'id' );
/*
* Get our current date format
*/
var format = model.get( 'date_format' );
if( 'default' === format) {
format = nfi18n.dateFormat;
}
/*
* Check our value to see if it is a valid email.
*/
if ( 0 == value.length ) {
nfRadio.channel( 'fields' ).request( 'remove:error', fieldID, errorID );
}
// use moment's isValid to check against the fields format setting
else if ( ! moment( value, format ).isValid() && ! model.get( 'clean' ) ) {
var fieldModel = nfRadio.channel( 'fields' ).request( 'get:field', fieldID );
var formModel = nfRadio.channel( 'app' ).request( 'get:form', fieldModel.get( 'formID' ) );
nfRadio.channel( 'fields' ).request( 'add:error', fieldID, errorID, formModel.get( 'settings' ).changeDateErrorMsg );
model.removeWrapperClass( 'nf-pass' );
}
// use moment's isValid to check against the fields format setting
else if ( moment( value, format ).isValid() ) {
nfRadio.channel( 'fields' ).request( 'remove:error', fieldID, errorID );
/*
* Add nf-pass class to the wrapper.
*/
model.addWrapperClass( 'nf-pass' );
model.set( 'clean', false );
}
}
});
return controller;
} );
\ No newline at end of file
......@@ -4,6 +4,7 @@ define(
'controllers/fieldError',
'controllers/changeField',
'controllers/changeEmail',
'controllers/changeDate',
'controllers/fieldCheckbox',
'controllers/fieldCheckboxList',
'controllers/fieldRadio',
......@@ -43,6 +44,7 @@ define(
FieldError,
ChangeField,
ChangeEmail,
ChangeDate,
FieldCheckbox,
FieldCheckboxList,
FieldRadio,
......@@ -112,6 +114,7 @@ define(
new FieldError();
new ChangeField();
new ChangeEmail();
new ChangeDate();
new MirrorField();
new ConfirmField();
......
......@@ -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,
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -35,6 +35,9 @@
}
}
}
&.note {
background: #ffffee;
}
}
.nf-group-wrap {
......
......@@ -35,6 +35,9 @@
border: 4px solid #FFF;
padding: 25px 15px 6px;
}
.nf-fields-sortable .hidden {
display: block !important;
}
.nf-fields-sortable-placeholder { margin-top: -14px; margin-bottom: 6px; border: 4px solid #84CC1E }
.nf-staged-fields-drag {
......
......@@ -237,6 +237,33 @@
}
}
}
.button-toggle {
text-align: left;
border: #ccc 1px solid;
border-radius: 5px;
background: #f1f1f1;
margin-top: 35px;
padding: 9px 0px;
width: 100%;
label {
width: 48%;
display: inline-block;
margin-left: 1%;
span {
width: 100%;
display: inline-block;
cursor: pointer;
}
input:checked + span {
background-color: #1ea9ea;
border-color: #1ea9ea;
}
input + span {
background-color: #ccc;
border-color: #ccc;
}
}
}
}
.nf-drawer-buttons {
display: none;
......
......@@ -102,16 +102,19 @@ th {
flex-wrap: wrap;
& > div {
display: flex;
width: 50%;
}
.template {
display: grid;
width: 100%;
a {
color: #424242;
display: block;
margin: 5px;
padding: 30px;
border: 1px solid #EBEDEE;
border: 1px solid #ccc;
border-radius: 4px;
min-height: 80px;
cursor: pointer;
......@@ -128,8 +131,50 @@ th {
}
}
}
.ad {
a {
border-color: #EBEDEE;
}
}
.default {
a {
background: #EBEDEE;
border-color: #EBEDEE;
}
}
}
table .forms-table-row li {
display: inline-block;
}
.modal-template {
padding: 20px;
img {
width: 100%;
}
iframe {
width: 100%;
height: 214px;
}
p:first-of-type {
font-size: 16px;
font-weight: bold;
line-height: 20px;
padding: 16px 40px;
text-align: center;
}
.actions {
margin-top: 30px;
&::after {
display: block;
clear: both;
content: "";
}
}
.primary.nf-button {
float: right;
}
}
......@@ -72,6 +72,7 @@ LABEL HIDDEN
}
.nf-field-label {
height: 0;
margin: 0 !important;
width: 100%;
visibility: hidden;
}
......
......@@ -16,10 +16,12 @@ define( ['models/formModel'], function( FormModel ) {
initialize: function() {
this.newIDs = [];
this.baseUrl = window.location.href.split('?')[0];
this.listenTo( nfRadio.channel( 'dashboard' ), 'forms:delete', this.modalConfirm );
this.listenTo( nfRadio.channel( 'dashboard' ), 'forms:duplicate', this.duplicate );
this.modal = new jBox( 'Modal', {
width: 300,
width: 400,
addClass: 'dashboard-modal',
overlay: true,
closeOnClick: 'body'
......@@ -32,35 +34,82 @@ define( ['models/formModel'], function( FormModel ) {
modalConfirm: function( view ){
var message, container, messageBox, title, buttons, confirm, cancel, lineBreak;
var formID = view.model.get( 'id' );
var formTitle = view.model.get( 'title' );
container = document.createElement( 'div' );
container.style.paddingRight = '20px';
container.style.paddingLeft = '20px';
container.style.paddingBottom = '20px';
messageBox = document.createElement( 'p' );
title = document.createElement( 'em' );
buttons = document.createElement( 'div' );
confirm = document.createElement( 'div' );
cancel = document.createElement( 'div' );
lineBreak = document.createElement( 'br' );
container.classList.add( 'message' );
title.innerHTML = view.model.get( 'title' );
messageBox.innerHTML += 'Once deleted, a Form cannot be recovered.';
messageBox.appendChild( lineBreak );
messageBox.innerHTML += 'Are you sure you want to delete ';
messageBox