Ninja Forms – The Easy and Powerful Forms Builder - Version 3.4.0

Version Description

(14 January 2019) =

Changes:

  • Implemented a new import process, which should be more reliable with large form imports.
  • Upgraded our data structure to reduce loading times for forms and the form builder.

=

Download this release

Release Info

Developer krmoorhouse
Plugin Icon 128x128 Ninja Forms – The Easy and Powerful Forms Builder
Version 3.4.0
Comparing to
See all releases

Code changes from version 3.3.21.3 to 3.4.0

Files changed (67) hide show
  1. assets/css/admin-settings.css +4 -1
  2. assets/css/required-updates.css +156 -0
  3. assets/js/admin-import-export.js +102 -0
  4. assets/js/admin-settings.js +55 -79
  5. assets/js/lib/batch-processor.js +117 -0
  6. assets/js/lib/ninjaModal.js +6 -0
  7. assets/js/min/dashboard.min.js +2 -1
  8. assets/js/min/dashboard.min.js.map +1 -1
  9. client/dashboard/main.js +17 -8
  10. client/dashboard/views/dashboardView.js +67 -116
  11. client/dashboard/views/sections/requiredUpdates.js +345 -0
  12. client/dashboard/views/widgets/forms/forms.js +2 -3
  13. client/dashboard/views/widgets/forms/newFormTemplate.js +33 -15
  14. deprecated/ninja-forms.php +1 -1
  15. includes/AJAX/Controllers/Form.php +16 -0
  16. includes/AJAX/Controllers/Submission.php +13 -0
  17. includes/AJAX/REST/BatchProcess.php +9 -17
  18. includes/AJAX/REST/NewFormTemplates.php +3 -3
  19. includes/AJAX/REST/RequiredUpdate.php +165 -0
  20. includes/Abstracts/BatchProcess.php +127 -1
  21. includes/Abstracts/Migration.php +102 -23
  22. includes/Abstracts/Model.php +28 -3
  23. includes/Abstracts/RequiredUpdate.php +224 -0
  24. includes/Admin/Menus/AddNew.php +13 -0
  25. includes/Admin/Menus/Addons.php +13 -0
  26. includes/Admin/Menus/Forms.php +32 -3
  27. includes/Admin/Menus/ImportExport.php +38 -1
  28. includes/Admin/Menus/Settings.php +34 -1
  29. includes/Admin/Menus/Submissions.php +18 -1
  30. includes/Admin/Menus/SystemStatus.php +2 -0
  31. includes/Admin/Processes/ChunkPublish.php +1 -1
  32. includes/Admin/Processes/DataCleanup.php +0 -131
  33. includes/Admin/Processes/ExpiredSubmissionCleanup.php +43 -67
  34. includes/Admin/Processes/ImportForm.php +856 -0
  35. includes/Admin/Processes/ImportFormTemplate.php +82 -0
  36. includes/Config/BatchProcesses.php +19 -0
  37. includes/Config/NewFormTemplates.php +3 -3
  38. includes/Config/PluginSettingsAdvanced.php +9 -0
  39. includes/Config/RequiredUpdates.php +31 -0
  40. includes/Database/FieldsController.php +253 -37
  41. includes/Database/Migrations.php +63 -8
  42. includes/Database/Migrations/ActionMeta.php +39 -1
  43. includes/Database/Migrations/Actions.php +38 -1
  44. includes/Database/Migrations/Chunks.php +1 -1
  45. includes/Database/Migrations/FieldMeta.php +37 -2
  46. includes/Database/Migrations/Fields.php +47 -1
  47. includes/Database/Migrations/FormMeta.php +34 -7
  48. includes/Database/Migrations/Forms.php +42 -23
  49. includes/Database/Migrations/ObjectMeta.php +33 -1
  50. includes/Database/Migrations/Objects.php +32 -1
  51. includes/Database/Migrations/Relationships.php +26 -1
  52. includes/Database/Migrations/Upgrades.php +32 -1
  53. includes/Database/Models/Action.php +21 -1
  54. includes/Database/Models/Form.php +172 -8
  55. includes/Database/Models/Object.php +21 -1
  56. includes/Display/Render.php +14 -0
  57. includes/Helper.php +153 -5
  58. includes/Templates/admin-menu-dashboard.html.php +33 -0
  59. includes/Templates/admin-metabox-import-export-forms-import.html.php +15 -29
  60. includes/Updates/CacheCollateActions.php +342 -0
  61. includes/Updates/CacheCollateCleanup.php +507 -0
  62. includes/Updates/CacheCollateFields.php +720 -0
  63. includes/Updates/CacheCollateForms.php +243 -0
  64. includes/Updates/CacheCollateObjects.php +298 -0
  65. lib/NF_Upgrade.php +1 -0
  66. ninja-forms.php +164 -52
  67. readme.txt +12 -4
assets/css/admin-settings.css CHANGED
@@ -69,7 +69,10 @@
69
  border-radius: 4px;
70
  }
71
 
72
- /*https://github.com/wpninjas/ninja-forms/issues/2296*/
 
 
 
73
  @media screen and ( max-width: 782px ) {
74
  .tablenav.top .actions, .tablenav .view-switch {
75
  display:block;
69
  border-radius: 4px;
70
  }
71
 
72
+ .jBox-content {
73
+ overflow: hidden !important;
74
+ }
75
+
76
  @media screen and ( max-width: 782px ) {
77
  .tablenav.top .actions, .tablenav .view-switch {
78
  display:block;
assets/css/required-updates.css ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ html,
2
+ body {
3
+ background: #fff;
4
+ }
5
+ h1 {
6
+ line-height: 32px;
7
+ }
8
+ h2 {
9
+ /*margin: 40px auto 20px;*/
10
+ }
11
+ p {
12
+ font-size: 16px;
13
+ /*padding-top: 20px;*/
14
+ }
15
+ ol {
16
+ font-size: 16px;
17
+ margin-left: 40px;
18
+ list-style-type: none;
19
+ }
20
+ hr {
21
+ margin: 50px 20px;
22
+ }
23
+ img {
24
+ width: 100%;
25
+ }
26
+
27
+ table {
28
+ width: 100%;
29
+ margin-bottom: 20px;
30
+ border-collapse: collapse;
31
+ }
32
+
33
+ tr {
34
+ height: 65px;
35
+ }
36
+
37
+ th {
38
+ text-align: left;
39
+ }
40
+ th, td {
41
+ padding: 15px;
42
+ text-align: left;
43
+ border-bottom: 1px solid #ddd;
44
+ }
45
+
46
+ th:nth-child( 1 ),
47
+ td:nth-child( 1 ) {
48
+ width: 30%;
49
+ }
50
+ th:nth-child( 2 ),
51
+ td:nth-child( 2 ) {
52
+ width: 70%;
53
+ text-align: center;
54
+ }
55
+
56
+
57
+ tbody tr:nth-child(odd) {
58
+ background-color: #f2f2f2
59
+ }
60
+
61
+ .opt-in {
62
+ text-align: center;
63
+ }
64
+
65
+ .nf-logo {
66
+ display: block;
67
+ margin: 0 auto;
68
+ max-width: 300px;
69
+ }
70
+ .nf-update {
71
+ margin: 0 auto 50px auto;
72
+ max-width: 800px;
73
+ }
74
+
75
+ /*
76
+ * Dashicons
77
+ */
78
+
79
+ .dashicons-yes {
80
+ color: green;
81
+ }
82
+ .dashicons-no {
83
+ color: #EF4748;
84
+ }
85
+ .dashicons-flag {
86
+ color: #ffba00; /* WP Update Nag Yellow */
87
+ }
88
+ .dashicons-warning {
89
+ color: gray;
90
+ }
91
+
92
+ .dashicons-update {
93
+ margin-left: -1px;
94
+ animation: dashicons-spin 1s infinite;
95
+ animation-timing-function: linear;
96
+ }
97
+ @keyframes dashicons-spin {
98
+ 0% {
99
+ transform: rotate( 0deg );
100
+ }
101
+ 100% {
102
+ transform: rotate( 360deg );
103
+ }
104
+ }
105
+
106
+ ol .dashicons {
107
+ margin-top: 2px;
108
+ margin-right: 8px;
109
+ margin-left: -30px;
110
+ }
111
+
112
+ /*
113
+ * Progress Bar
114
+ */
115
+ .progress-bar,
116
+ .progress-bar--wrapper {
117
+ height: 10px;
118
+ }
119
+ .progress-bar {
120
+ width: 0;
121
+ background-color: green;
122
+ }
123
+ .progress-bar--wrapper {
124
+ width: 100%;
125
+ }
126
+
127
+ /*
128
+ * Button
129
+ */
130
+
131
+ .nf-update-button {
132
+ color: white;
133
+ background-color: #2BAAE7;
134
+ cursor: pointer;
135
+ padding: 10px 20px;
136
+ height: auto;
137
+ display: none;
138
+ margin: auto;
139
+ border: 2px solid transparent;
140
+ transition: all .5s;
141
+ position: relative;
142
+ }
143
+ .nf-update-button:hover,
144
+ .nf-update-button:active {
145
+ color: #2BAAE7;
146
+ border-color: #2BAAE7;
147
+ background-color: white;
148
+ }
149
+
150
+ #nf-upgrades-table .nf-progress-bar {
151
+ margin: 0;
152
+ }
153
+
154
+ .jBox-Modal {
155
+ max-width: 500px
156
+ }
assets/js/admin-import-export.js CHANGED
@@ -1,4 +1,106 @@
1
  jQuery( document ).ready( function( $ ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
  var clickedElement;
4
 
1
  jQuery( document ).ready( function( $ ) {
2
+ /**
3
+ * Stores the selected file details for form imports.
4
+ *
5
+ * name is the filename from the user's computer, including extension.
6
+ * content is the base64 encoded contents as a result of using the HTML5 FileReader API.
7
+ * @type {Object}
8
+ */
9
+ var importFormFile = {
10
+ name: '',
11
+ content: ''
12
+ };
13
+
14
+ /**
15
+ * Listen for clicks on our "import" button.
16
+ * It sets data for our batch processor and then instantiates a batch process.
17
+ *
18
+ * @since 3.4.0
19
+ * @param object e Click Event
20
+ * @return void
21
+ */
22
+ $( document ).on( 'click', '#nf-import-form-submit', function( e ) {
23
+ // Make sure that our file field isn't empty.
24
+ if ( '' == importFormFile.name ) return false;
25
+
26
+ // Settings object for our batch processor
27
+ var settings = {
28
+ batch_type: 'import_form',
29
+ extraData: importFormFile,
30
+ loadingText: 'Importing...',
31
+ onCompleteCallback: function( response ) {
32
+ // If we don't get back a form ID, then bail.
33
+ if ( 'undefined' == typeof response.form_id ) return false;
34
+
35
+ jQuery( '#nf-import-file' ).val('');;
36
+ jQuery( '#nf-import-url' ).attr( 'href', nfAdmin.builderURL + response.form_id );
37
+ jQuery( '#row-nf-import-response' ).show();
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Instantiate our batch processor.
43
+ *
44
+ * This will open the modal and present the user with content and buttons.
45
+ */
46
+ new NinjaBatchProcessor( settings );
47
+ } );
48
+
49
+ /**
50
+ * Selecting a file within an input field triggers a jQuery change event.
51
+ *
52
+ * When we select a form file to import, we need to do a few things:
53
+ *
54
+ * Disable the primary button of our batch processing modal.
55
+ * Grab the file and make sure that it has a .nff extension.
56
+ * Read the contents and base64 encode them using the HTML5 FileReader API.
57
+ * Set the contents to our importFormFile variable.
58
+ *
59
+ * @since 3.4.0
60
+ * @param object e Change Event
61
+ * @return {[type]} [description]
62
+ */
63
+ $( document ).on( 'change', '#nf-import-file', function( e ) {
64
+ // Hide our success message.
65
+ jQuery( '#row-nf-import-response' ).hide();
66
+ // Hide our extension type error.
67
+ jQuery( '#row-nf-import-type-error' ).hide();
68
+
69
+ // Grab the file from the input.
70
+ var file = e.target.files[0];
71
+ // If our file var is empty, bail.
72
+ if ( ! file ) {
73
+ return false;
74
+ }
75
+
76
+ // Use some Regex to get the extension
77
+ var extension = file.name.match(/\.[0-9a-z]+$/i);
78
+
79
+ // If we don't have a .nff extension, show our type error and bail.
80
+ if ( '.nff' !== extension[0] ) {
81
+ jQuery( '#row-nf-import-type-error' ).show();
82
+ importFormFile.name = '';
83
+ importFormFile.content = '';
84
+ return false;
85
+ }
86
+
87
+ // Instantiate the HTML5 FileReader API.
88
+ var reader = new FileReader();
89
+
90
+ /**
91
+ * When the HTML5 API says that we've successfully loaded the file contents:
92
+ * Set our importFormFile var.
93
+ * Enable our batch processor primary button.
94
+ * We use Javascript's addEventListener to update our var.
95
+ */
96
+ reader.addEventListener( 'load', function () {
97
+ importFormFile.name = file.name;
98
+ importFormFile.content = reader.result;
99
+ }, false);
100
+
101
+ // Use the readAsDataURL method of the FileReader API.
102
+ reader.readAsDataURL( file );
103
+ } );
104
 
105
  var clickedElement;
106
 
assets/js/admin-settings.js CHANGED
@@ -68,12 +68,12 @@ jQuery(document).ready(function($) {
68
  downgradeInput.style.width = '100%';
69
  downgradeInput.style.height = '3em';
70
  downgradeCTA = document.createElement( 'p' );
71
- downgradeCTA.innerHTML = nf_settings.i18n.downgradeConfirmMessage;
72
  downgradeWarning = document.createElement( 'p' );
73
- downgradeWarning.innerHTML = nf_settings.i18n.downgradeWarningMessage;
74
  downgradeWarning.style.color = 'red';
75
  downgradeTitle = document.createElement( 'h3' );
76
- downgradeTitle.innerHTML = nf_settings.i18n.downgradeMessage;
77
  downgradeContainer = document.createElement( 'div' );
78
  downgradeContainer.appendChild( downgradeTitle );
79
  downgradeContainer.appendChild( downgradeWarning );
@@ -105,20 +105,20 @@ jQuery(document).ready(function($) {
105
  var last_form = 0;
106
  // Gives the user confidence things are happening
107
  $( '#progressMsg' ).html( 'Deleting submissions for '
108
- + nf_settings.forms[ formIndex ].title + "" + ' ( ID: '
109
- + nf_settings.forms[ formIndex ].id + ' )' );
110
  $( '#progressMsg').show();
111
  // notify php this is the last one so it delete data and deactivate NF
112
- if( formIndex === nf_settings.forms.length - 1 ) {
113
  last_form = 1;
114
  }
115
  // do this deletion thang
116
  $.post(
117
- nf_settings.ajax_url,
118
  {
119
  'action': 'nf_delete_all_data',
120
- 'form': nf_settings.forms[ formIndex ].id,
121
- 'security': nf_settings.nonce,
122
  'last_form': last_form
123
  }
124
  ).then (function( response ) {
@@ -126,7 +126,7 @@ jQuery(document).ready(function($) {
126
  response = JSON.parse( response );
127
  // we expect success and then move to the next form
128
  if( response.data.success ) {
129
- if( formIndex < nf_settings.forms.length ) {
130
  doAllDataDeletions( formIndex )
131
  } else {
132
  // if we're finished deleting data then redirect to plugins
@@ -139,7 +139,7 @@ jQuery(document).ready(function($) {
139
  // writes error messages to console to help us debug
140
  console.log( xhr.status + ' ' + error + '\r\n' +
141
  'There was an error deleting submissions for '
142
- + nf_settings.forms[ formIndex ].title );
143
  });
144
  };
145
  // Add event listener for delete button
@@ -176,10 +176,10 @@ jQuery(document).ready(function($) {
176
  'field': {
177
  id: $( that ).data( 'id' )
178
  },
179
- 'security': nf_settings.nonce
180
  };
181
 
182
- $.post( nf_settings.ajax_url, data )
183
  .done( function( response ) {
184
  $( that ).closest( 'tr').fadeOut().remove();
185
  });
@@ -194,7 +194,7 @@ jQuery(document).ready(function($) {
194
  // TODO: Maybe this should be build using DOM node construction?
195
  content: downgradeContainer.innerHTML,
196
  btnPrimary: {
197
- text: nf_settings.i18n.downgradeButtonPrimary,
198
  class: 'nfDowngradeButtonPrimary',
199
  callback: function( e ) {
200
  // If our "Downgrade" button does not have have an attribute of disabled...
@@ -210,7 +210,7 @@ jQuery(document).ready(function($) {
210
  }
211
  },
212
  btnSecondary: {
213
- text: nf_settings.i18n.downgradeButtonSecondary,
214
  class: 'nfDowngradeButtonSecondary',
215
  callback: function( e ) {
216
  // Close the modal if this button is clicked.
@@ -256,7 +256,7 @@ jQuery(document).ready(function($) {
256
  } );
257
 
258
  // If we're allowed to track site data...
259
- if ( '1' == nf_settings.allow_telemetry ) {
260
  // Show the optout button.
261
  $( '#nfTelOptin' ).addClass( 'hidden' );
262
  $( '#nfTelOptout' ).removeClass( 'hidden' );
@@ -294,71 +294,47 @@ jQuery(document).ready(function($) {
294
  } );
295
 
296
  jQuery( '#nfTrashExpiredSubmissions' ).click( function( e ) {
297
- var that = this;
298
- var data = {
299
- closeOnClick: false,
300
- closeOnEsc: true,
301
- content: '<p>' + nf_settings.i18n.trashExpiredSubsMessage + '<p>',
302
- btnPrimary: {
303
- text: nf_settings.i18n.trashExpiredSubsButtonPrimary,
304
- callback: function( e ) {
305
- // Hide the buttons.
306
- deleteModal.maybeShowActions( false );
307
- // Show the progress bar.
308
- deleteModal.maybeShowProgress( true );
309
- // Begin our cleanup process.
310
- that.submissionExpirationProcess( that, -1, deleteModal );
311
 
312
- }
313
- },
314
- btnSecondary: {
315
- text: nf_settings.i18n.trashExpiredSubsButtonSecondary,
316
- callback: function( e ) {
317
- deleteModal.toggleModal( false );
318
- }
319
- },
320
- useProgressBar: true,
 
 
321
  };
 
 
 
 
 
 
 
 
 
322
 
323
- this.submissionExpirationProcess = function( context, steps, modal ) {
324
- var data = {
325
- action: 'nf_batch_process',
326
- batch_type: 'expired_submission_cleanup',
327
- security: nf_settings.batch_nonce
328
- };
329
- jQuery.post( nf_settings.ajax_url, data, function( response ) {
330
- response = JSON.parse( response );
331
- // If we're done...
332
- if ( response.batch_complete ) {
333
- // Push our progress bar to 100%.
334
- modal.setProgress( 100 );
335
- modal.toggleModal( false );
336
- // Exit.
337
- return false;
338
- }
339
- // If we do not yet have a determined number of steps...
340
- if ( -1 == steps ) {
341
- // If step_toal is defined...
342
- if ( 'undefined' != typeof response.step_total ) {
343
- // Use the step_total.
344
- steps = response.step_total;
345
- } // Otherwise... (step_total is not defined)
346
- else {
347
- // Use step_remaining.
348
- steps = response.step_remaining;
349
- }
350
- }
351
- // Calculate our current step.
352
- var step = steps - response.step_remaining;
353
- // Calculate our maximum progress for this step.
354
- var maxProgress = Math.round( step / steps * 100 );
355
- // Increment the progress.
356
- modal.incrementProgress ( maxProgress );
357
- // Recall our function...
358
- context.submissionExpirationProcess( context, steps, modal );
359
- } );
360
- }
361
 
362
- var deleteModal = new NinjaModal( data );
363
- });
 
 
 
 
 
364
  });
68
  downgradeInput.style.width = '100%';
69
  downgradeInput.style.height = '3em';
70
  downgradeCTA = document.createElement( 'p' );
71
+ downgradeCTA.innerHTML = nfAdmin.i18n.downgradeConfirmMessage;
72
  downgradeWarning = document.createElement( 'p' );
73
+ downgradeWarning.innerHTML = nfAdmin.i18n.downgradeWarningMessage;
74
  downgradeWarning.style.color = 'red';
75
  downgradeTitle = document.createElement( 'h3' );
76
+ downgradeTitle.innerHTML = nfAdmin.i18n.downgradeMessage;
77
  downgradeContainer = document.createElement( 'div' );
78
  downgradeContainer.appendChild( downgradeTitle );
79
  downgradeContainer.appendChild( downgradeWarning );
105
  var last_form = 0;
106
  // Gives the user confidence things are happening
107
  $( '#progressMsg' ).html( 'Deleting submissions for '
108
+ + nfAdmin.forms[ formIndex ].title + "" + ' ( ID: '
109
+ + nfAdmin.forms[ formIndex ].id + ' )' );
110
  $( '#progressMsg').show();
111
  // notify php this is the last one so it delete data and deactivate NF
112
+ if( formIndex === nfAdmin.forms.length - 1 ) {
113
  last_form = 1;
114
  }
115
  // do this deletion thang
116
  $.post(
117
+ nfAdmin.ajax_url,
118
  {
119
  'action': 'nf_delete_all_data',
120
+ 'form': nfAdmin.forms[ formIndex ].id,
121
+ 'security': nfAdmin.nonce,
122
  'last_form': last_form
123
  }
124
  ).then (function( response ) {
126
  response = JSON.parse( response );
127
  // we expect success and then move to the next form
128
  if( response.data.success ) {
129
+ if( formIndex < nfAdmin.forms.length ) {
130
  doAllDataDeletions( formIndex )
131
  } else {
132
  // if we're finished deleting data then redirect to plugins
139
  // writes error messages to console to help us debug
140
  console.log( xhr.status + ' ' + error + '\r\n' +
141
  'There was an error deleting submissions for '
142
+ + nfAdmin.forms[ formIndex ].title );
143
  });
144
  };
145
  // Add event listener for delete button
176
  'field': {
177
  id: $( that ).data( 'id' )
178
  },
179
+ 'security': nfAdmin.nonce
180
  };
181
 
182
+ $.post( nfAdmin.ajax_url, data )
183
  .done( function( response ) {
184
  $( that ).closest( 'tr').fadeOut().remove();
185
  });
194
  // TODO: Maybe this should be build using DOM node construction?
195
  content: downgradeContainer.innerHTML,
196
  btnPrimary: {
197
+ text: nfAdmin.i18n.downgradeButtonPrimary,
198
  class: 'nfDowngradeButtonPrimary',
199
  callback: function( e ) {
200
  // If our "Downgrade" button does not have have an attribute of disabled...
210
  }
211
  },
212
  btnSecondary: {
213
+ text: nfAdmin.i18n.downgradeButtonSecondary,
214
  class: 'nfDowngradeButtonSecondary',
215
  callback: function( e ) {
216
  // Close the modal if this button is clicked.
256
  } );
257
 
258
  // If we're allowed to track site data...
259
+ if ( '1' == nfAdmin.allow_telemetry ) {
260
  // Show the optout button.
261
  $( '#nfTelOptin' ).addClass( 'hidden' );
262
  $( '#nfTelOptout' ).removeClass( 'hidden' );
294
  } );
295
 
296
  jQuery( '#nfTrashExpiredSubmissions' ).click( function( e ) {
297
+ var settings = {
298
+ content: '<p>' + nfAdmin.i18n.trashExpiredSubsMessage + '</p>',
299
+ btnPrimaryText: nfAdmin.i18n.trashExpiredSubsButtonPrimary,
300
+ btnSecondaryText: nfAdmin.i18n.trashExpiredSubsButtonSecondary,
301
+ batch_type: 'expired_submission_cleanup',
302
+ // extraData: [ 'test1', 'test2', 'test3' ]
303
+ }
304
+ new NinjaBatchProcessor( settings );
305
+ });
 
 
 
 
 
306
 
307
+ jQuery( '#nfRemoveMaintenanceMode' ).click( function( e ) {
308
+
309
+ var that = this;
310
+
311
+ jQuery( this ).addClass( 'disabled' ).attr( 'disabled', 'disabled' );
312
+ jQuery( '#nf_maintenanceModeProgress' ).html("<strong>Removing Maintenance Mode...</strong>" );
313
+ jQuery( '#nf_maintenanceModeProgress' ).fadeIn( 1 );
314
+
315
+ var data = {
316
+ action: 'nf_remove_maintenance_mode',
317
+ security: nf_settings.nonce,
318
  };
319
+ $.post(
320
+ nf_settings.ajax_url,
321
+ data
322
+ ).then (function( response ) {
323
+ response = JSON.parse( response );
324
+ // if there are errors then, console it out
325
+ if( response.data.errors ) {
326
+ console.log( response.data.errors );
327
+ }
328
 
329
+ jQuery( that ).removeClass( 'disabled' ).removeAttr( 'disabled' );
330
+ jQuery( '#nf_maintenanceModeProgress' ).html("<strong>Done.</strong>" );
331
+ jQuery( '#nf_maintenanceModeProgress' ).fadeOut( 600 );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
332
 
333
+ } ).fail( function( xhr, status, error ) {
334
+ // writes error messages to console to help us debug
335
+ console.log( xhr.status + ' ' + error + '\r\n' +
336
+ 'There was an error resetting maintenance mode' );
337
+ });
338
+
339
+ } );
340
  });
assets/js/lib/batch-processor.js ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Batch Processor JS Object
3
+ */
4
+ function NinjaBatchProcessor( settings ) {
5
+ var that = this;
6
+ var modalData = {
7
+ closeOnClick: false,
8
+ closeOnEsc: true,
9
+ useProgressBar: true
10
+ };
11
+
12
+ /**
13
+ * If we haven't been passed any content, make sure we pass an empty content var.
14
+ */
15
+ if ( 'undefined' == typeof settings.content ) {
16
+ settings.content = '';
17
+ }
18
+ // Set our modalData content var.
19
+ modalData.content = settings.content;
20
+
21
+ /**
22
+ * If we've been passed a loadingText var, pass that along.
23
+ */
24
+ if ( 'undefined' != typeof settings.loadingText ) {
25
+ modalData.loadingText = settings.loadingText;
26
+ }
27
+
28
+ /**
29
+ * If we haven't defined button text for our primary button, we don't want to pass button settings.
30
+ *
31
+ * Check to see if we've defined primary button text, and if we have, add button settings to the modalData.
32
+ */
33
+ if ( 'undefined' != typeof settings.btnPrimaryText ) {
34
+ modalData.btnPrimary = {
35
+ text: settings.btnPrimaryText,
36
+ callback: function( e ) {
37
+ // Hide the buttons.
38
+ modalInstance.maybeShowActions( false );
39
+ // Show the progress bar.
40
+ modalInstance.maybeShowProgress( true );
41
+ // Begin our cleanup process.
42
+ that.postToProcessor( that, -1, modalInstance );
43
+
44
+ }
45
+ };
46
+
47
+ modalData.btnSecondary = {
48
+ text: settings.btnSecondaryText,
49
+ callback: function( e ) {
50
+ modalInstance.toggleModal( false );
51
+ }
52
+ };
53
+ } else { // If we don't have any buttons defined, then we want to run the batch process on modal open.
54
+ modalData.onOpenCallback = function() {
55
+ // Hide the buttons.
56
+ this.maybeShowActions( false );
57
+ // Show the progress bar.
58
+ this.maybeShowProgress( true );
59
+ // Begin our cleanup process.
60
+ that.postToProcessor( that, -1, this );
61
+ }
62
+ }
63
+
64
+ this.postToProcessor = function( context, steps, modal, data ) {
65
+ if ( 'undefined' == typeof data ) {
66
+ var data = {
67
+ action: 'nf_batch_process',
68
+ batch_type: settings.batch_type,
69
+ security: nfAdmin.batchNonce,
70
+ extraData: settings.extraData
71
+ };
72
+ }
73
+
74
+ jQuery.post( ajaxurl, data, function( response ) {
75
+ response = JSON.parse( response );
76
+ // If we're done...
77
+ if ( response.batch_complete ) {
78
+ // Push our progress bar to 100%.
79
+ modal.setProgress( 100 );
80
+ modal.toggleModal( false );
81
+ if ( 'undefined' != typeof settings.onCompleteCallback ) {
82
+ settings.onCompleteCallback( response );
83
+ }
84
+ // Exit.
85
+ return false;
86
+ }
87
+ // If we do not yet have a determined number of steps...
88
+ if ( -1 == steps ) {
89
+ // If step_toal is defined...
90
+ if ( 'undefined' != typeof response.step_total ) {
91
+ // Use the step_total.
92
+ steps = response.step_total;
93
+ } // Otherwise... (step_total is not defined)
94
+ else {
95
+ // Use step_remaining.
96
+ steps = response.step_remaining;
97
+ }
98
+ }
99
+ // If our PHP edited our extraData variable, update our JS var and pass it along.
100
+ if ( 'undefined' != typeof response.extraData ) {
101
+ // Update our extraData property.
102
+ data.extraData = response.extraData;
103
+ }
104
+
105
+ // Calculate our current step.
106
+ var step = steps - response.step_remaining;
107
+ // Calculate our maximum progress for this step.
108
+ var maxProgress = Math.round( step / steps * 100 );
109
+ // Increment the progress.
110
+ modal.incrementProgress ( maxProgress );
111
+ // Recall our function...
112
+ context.postToProcessor( context, steps, modal, data );
113
+ } );
114
+ }
115
+
116
+ var modalInstance = new NinjaModal( modalData );
117
+ }
assets/js/lib/ninjaModal.js CHANGED
@@ -37,6 +37,7 @@ function NinjaModal ( data ) {
37
  this.buttons.secondary = {};
38
  this.buttons.primary.data = ( 'undefined' != typeof data.btnPrimary ? data.btnPrimary : false );
39
  this.buttons.secondary.data = ( 'undefined' != typeof data.btnSecondary ? data.btnSecondary : false );
 
40
  // See if we need the progress bar.
41
  this.useProgressBar = ( 'undefined' != typeof data.useProgressBar ? data.useProgressBar : false );
42
  if ( this.useProgressBar ) {
@@ -170,6 +171,10 @@ NinjaModal.prototype.initModal = function () {
170
  // Attach the callback.
171
  jQuery( this.content ).find( '#nf-button-secondary-' + this.dataId ).click( that.buttons.secondary.callback );
172
  }
 
 
 
 
173
  },
174
  } );
175
  // Setup our data id to keep the DOM ids unique.
@@ -268,6 +273,7 @@ NinjaModal.prototype.renderContent = function () {
268
  this.buttons.secondary.dom.onclick = this.buttons.secondary.callback;
269
  }
270
  }
 
271
  // Set our content.
272
  this.popup.setContent( document.createElement( 'div' ).appendChild( contentBox ).parentElement.innerHTML );
273
  }
37
  this.buttons.secondary = {};
38
  this.buttons.primary.data = ( 'undefined' != typeof data.btnPrimary ? data.btnPrimary : false );
39
  this.buttons.secondary.data = ( 'undefined' != typeof data.btnSecondary ? data.btnSecondary : false );
40
+ this.onOpenCallback = ( 'undefined' != typeof data.onOpenCallback ? data.onOpenCallback : false );
41
  // See if we need the progress bar.
42
  this.useProgressBar = ( 'undefined' != typeof data.useProgressBar ? data.useProgressBar : false );
43
  if ( this.useProgressBar ) {
171
  // Attach the callback.
172
  jQuery( this.content ).find( '#nf-button-secondary-' + this.dataId ).click( that.buttons.secondary.callback );
173
  }
174
+
175
+ if ( that.onOpenCallback ) {
176
+ that.onOpenCallback();
177
+ }
178
  },
179
  } );
180
  // Setup our data id to keep the DOM ids unique.
273
  this.buttons.secondary.dom.onclick = this.buttons.secondary.callback;
274
  }
275
  }
276
+
277
  // Set our content.
278
  this.popup.setContent( document.createElement( 'div' ).appendChild( contentBox ).parentElement.innerHTML );
279
  }
assets/js/min/dashboard.min.js CHANGED
@@ -1,2 +1,3 @@
1
- !function(){var e,t,n;!function(i){function o(e,t){return y.call(e,t)}function s(e,t){var n,i,o,s,r,a,l,c,d,u,h,m=t&&t.split("/"),f=v.map,p=f&&f["*"]||{};if(e&&"."===e.charAt(0))if(t){for(e=e.split("/"),r=e.length-1,v.nodeIdCompat&&j.test(e[r])&&(e[r]=e[r].replace(j,"")),e=m.slice(0,m.length-1).concat(e),d=0;d<e.length;d+=1)if("."===(h=e[d]))e.splice(d,1),d-=1;else if(".."===h){if(1===d&&(".."===e[2]||".."===e[0]))break;d>0&&(e.splice(d-1,2),d-=2)}e=e.join("/")}else 0===e.indexOf("./")&&(e=e.substring(2));if((m||p)&&f){for(n=e.split("/"),d=n.length;d>0;d-=1){if(i=n.slice(0,d).join("/"),m)for(u=m.length;u>0;u-=1)if((o=f[m.slice(0,u).join("/")])&&(o=o[i])){s=o,a=d;break}if(s)break;!l&&p&&p[i]&&(l=p[i],c=d)}!s&&l&&(s=l,a=c),s&&(n.splice(0,a,s),e=n.join("/"))}return e}function r(e,t){return function(){var n=C.call(arguments,0);return"string"!=typeof n[0]&&1===n.length&&n.push(null),m.apply(i,n.concat([e,t]))}}function a(e){return function(t){return s(t,e)}}function l(e){return function(t){w[e]=t}}function c(e){if(o(g,e)){var t=g[e];delete g[e],b[e]=!0,h.apply(i,t)}if(!o(w,e)&&!o(b,e))throw new Error("No "+e);return w[e]}function d(e){var t,n=e?e.indexOf("!"):-1;return n>-1&&(t=e.substring(0,n),e=e.substring(n+1,e.length)),[t,e]}function u(e){return function(){return v&&v.config&&v.config[e]||{}}}var h,m,f,p,w={},g={},v={},b={},y=Object.prototype.hasOwnProperty,C=[].slice,j=/\.js$/;f=function(e,t){var n,i=d(e),o=i[0];return e=i[1],o&&(o=s(o,t),n=c(o)),o?e=n&&n.normalize?n.normalize(e,a(t)):s(e,t):(e=s(e,t),i=d(e),o=i[0],e=i[1],o&&(n=c(o))),{f:o?o+"!"+e:e,n:e,pr:o,p:n}},p={require:function(e){return r(e)},exports:function(e){var t=w[e];return void 0!==t?t:w[e]={}},module:function(e){return{id:e,uri:"",exports:w[e],config:u(e)}}},h=function(e,t,n,s){var a,d,u,h,m,v,y=[],C=typeof n;if(s=s||e,"undefined"===C||"function"===C){for(t=!t.length&&n.length?["require","exports","module"]:t,m=0;m<t.length;m+=1)if(h=f(t[m],s),"require"===(d=h.f))y[m]=p.require(e);else if("exports"===d)y[m]=p.exports(e),v=!0;else if("module"===d)a=y[m]=p.module(e);else if(o(w,d)||o(g,d)||o(b,d))y[m]=c(d);else{if(!h.p)throw new Error(e+" missing "+d);h.p.load(h.n,r(s,!0),l(d),{}),y[m]=w[d]}u=n?n.apply(w[e],y):void 0,e&&(a&&a.exports!==i&&a.exports!==w[e]?w[e]=a.exports:u===i&&v||(w[e]=u))}else e&&(w[e]=n)},e=t=m=function(e,t,n,o,s){if("string"==typeof e)return p[e]?p[e](t):c(f(e,t).f);if(!e.splice){if(v=e,v.deps&&m(v.deps,v.callback),!t)return;t.splice?(e=t,t=n,n=null):e=i}return t=t||function(){},"function"==typeof n&&(n=o,o=s),o?h(i,e,t,n):setTimeout(function(){h(i,e,t,n)},4),m},m.config=function(e){return m(e)},e._defined=w,n=function(e,t,n){if("string"!=typeof e)throw new Error("See almond README: incorrect module build, no module name");t.splice||(n=t,t=[]),o(w,e)||o(g,e)||(g[e]=[e,t,n])},n.amd={jQuery:!0}}(),n("../../assets/js/lib/almond",function(){}),n("models/formModel",[],function(){return Backbone.Model.extend({defaults:{objectType:"form",id:0,title:"unknown",created_at:"unknown"},url:function(){return ajaxurl+"?action=nf_forms&form_id="+this.get("id")},initialize:function(){this.set("id",Number(this.get("id"))),this.get("id")&&this.initShortcode(this.get("id")),this.get("title")&&this.set("title",this.get("title").replace(/<\/?[^>]+(>|$)/g,""))},initShortcode:function(e){var t="[ninja_form id="+e+"]";this.set("shortcode",t)},destroy:function(){var e=this;jQuery.ajax({type:"POST",url:ajaxurl+"?action=nf_forms&method_override=delete&form_id="+this.get("id"),success:function(t){var t=JSON.parse(t);e.collection.remove(e)}})}})}),n("models/formCollection",["models/formModel"],function(e){return Backbone.Collection.extend({model:e,comparator:"title",tmpNum:1,url:function(){return ajaxurl+"?action=nf_forms"},initialize:function(){this.newIDs=[],this.baseUrl=window.location.href.split("?")[0],this.listenTo(i.channel("dashboard"),"forms:delete",this.modalConfirm),this.listenTo(i.channel("dashboard"),"forms:duplicate",this.duplicate),this.modal=new jBox("Modal",{width:400,addClass:"dashboard-modal",overlay:!0,closeOnClick:"body"})},parse:function(e,t){return e.data},modalConfirm:function(e){var t,n,i,o,s,r,a,l=e.model.get("id"),c=e.model.get("title");n=document.createElement("div"),n.style.paddingRight="20px",n.style.paddingLeft="20px",n.style.paddingBottom="20px",i=document.createElement("p"),o=document.createElement("em"),s=document.createElement("div"),r=document.createElement("button"),a=document.createElement("div"),n.classList.add("message"),o.innerHTML=c,i.innerHTML+=nfi18n.deleteWarningA+" (<strong>"+c+"</strong>). "+nfi18n.deleteWarningB,i.appendChild(document.createElement("br")),i.appendChild(document.createElement("br"));var d=document.createElement("a");d.href=this.baseUrl+"?page=nf-import-export&exportFormId="+l,d.innerHTML='<i class="fa fa-download" style="padding:5px;"></i>'+nfi18n.deleteXForm,d.target="_blank",i.appendChild(d),i.appendChild(document.createElement("br"));var u=document.createElement("a");u.href=this.baseUrl+"?page=nf-processing&action=download_all_subs&form_id="+l+"&redirect="+encodeURIComponent(this.baseUrl.replace("admin.php","edit.php")+"?post_status=all&post_type=nf_sub&form_id="+l),u.target="_blank",u.innerHTML='<i class="fa fa-download" style="padding:5px;"></i>'+nfi18n.deleteXSubs,i.appendChild(u),i.appendChild(document.createElement("br")),n.appendChild(i);var h=document.createElement("label");h.for="confirmDeleteFormInput",h.innerHTML=nfi18n.deleteConfirmA+' <span style="color:red;">DELETE</span> '+nfi18n.deleteConfirmB;var m=document.createElement("input");m.type="text",m.id="confirmDeleteFormInput",m.style.marginTop="10px",m.style.width="100%",m.style.height="2.5em",m.style.fontSize="1em",n.appendChild(h),n.appendChild(document.createElement("br")),n.appendChild(m),n.appendChild(document.createElement("br")),n.appendChild(document.createElement("br")),r.innerHTML=nfi18n.delete,r.classList.add("confirm","nf-button","primary","pull-right"),a.innerHTML=nfi18n.cancel,a.classList.add("cancel","nf-button","secondary"),s.appendChild(a),s.appendChild(r),s.classList.add("buttons"),n.appendChild(s),t=document.createElement("div"),t.appendChild(n),this.modal.setContent(t.innerHTML),this.modal.setTitle(nfi18n.deleteTitle),this.modal.open();var f=this;this.modal.container[0].getElementsByClassName("cancel")[0].addEventListener("click",function(){f.modalClose()}),this.modal.container[0].getElementsByClassName("confirm")[0].addEventListener("click",function(t){t.preventDefault(),"DELETE"===document.getElementById("confirmDeleteFormInput").value?f.confirmDelete(e):f.modalClose()})},modalClose:function(){this.modal.close()},confirmDelete:function(e){jQuery(e.el).removeClass("show-actions"),jQuery(e.el).addClass("deleting"),jQuery(e.el).animate({opacity:0,"line-height":0,display:"none"},500),console.log(e),e.model.destroy(),this.modalClose()},duplicate:function(e){var t='<div class="message">Duplicating <em>'+e.model.get("title")+'</em>...<div class="nf-loading-spinner"></div></div>';this.modal.setContent(t),this.modal.setTitle("Please Wait"),this.modal.open();var n=this;jQuery.ajax({type:"POST",url:ajaxurl+"?action=nf_forms&clone_id="+e.model.get("id"),success:function(t){var t=JSON.parse(t),i=t.data.new_form_id,o=e.model.clone();o.set({id:i,title:o.get("title")+" - copy",created_at:new Date}),o.initShortcode(i),e.model.collection.add(o),n.modalClose()}})}})}),n("controllers/formsController",["models/formModel","models/formCollection"],function(e,t){return Marionette.Object.extend({initialize:function(){this.forms=new t,i.channel("dashboard").reply("get:forms",this.getForms,this),this.forms.fetch({success:function(e){i.channel("dashboard").trigger("fetch:forms",e)}})},getForms:function(){return this.forms}})}),n("models/oauthModel",[],function(){return Backbone.Model.extend({defaults:{connected:null,connect_url:""},url:function(){return ajaxurl+"?action=nf_oauth"},initialize:function(){},parse:function(e,t){return e.data}})}),n("controllers/oauthController",["models/oauthModel"],function(e){return Marionette.Object.extend({initialize:function(){this.oauth=new e,i.channel("dashboard").reply("get:oauth",this.getOAuth,this),i.channel("dashboard").reply("disconnect:oauth",this.disconnect,this),i.channel("dashboard").reply("oauth:learn-more",this.learnMoreModal,this),this.initOAuth()},getOAuth:function(){return this.oauth},initOAuth:function(){this.oauth.fetch({success:function(e){i.channel("dashboard").trigger("fetch:oauth")}})},disconnect:function(){var e=this;new jBox("Confirm",{width:750,content:nfi18n.oauthDisconnectContent,confirmButton:nfi18n.oauthDisconnectConfirm,cancelButton:nfi18n.oauthDisconnectCancel,closeOnConfirm:!0,confirm:function(){jQuery.ajax({type:"POST",url:ajaxurl+"?action=nf_oauth_disconnect",success:function(t){console.log(t),e.initOAuth()}})}}).open()},learnMoreModal:function(){new jBox("Modal",{width:500,content:nfi18n.oauthLearnMoreContent}).open()}})}),n("models/serviceModel",[],function(){return Backbone.Model.extend({defaults:{objectType:"service",name:"",slug:"",installPath:"",description:"",enabled:null,infoLink:null,serviceLink:null,is_installing:!1,classes:""},url:function(){return ajaxurl+"?action=nf_service_"+this.get("slug")},initialize:function(){this.get("slug")==serviceSuccess&&this.get("successMessage")&&new jBox("Modal",{width:300,addClass:"dashboard-modal",overlay:!0,closeOnClick:!0,content:this.get("successMessage"),title:this.get("successMessageTitle"),closeButton:"box"}).open();var e=this;i.channel("dashboard").reply("install:service:"+this.get("slug"),function(){if(e.get("serviceLink")&&e.get("serviceLink").href){var t=e.get("serviceLink").href;new jBox("Modal",{width:300,addClass:"dashboard-modal",overlay:!0,closeOnClick:"body",content:nfi18n.serviceRedirect}).open();var n=i.channel("dashboard").request("get:oauth");if(n.get("connected"))window.location=t;else{if(e.get("connect_url"))return window.location=e.get("connect_url")+"&redirect="+t;window.location=n.get("connect_url")+"&redirect="+t}}})},save:function(){var e=this;jQuery.ajax({type:"POST",url:this.url(),data:this.toJSON()}).done(function(t){var n=JSON.parse(t);void 0!==n.error&&(alert(nfi18n.serviceUpdateError+" "+n.error),e.set("enabled",!e.get("enabled"))),i.channel("dashboard").trigger("save:service-"+e.get("slug"))})}})}),n("models/serviceCollection",["models/serviceModel"],function(e){return Backbone.Collection.extend({model:e,comparator:"name",url:function(){return ajaxurl+"?action=nf_services"},initialize:function(){},parse:function(e,t){return e.data}})}),n("controllers/servicesController",["models/serviceCollection"],function(e){return Marionette.Object.extend({initialize:function(){this.services=new e,i.channel("dashboard").reply("install:service",this.installService,this),i.channel("dashboard").reply("get:services",this.getServices,this),this.fetchServices()},getServices:function(){return this.services},fetchServices:function(e){this.services.fetch({success:function(t){e&&e(t),i.channel("dashboard").trigger("fetch:services")}})},installService:function(e){var t=this;if(!(e instanceof Backbone.Model))var e=this.services.find(function(t){return e==t.get("slug")});e.set("is_installing",!0);var n=e.get("slug"),o=e.get("installPath");jQuery.post(ajaxurl,{action:"nf_services_install",plugin:n,install_path:o},function(e){t.fetchServices(function(){i.channel("dashboard").request("install:service:"+n)})})}})}),n("views/widgets/forms/formsFilter",[],function(){return Marionette.View.extend({template:"#tmpl-nf-widget-forms-filter",ui:{input:"input"},events:{"keyup @ui.input":"updateFilter"},initialize:function(){this.listenTo(i.channel("widget-forms"),"change:content",this.clearFilter)},updateFilter:function(){var e=this.getUI("input").val();i.channel("widget-forms").trigger("update:filter",e)},clearFilter:function(){this.getUI("input").val("")},updatePlaceholder:function(e){this.getUI("input").attr("placeholder",e)}})}),n("views/widgets/forms/formsTableRow",[],function(){return Marionette.View.extend({template:"#tmpl-nf-widget-forms-table-row",tagName:"tr",replaceElement:!0,ui:{delete:".delete",duplicate:".duplicate",edit:".nf-item-edit"},events:{"click @ui.delete":function(){i.channel("dashboard").trigger("forms:delete",this)},"click @ui.duplicate":function(){i.channel("dashboard").trigger("forms:duplicate",this)},"click @ui.edit":function(e){this.$el.toggleClass("show-actions").siblings().removeClass("show-actions")}},templateContext:function(){var e=this.model;return{created_at:moment(e.get("created_at")).format("MM/DD/YY h:mm A")}}})}),n("views/widgets/forms/formsTableEmpty",[],function(){return Marionette.View.extend({template:"#tmpl-nf-widget-forms-table-empty",tagName:"tr"})}),n("views/widgets/forms/formsTableBody",["views/widgets/forms/formsTableRow","views/widgets/forms/formsTableEmpty"],function(e,t){return Marionette.CollectionView.extend({childView:e,emptyView:t,className:"forms-collection",tagName:"tbody",initialize:function(){this.listenTo(i.channel("widget-forms"),"update:filter",this.updateFilter)},updateFilter:function(e){this.setFilter(function(t,n,i){return 0<=t.get("title").toLowerCase().indexOf(e.toLowerCase())})}})}),n("views/widgets/forms/formsTableLoading",[],function(){return Marionette.View.extend({template:"#tmpl-nf-widget-forms-table-loading",tagName:"tr"})}),n("views/widgets/forms/formsTable",["views/widgets/forms/formsTableBody","views/widgets/forms/formsTableLoading","models/formCollection"],function(e,t,n){return Marionette.View.extend({template:"#tmpl-nf-widget-forms-table",className:"nf-table-display",tagName:"table",initialize:function(){var t=this;this.listenTo(i.channel("dashboard"),"fetch:forms",function(n){t.showChildView("body",new e({collection:n}))})},regions:{body:{el:"tbody",replaceElement:!0}},ui:{sortable:".sortable",body:"tbody",action2:".action2",more:".more",less:".less"},onRender:function(){this.getUI("less").hide();var n=i.channel("dashboard").request("get:forms");void 0===n?this.showChildView("body",new t):this.showChildView("body",new e({collection:n})),this.maybeHideMoreButton()},events:{"click @ui.sortable":"sortFormsTable","click @ui.more":"showMore","click @ui.less":"showLess"},sortFormsTable:function(e){this.getUI("sortable").removeClass("sorted-asc"),this.getUI("sortable").removeClass("sorted-desc");var t=jQuery(e.target).data("sort"),n=jQuery(e.target).data("reverse")||0;n?(jQuery(e.target).addClass("sorted-desc"),jQuery(e.target).removeClass("sorted-asc")):(jQuery(e.target).addClass("sorted-asc"),jQuery(e.target).removeClass("sorted-desc"));var i=this.getChildView("body").collection;i.comparator=function(e,i){return name1=e.get(t).toLowerCase(),name2=i.get(t).toLowerCase(),name1<name2?ret=-1:name1>name2?ret=1:ret=0,n&&(ret=-ret),ret},i.sort(),n?(i.models.reverse(),jQuery(e.target).data("reverse",0)):jQuery(e.target).data("reverse",1)},showMore:function(){this.getUI("more").hide(),this.getUI("less").show(),this.getUI("body").addClass("more")},showLess:function(){this.getUI("less").hide(),this.getUI("more").show(),this.getUI("body").removeClass("more")},maybeHideMoreButton:function(){void 0!==this.collection&&10<this.collection.length||this.getUI("action2").hide()}})}),n("models/formTemplateModel",[],function(){return Backbone.Model.extend({defaults:{objectType:"template",id:"none",title:"unknown",type:""},initialize:function(){this.set("desc",this.get("template-desc")),this.set("modal-content",this.get("modal-content")),this.set("modal-title",this.get("modal-title"))}})}),n("models/formTemplateCollection",["models/formTemplateModel"],function(e){return Backbone.Collection.extend({model:e,tmpNum:1,url:function(){return ajaxurl+"?action=nf_new_form_templates"},parse:function(e,t){return e.data},initialize:function(){this.fetch({success:function(e){},error:function(e){}})}})}),n("views/widgets/forms/newFormTemplate",[],function(){return Marionette.View.extend({template:"#tmpl-nf-widget-forms-template",events:{click:"maybeOpenModal"},maybeOpenModal:function(e){if("ad"!=this.model.get("type"))return!0;e.preventDefault(),new jBox("Modal",{width:450,title:this.model.get("modal-title"),content:this.model.get("modal-content"),closeButton:"box",blockScroll:!0}).open()}})}),n("views/widgets/forms/newFormGrid",["models/formTemplateCollection","views/widgets/forms/newFormTemplate"],function(e,t){return Marionette.CollectionView.extend({tagName:"div",className:"template-list",collection:new e,childView:t,initialize:function(){this.listenTo(i.channel("widget-forms"),"update:filter",this.updateFilter)},updateFilter:function(e){this.setFilter(function(t,n,i){return 0<=t.get("title").toLowerCase().indexOf(e.toLowerCase())})}})}),n("views/widgets/forms/forms",["views/widgets/forms/formsFilter","views/widgets/forms/formsTable","views/widgets/forms/newFormGrid"],function(e,t,n){return Marionette.View.extend({template:"#tmpl-nf-widget-forms",regions:{filter:".filter",content:".content"},ui:{add:".add",cancel:".cancel"},initialize:function(){i.channel("widget-forms").reply("show:newFormsGrid",this.showNewFormGrid,this),i.channel("widget-forms").reply("show:formsTable",this.showFormsTable,this)},onRender:function(){this.getUI("cancel").hide(),this.showChildView("filter",new e),"#new-form"==window.location.hash?(this.getUI("add").hide(),this.getUI("cancel").show(),this.showChildView("content",new n)):this.showChildView("content",new t)},events:{"click @ui.add":"showNewFormGrid","click @ui.cancel":"showFormsTable"},showNewFormGrid:function(){window.location.hash="new-form",this.showChildView("content",new n),i.channel("widget-forms").trigger("change:content"),this.getUI("add").hide(),this.getUI("cancel").show(),this.getChildView("filter").updatePlaceholder("Search Templates")},showFormsTable:function(){console.log(this),window.location.hash="forms",this.showChildView("content",new t),i.channel("widget-forms").trigger("change:content"),this.getUI("cancel").hide(),this.getUI("add").show(),this.getChildView("filter").updatePlaceholder("Search Forms")}})}),n("views/sections/widgets.js",["views/widgets/forms/forms"],function(e){return Marionette.View.extend({template:"#tmpl-nf-widgets",regions:{forms:".widget-forms"},onRender:function(){this.showChildView("forms",new e)}})}),n("views/services/service",[],function(){return Marionette.View.extend({template:"#tmpl-nf-service",className:function(){return"nf-extend nf-box "+this.model.get("classes")},ui:{install:".js--install",learnMore:".js--learn-more",enabled:".nf-toggle.setting",toggleEnable:".nf-toggle + label"},events:{"click @ui.install":function(){i.channel("dashboard").request("install:service",this.model)},"click @ui.learnMore":function(){this.showLearnMore()},"click @ui.toggleEnable":function(){if(null==this.model.get("enabled")&&this.model.get("link"))return window.location=this.model.get("link"),this.render();this.model.set("enabled",!this.model.get("enabled")),this.model.save("enabled"),this.render()}},initialize:function(e){this.updateOAuth(),this.listenTo(this.model,"change",this.render),i.channel("dashboard").reply("more:service:"+this.model.get("slug"),this.showLearnMore,this),this.listenTo(i.channel("dashboard"),"fetch:oauth",this.updateOAuth),this.listenTo(i.channel("dashboard"),"save:service-"+this.model.get("slug"),this.render)},showLearnMore:function(){var e=new jBox("Modal",{width:750,title:this.model.get("learnMoreTitle")||this.model.get("name"),content:this.model.get("learnMore"),closeButton:"box",blockScroll:!0});e.open(),i.channel("dashboard").reply("service:"+this.model.get("slug")+":modal",function(){return e})},updateOAuth:function(){var e=i.channel("dashboard").request("get:oauth");this.connected=e.get("connected"),this.render()},templateContext:function(){return{is_connected:this.connected}}})}),n("views/services/services",["views/services/service","models/serviceCollection"],function(e,t){return Marionette.CollectionView.extend({collection:new t,className:"wrap apps-container",childView:e,initialize:function(){this.updateCollection(),this.listenTo(i.channel("dashboard"),"fetch:services",this.updateCollection)},updateCollection:function(){this.collection=i.channel("dashboard").request("get:services"),this.render()}})}),n("views/sections/services.js",["views/services/services"],function(e){return Marionette.View.extend({template:"#tmpl-nf-services",regions:{services:".services"},onRender:function(){this.showChildView("services",new e)}})}),n("views/sections/apps.js",[],function(){return Marionette.View.extend({template:"#tmpl-nf-apps"})}),n("views/sections/memberships.js",[],function(){return Marionette.View.extend({template:"#tmpl-nf-memberships"})}),n("views/oauth.js",["models/oauthModel"],function(e){return Marionette.View.extend({model:new e,template:"#tmpl-nf-notices-oauth",className:"nf-notices--oauth",ui:{disconnect:".js--disconnect"},initialize:function(e){this.listenTo(i.channel("dashboard"),"fetch:oauth",this.updateModel)},updateModel:function(){this.model=i.channel("dashboard").request("get:oauth"),this.render()},events:{"click @ui.disconnect":function(){i.channel("dashboard").request("disconnect:oauth")}}})}),n("models/promotionModel",[],function(){return Backbone.Model.extend({defaults:{id:"",content:""},initialize:function(){}})}),n("views/promotion.js",["models/promotionModel"],function(e){return Marionette.View.extend({model:null,template:"#tmpl-nf-promotion",className:"nf-promotion",initialize:function(){var t=nfPromotions[Math.floor(Math.random()*nfPromotions.length)];this.model=new e(t)}})}),n("views/dashboardView",["views/sections/widgets.js","views/sections/services.js","views/sections/apps.js","views/sections/memberships.js","views/oauth.js","views/promotion.js"],function(e,t,n,o,s,r){return Marionette.View.extend({template:"#tmpl-nf-dashboard",currentView:"widgets",regions:{notices:".notices",promotions:".promotions",content:".content"},events:{"click .widgets a":function(t){this.showChildView("content",new e),jQuery("."+this.currentView).find("a").removeClass("active"),t.target.classList.add("active"),this.currentView="widgets"},"click .services a":function(e){this.showChildView("content",new t),jQuery("."+this.currentView).find("a").removeClass("active"),e.target.classList.add("active"),this.currentView="services"},"click .apps a":function(e){this.showChildView("content",new n),jQuery("."+this.currentView).find("a").removeClass("active"),e.target.classList.add("active"),this.currentView="apps"},"click .memberships a":function(e){this.showChildView("content",new o),jQuery("."+this.currentView).find("a").removeClass("active"),e.target.classList.add("active"),this.currentView="memberships"}},initialize:function(){switch(window.location.hash){case"#apps":this.currentView="apps";break;case"#services":this.currentView="services";break;case"#memberships":this.currentView="memberships";break;case"#widgets":default:this.currentView="widgets"}i.channel("dashboard").reply("show:widgets",function(){this.showChildView("content",new e),jQuery("nav.sections a.active").removeClass("active"),jQuery("nav.sections .widgets a").addClass("active"),this.currentView="widgets"},this),i.channel("dashboard").reply("show:services",function(){this.showChildView("content",new t),jQuery("nav.sections a.active").removeClass("active"),jQuery("nav.sections .services a").addClass("active"),this.currentView="services"},this),i.channel("dashboard").reply("show:apps",function(){this.showChildView("content",new n),jQuery("nav.sections a.active").removeClass("active"),jQuery("nav.sections .apps a").addClass("active"),this.currentView="apps"},this)},onRender:function(){switch(useServices&&this.showChildView("notices",new s),useServices&&this.showChildView("promotions",new r),window.location.hash){case"#apps":var i=new n;break;case"#memberships":var i=new o;break;case"#services":var i=new t;break;case"#widgets":default:var i=new e}if(this.showChildView("content",i),"1"==nfAdmin.showOptin){var a=new jBox("Modal",{closeOnEsc:!1,closeOnClick:!1,width:400}),l=document.createElement("div");l.id="optin-modal-title";var c=document.createElement("h2");c.innerHTML="Help make Ninja Forms better!",l.appendChild(c);var d=document.createElement("div");d.classList.add("message"),d.style.padding="0px 20px 20px 20px",d.innerHTML=nfi18n.optinContent;var u=document.createElement("p");u.style.paddingBottom="10px";var h=document.createElement("input");h.id="optin-send-email",h.setAttribute("type","checkbox"),h.style.margin="7px";var m=document.createElement("label");m.setAttribute("for","optin-send-email"),m.innerHTML=nfi18n.optinYesplease,u.appendChild(h),u.appendChild(m),d.appendChild(u),u=document.createElement("p"),u.id="optin-block",u.style.padding="0px 5px 20px 5px",u.style.display="none";var f=document.createElement("input");f.id="optin-email-address",f.setAttribute("type","text"),f.setAttribute("value",nfAdmin.currentUserEmail),f.style.width="100%",f.style.fontSize="16px",u.appendChild(f),d.appendChild(u);var p=document.createElement("span");p.id="optin-spinner",p.classList.add("spinner"),p.style.display="none",d.appendChild(p);var w=document.createElement("div");w.id="optin-buttons",w.classList.add("buttons");var g=document.createElement("div");g.id="optout",g.classList.add("nf-button","secondary"),g.innerHTML=nfi18n.optinSecondary,w.appendChild(g);var v=document.createElement("div");v.id="optin",v.classList.add("nf-button","primary","pull-right"),v.innerHTML=nfi18n.optinPrimary,w.appendChild(v),d.appendChild(w);var b=document.createElement("h2");b.innerHTML=nfi18n.optinAwesome;var y=document.createElement("div");y.id="optin-thankyou",y.classList.add("message"),y.style.padding="20px",y.innerHTML=nfi18n.optinThanks,a.setContent(document.createElement("div").appendChild(d).innerHTML),a.setTitle(document.createElement("div").appendChild(l).innerHTML),a.open(),jQuery("#optin-send-email").click(function(e){jQuery(this).is(":checked")?jQuery("#optin-block").show():jQuery("#optin-block").hide()}),jQuery("#optin").click(function(e){var t;jQuery("#optin-send-email").attr("checked")?(t=1,userEmail=jQuery("#optin-email-address").val()):(t=0,userEmail=""),jQuery("#optin").unbind("click"),jQuery("#optout").unbind("click");var n=jQuery("#optin").width();jQuery("#optin").html('<span class="dashicons dashicons-update dashicons-update-spin"></span>'),jQuery("#optin").width(n),jQuery.post(ajaxurl,{action:"nf_optin",ninja_forms_opt_in:1,send_email:t,user_email:userEmail},function(e){a.setTitle(document.createElement("div").appendChild(b).innerHTML),a.setContent(document.createElement("div").appendChild(y).innerHTML),setTimeout(function(){a.close()},2e3)})}),jQuery("#optout").click(function(e){jQuery("#optin").unbind("click"),jQuery("#optout").unbind("click");var t=jQuery("#optout").width();jQuery("#optout").html('<span class="dashicons dashicons-update dashicons-update-spin"></span>'),jQuery("#optout").width(t),jQuery.post(ajaxurl,{action:"nf_optin",ninja_forms_opt_in:0},function(e){a.close()})})}else if("1"==nfAdmin.doingCleanup)var C=this,j={width:450,closeOnClick:!1,closeOnEsc:!1,content:nfi18n.cleanupContent,useProgressBar:!0,loadingText:nfi18n.cleanupLoading,btnSecondary:{text:nfi18n.cleanupSecondary,callback:function(){x.toggleModal(!1)}},btnPrimary:{text:nfi18n.cleanupPrimary,callback:function(){jQuery(window).bind("beforeunload",function(){return"Are you sure? Leaving before the process completes could cause damage to your data."}),x.maybeShowActions(!1),x.maybeShowProgress(!0),C.cleanupProcess(C,-1,x)}}},x=new NinjaModal(j);if(void 0!==nfAdmin.formTelemetry&&1==nfAdmin.formTelemetry){var M={action:"nf_form_telemetry",security:nfAdmin.ajaxNonce};jQuery.post(ajaxurl,M)}},templateContext:function(){var e=this;return{renderNav:function(){var t=document.createElement("div");return _.each(nfDashItems,function(n){var i=document.createElement("li"),o=document.createElement("a");o.href="#"+n.slug,e.currentView==n.slug&&o.classList.add("active"),o.innerHTML=n.niceName,i.classList.add(n.slug),i.appendChild(o),t.appendChild(i)}),t.innerHTML}}},cleanupProcess:function(e,t,n){var i={action:"nf_batch_process",batch_type:"data_cleanup",security:nfAdmin.batchNonce};jQuery.post(ajaxurl,i,function(i){if(i=JSON.parse(i),i.batch_complete)return n.setProgress(100),jQuery(window).unbind("beforeunload"),n.toggleModal(!1),!1;-1==t&&(t=void 0!==i.step_total?i.step_total:i.step_remaining);var o=t-i.step_remaining,s=Math.round(o/t*100);n.incrementProgress(s),e.cleanupProcess(e,t,n)})}})});var i=Backbone.Radio;useServices||(nfDashItems=nfDashItems.filter(function(e){return"services"!==e.slug})),jQuery(document).ready(function(e){t(["controllers/formsController","controllers/oauthController","controllers/servicesController","views/dashboardView"],function(t,n,i,o){(new(Marionette.Application.extend({region:"#ninja-forms-dashboard",controllers:{},initialize:function(e){var t=this;Marionette.Renderer.render=function(e,n){var e=t.template(e);return e(n)}},onStart:function(){this.showView(new o),this.controllers.forms=new t,useServices&&(this.controllers.oauth=new n),useServices&&(this.controllers.services=new i)},template:function(t){return _.template(e(t).html(),{evaluate:/<#([\s\S]+?)#>/g,interpolate:/\{\{\{([\s\S]+?)\}\}\}/g,escape:/\{\{([^\}]+?)\}\}(?!\})/g,variable:"data"})}}))).start()})}),jQuery('a[href="admin.php?page=ninja-forms#new-form"]').on("click",function(e){e.preventDefault(),window.location.hash="new-form",i.channel("dashboard").request("show:widgets"),i.channel("widget-forms").request("show:newFormsGrid")}),jQuery('a[href="admin.php?page=ninja-forms#apps"]').on("click",function(e){e.preventDefault(),window.location.hash="apps",i.channel("dashboard").request("show:apps")}),jQuery('a[href="admin.php?page=ninja-forms"]').on("click",function(e){e.preventDefault(),window.location.hash="forms",i.channel("dashboard").request("show:widgets"),i.channel("widget-forms").request("show:formsTable")}),jQuery(window).on("hashchange",function(){var e=window.location.hash.substr(1);i.channel("dashboard").request("show:"+e)}),n("main",function(){})}();
 
2
  //# sourceMappingURL=dashboard.min.js.map
1
+ !function(){var e,t,n;!function(i){function s(e,t){return y.call(e,t)}function o(e,t){var n,i,s,o,r,a,l,d,c,u,h,m=t&&t.split("/"),p=v.map,f=p&&p["*"]||{};if(e&&"."===e.charAt(0))if(t){for(e=e.split("/"),r=e.length-1,v.nodeIdCompat&&j.test(e[r])&&(e[r]=e[r].replace(j,"")),e=m.slice(0,m.length-1).concat(e),c=0;c<e.length;c+=1)if("."===(h=e[c]))e.splice(c,1),c-=1;else if(".."===h){if(1===c&&(".."===e[2]||".."===e[0]))break;c>0&&(e.splice(c-1,2),c-=2)}e=e.join("/")}else 0===e.indexOf("./")&&(e=e.substring(2));if((m||f)&&p){for(n=e.split("/"),c=n.length;c>0;c-=1){if(i=n.slice(0,c).join("/"),m)for(u=m.length;u>0;u-=1)if((s=p[m.slice(0,u).join("/")])&&(s=s[i])){o=s,a=c;break}if(o)break;!l&&f&&f[i]&&(l=f[i],d=c)}!o&&l&&(o=l,a=d),o&&(n.splice(0,a,o),e=n.join("/"))}return e}function r(e,t){return function(){var n=C.call(arguments,0);return"string"!=typeof n[0]&&1===n.length&&n.push(null),m.apply(i,n.concat([e,t]))}}function a(e){return function(t){return o(t,e)}}function l(e){return function(t){w[e]=t}}function d(e){if(s(g,e)){var t=g[e];delete g[e],b[e]=!0,h.apply(i,t)}if(!s(w,e)&&!s(b,e))throw new Error("No "+e);return w[e]}function c(e){var t,n=e?e.indexOf("!"):-1;return n>-1&&(t=e.substring(0,n),e=e.substring(n+1,e.length)),[t,e]}function u(e){return function(){return v&&v.config&&v.config[e]||{}}}var h,m,p,f,w={},g={},v={},b={},y=Object.prototype.hasOwnProperty,C=[].slice,j=/\.js$/;p=function(e,t){var n,i=c(e),s=i[0];return e=i[1],s&&(s=o(s,t),n=d(s)),s?e=n&&n.normalize?n.normalize(e,a(t)):o(e,t):(e=o(e,t),i=c(e),s=i[0],e=i[1],s&&(n=d(s))),{f:s?s+"!"+e:e,n:e,pr:s,p:n}},f={require:function(e){return r(e)},exports:function(e){var t=w[e];return void 0!==t?t:w[e]={}},module:function(e){return{id:e,uri:"",exports:w[e],config:u(e)}}},h=function(e,t,n,o){var a,c,u,h,m,v,y=[],C=typeof n;if(o=o||e,"undefined"===C||"function"===C){for(t=!t.length&&n.length?["require","exports","module"]:t,m=0;m<t.length;m+=1)if(h=p(t[m],o),"require"===(c=h.f))y[m]=f.require(e);else if("exports"===c)y[m]=f.exports(e),v=!0;else if("module"===c)a=y[m]=f.module(e);else if(s(w,c)||s(g,c)||s(b,c))y[m]=d(c);else{if(!h.p)throw new Error(e+" missing "+c);h.p.load(h.n,r(o,!0),l(c),{}),y[m]=w[c]}u=n?n.apply(w[e],y):void 0,e&&(a&&a.exports!==i&&a.exports!==w[e]?w[e]=a.exports:u===i&&v||(w[e]=u))}else e&&(w[e]=n)},e=t=m=function(e,t,n,s,o){if("string"==typeof e)return f[e]?f[e](t):d(p(e,t).f);if(!e.splice){if(v=e,v.deps&&m(v.deps,v.callback),!t)return;t.splice?(e=t,t=n,n=null):e=i}return t=t||function(){},"function"==typeof n&&(n=s,s=o),s?h(i,e,t,n):setTimeout(function(){h(i,e,t,n)},4),m},m.config=function(e){return m(e)},e._defined=w,n=function(e,t,n){if("string"!=typeof e)throw new Error("See almond README: incorrect module build, no module name");t.splice||(n=t,t=[]),s(w,e)||s(g,e)||(g[e]=[e,t,n])},n.amd={jQuery:!0}}(),n("../../assets/js/lib/almond",function(){}),n("models/formModel",[],function(){return Backbone.Model.extend({defaults:{objectType:"form",id:0,title:"unknown",created_at:"unknown"},url:function(){return ajaxurl+"?action=nf_forms&form_id="+this.get("id")},initialize:function(){this.set("id",Number(this.get("id"))),this.get("id")&&this.initShortcode(this.get("id")),this.get("title")&&this.set("title",this.get("title").replace(/<\/?[^>]+(>|$)/g,""))},initShortcode:function(e){var t="[ninja_form id="+e+"]";this.set("shortcode",t)},destroy:function(){var e=this;jQuery.ajax({type:"POST",url:ajaxurl+"?action=nf_forms&method_override=delete&form_id="+this.get("id"),success:function(t){var t=JSON.parse(t);e.collection.remove(e)}})}})}),n("models/formCollection",["models/formModel"],function(e){return Backbone.Collection.extend({model:e,comparator:"title",tmpNum:1,url:function(){return ajaxurl+"?action=nf_forms"},initialize:function(){this.newIDs=[],this.baseUrl=window.location.href.split("?")[0],this.listenTo(i.channel("dashboard"),"forms:delete",this.modalConfirm),this.listenTo(i.channel("dashboard"),"forms:duplicate",this.duplicate),this.modal=new jBox("Modal",{width:400,addClass:"dashboard-modal",overlay:!0,closeOnClick:"body"})},parse:function(e,t){return e.data},modalConfirm:function(e){var t,n,i,s,o,r,a,l=e.model.get("id"),d=e.model.get("title");n=document.createElement("div"),n.style.paddingRight="20px",n.style.paddingLeft="20px",n.style.paddingBottom="20px",i=document.createElement("p"),s=document.createElement("em"),o=document.createElement("div"),r=document.createElement("button"),a=document.createElement("div"),n.classList.add("message"),s.innerHTML=d,i.innerHTML+=nfi18n.deleteWarningA+" (<strong>"+d+"</strong>). "+nfi18n.deleteWarningB,i.appendChild(document.createElement("br")),i.appendChild(document.createElement("br"));var c=document.createElement("a");c.href=this.baseUrl+"?page=nf-import-export&exportFormId="+l,c.innerHTML='<i class="fa fa-download" style="padding:5px;"></i>'+nfi18n.deleteXForm,c.target="_blank",i.appendChild(c),i.appendChild(document.createElement("br"));var u=document.createElement("a");u.href=this.baseUrl+"?page=nf-processing&action=download_all_subs&form_id="+l+"&redirect="+encodeURIComponent(this.baseUrl.replace("admin.php","edit.php")+"?post_status=all&post_type=nf_sub&form_id="+l),u.target="_blank",u.innerHTML='<i class="fa fa-download" style="padding:5px;"></i>'+nfi18n.deleteXSubs,i.appendChild(u),i.appendChild(document.createElement("br")),n.appendChild(i);var h=document.createElement("label");h.for="confirmDeleteFormInput",h.innerHTML=nfi18n.deleteConfirmA+' <span style="color:red;">DELETE</span> '+nfi18n.deleteConfirmB;var m=document.createElement("input");m.type="text",m.id="confirmDeleteFormInput",m.style.marginTop="10px",m.style.width="100%",m.style.height="2.5em",m.style.fontSize="1em",n.appendChild(h),n.appendChild(document.createElement("br")),n.appendChild(m),n.appendChild(document.createElement("br")),n.appendChild(document.createElement("br")),r.innerHTML=nfi18n.delete,r.classList.add("confirm","nf-button","primary","pull-right"),a.innerHTML=nfi18n.cancel,a.classList.add("cancel","nf-button","secondary"),o.appendChild(a),o.appendChild(r),o.classList.add("buttons"),n.appendChild(o),t=document.createElement("div"),t.appendChild(n),this.modal.setContent(t.innerHTML),this.modal.setTitle(nfi18n.deleteTitle),this.modal.open();var p=this;this.modal.container[0].getElementsByClassName("cancel")[0].addEventListener("click",function(){p.modalClose()}),this.modal.container[0].getElementsByClassName("confirm")[0].addEventListener("click",function(t){t.preventDefault(),"DELETE"===document.getElementById("confirmDeleteFormInput").value?p.confirmDelete(e):p.modalClose()})},modalClose:function(){this.modal.close()},confirmDelete:function(e){jQuery(e.el).removeClass("show-actions"),jQuery(e.el).addClass("deleting"),jQuery(e.el).animate({opacity:0,"line-height":0,display:"none"},500),console.log(e),e.model.destroy(),this.modalClose()},duplicate:function(e){var t='<div class="message">Duplicating <em>'+e.model.get("title")+'</em>...<div class="nf-loading-spinner"></div></div>';this.modal.setContent(t),this.modal.setTitle("Please Wait"),this.modal.open();var n=this;jQuery.ajax({type:"POST",url:ajaxurl+"?action=nf_forms&clone_id="+e.model.get("id"),success:function(t){var t=JSON.parse(t),i=t.data.new_form_id,s=e.model.clone();s.set({id:i,title:s.get("title")+" - copy",created_at:new Date}),s.initShortcode(i),e.model.collection.add(s),n.modalClose()}})}})}),n("controllers/formsController",["models/formModel","models/formCollection"],function(e,t){return Marionette.Object.extend({initialize:function(){this.forms=new t,i.channel("dashboard").reply("get:forms",this.getForms,this),this.forms.fetch({success:function(e){i.channel("dashboard").trigger("fetch:forms",e)}})},getForms:function(){return this.forms}})}),n("models/oauthModel",[],function(){return Backbone.Model.extend({defaults:{connected:null,connect_url:""},url:function(){return ajaxurl+"?action=nf_oauth"},initialize:function(){},parse:function(e,t){return e.data}})}),n("controllers/oauthController",["models/oauthModel"],function(e){return Marionette.Object.extend({initialize:function(){this.oauth=new e,i.channel("dashboard").reply("get:oauth",this.getOAuth,this),i.channel("dashboard").reply("disconnect:oauth",this.disconnect,this),i.channel("dashboard").reply("oauth:learn-more",this.learnMoreModal,this),this.initOAuth()},getOAuth:function(){return this.oauth},initOAuth:function(){this.oauth.fetch({success:function(e){i.channel("dashboard").trigger("fetch:oauth")}})},disconnect:function(){var e=this;new jBox("Confirm",{width:750,content:nfi18n.oauthDisconnectContent,confirmButton:nfi18n.oauthDisconnectConfirm,cancelButton:nfi18n.oauthDisconnectCancel,closeOnConfirm:!0,confirm:function(){jQuery.ajax({type:"POST",url:ajaxurl+"?action=nf_oauth_disconnect",success:function(t){console.log(t),e.initOAuth()}})}}).open()},learnMoreModal:function(){new jBox("Modal",{width:500,content:nfi18n.oauthLearnMoreContent}).open()}})}),n("models/serviceModel",[],function(){return Backbone.Model.extend({defaults:{objectType:"service",name:"",slug:"",installPath:"",description:"",enabled:null,infoLink:null,serviceLink:null,is_installing:!1,classes:""},url:function(){return ajaxurl+"?action=nf_service_"+this.get("slug")},initialize:function(){this.get("slug")==serviceSuccess&&this.get("successMessage")&&new jBox("Modal",{width:300,addClass:"dashboard-modal",overlay:!0,closeOnClick:!0,content:this.get("successMessage"),title:this.get("successMessageTitle"),closeButton:"box"}).open();var e=this;i.channel("dashboard").reply("install:service:"+this.get("slug"),function(){if(e.get("serviceLink")&&e.get("serviceLink").href){var t=e.get("serviceLink").href;new jBox("Modal",{width:300,addClass:"dashboard-modal",overlay:!0,closeOnClick:"body",content:nfi18n.serviceRedirect}).open();var n=i.channel("dashboard").request("get:oauth");if(n.get("connected"))window.location=t;else{if(e.get("connect_url"))return window.location=e.get("connect_url")+"&redirect="+t;window.location=n.get("connect_url")+"&redirect="+t}}})},save:function(){var e=this;jQuery.ajax({type:"POST",url:this.url(),data:this.toJSON()}).done(function(t){var n=JSON.parse(t);void 0!==n.error&&(alert(nfi18n.serviceUpdateError+" "+n.error),e.set("enabled",!e.get("enabled"))),i.channel("dashboard").trigger("save:service-"+e.get("slug"))})}})}),n("models/serviceCollection",["models/serviceModel"],function(e){return Backbone.Collection.extend({model:e,comparator:"name",url:function(){return ajaxurl+"?action=nf_services"},initialize:function(){},parse:function(e,t){return e.data}})}),n("controllers/servicesController",["models/serviceCollection"],function(e){return Marionette.Object.extend({initialize:function(){this.services=new e,i.channel("dashboard").reply("install:service",this.installService,this),i.channel("dashboard").reply("get:services",this.getServices,this),this.fetchServices()},getServices:function(){return this.services},fetchServices:function(e){this.services.fetch({success:function(t){e&&e(t),i.channel("dashboard").trigger("fetch:services")}})},installService:function(e){var t=this;if(!(e instanceof Backbone.Model))var e=this.services.find(function(t){return e==t.get("slug")});e.set("is_installing",!0);var n=e.get("slug"),s=e.get("installPath");jQuery.post(ajaxurl,{action:"nf_services_install",plugin:n,install_path:s},function(e){t.fetchServices(function(){i.channel("dashboard").request("install:service:"+n)})})}})}),n("views/widgets/forms/formsFilter",[],function(){return Marionette.View.extend({template:"#tmpl-nf-widget-forms-filter",ui:{input:"input"},events:{"keyup @ui.input":"updateFilter"},initialize:function(){this.listenTo(i.channel("widget-forms"),"change:content",this.clearFilter)},updateFilter:function(){var e=this.getUI("input").val();i.channel("widget-forms").trigger("update:filter",e)},clearFilter:function(){this.getUI("input").val("")},updatePlaceholder:function(e){this.getUI("input").attr("placeholder",e)}})}),n("views/widgets/forms/formsTableRow",[],function(){return Marionette.View.extend({template:"#tmpl-nf-widget-forms-table-row",tagName:"tr",replaceElement:!0,ui:{delete:".delete",duplicate:".duplicate",edit:".nf-item-edit"},events:{"click @ui.delete":function(){i.channel("dashboard").trigger("forms:delete",this)},"click @ui.duplicate":function(){i.channel("dashboard").trigger("forms:duplicate",this)},"click @ui.edit":function(e){this.$el.toggleClass("show-actions").siblings().removeClass("show-actions")}},templateContext:function(){var e=this.model;return{created_at:moment(e.get("created_at")).format("MM/DD/YY h:mm A")}}})}),n("views/widgets/forms/formsTableEmpty",[],function(){return Marionette.View.extend({template:"#tmpl-nf-widget-forms-table-empty",tagName:"tr"})}),n("views/widgets/forms/formsTableBody",["views/widgets/forms/formsTableRow","views/widgets/forms/formsTableEmpty"],function(e,t){return Marionette.CollectionView.extend({childView:e,emptyView:t,className:"forms-collection",tagName:"tbody",initialize:function(){this.listenTo(i.channel("widget-forms"),"update:filter",this.updateFilter)},updateFilter:function(e){this.setFilter(function(t,n,i){return 0<=t.get("title").toLowerCase().indexOf(e.toLowerCase())})}})}),n("views/widgets/forms/formsTableLoading",[],function(){return Marionette.View.extend({template:"#tmpl-nf-widget-forms-table-loading",tagName:"tr"})}),n("views/widgets/forms/formsTable",["views/widgets/forms/formsTableBody","views/widgets/forms/formsTableLoading","models/formCollection"],function(e,t,n){return Marionette.View.extend({template:"#tmpl-nf-widget-forms-table",className:"nf-table-display",tagName:"table",initialize:function(){var t=this;this.listenTo(i.channel("dashboard"),"fetch:forms",function(n){t.showChildView("body",new e({collection:n}))})},regions:{body:{el:"tbody",replaceElement:!0}},ui:{sortable:".sortable",body:"tbody",action2:".action2",more:".more",less:".less"},onRender:function(){this.getUI("less").hide();var n=i.channel("dashboard").request("get:forms");void 0===n?this.showChildView("body",new t):this.showChildView("body",new e({collection:n})),this.maybeHideMoreButton()},events:{"click @ui.sortable":"sortFormsTable","click @ui.more":"showMore","click @ui.less":"showLess"},sortFormsTable:function(e){this.getUI("sortable").removeClass("sorted-asc"),this.getUI("sortable").removeClass("sorted-desc");var t=jQuery(e.target).data("sort"),n=jQuery(e.target).data("reverse")||0;n?(jQuery(e.target).addClass("sorted-desc"),jQuery(e.target).removeClass("sorted-asc")):(jQuery(e.target).addClass("sorted-asc"),jQuery(e.target).removeClass("sorted-desc"));var i=this.getChildView("body").collection;i.comparator=function(e,i){return name1=e.get(t).toLowerCase(),name2=i.get(t).toLowerCase(),name1<name2?ret=-1:name1>name2?ret=1:ret=0,n&&(ret=-ret),ret},i.sort(),n?(i.models.reverse(),jQuery(e.target).data("reverse",0)):jQuery(e.target).data("reverse",1)},showMore:function(){this.getUI("more").hide(),this.getUI("less").show(),this.getUI("body").addClass("more")},showLess:function(){this.getUI("less").hide(),this.getUI("more").show(),this.getUI("body").removeClass("more")},maybeHideMoreButton:function(){void 0!==this.collection&&10<this.collection.length||this.getUI("action2").hide()}})}),n("models/formTemplateModel",[],function(){return Backbone.Model.extend({defaults:{objectType:"template",id:"none",title:"unknown",type:""},initialize:function(){this.set("desc",this.get("template-desc")),this.set("modal-content",this.get("modal-content")),this.set("modal-title",this.get("modal-title"))}})}),n("models/formTemplateCollection",["models/formTemplateModel"],function(e){return Backbone.Collection.extend({model:e,tmpNum:1,url:function(){return ajaxurl+"?action=nf_new_form_templates"},parse:function(e,t){return e.data},initialize:function(){this.fetch({success:function(e){},error:function(e){}})}})}),n("views/widgets/forms/newFormTemplate",[],function(){return Marionette.View.extend({template:"#tmpl-nf-widget-forms-template",events:{click:"maybeOpenModal"},maybeOpenModal:function(e){if(e.preventDefault(),"ad"==this.model.get("type")){new jBox("Modal",{width:450,title:this.model.get("modal-title"),content:this.model.get("modal-content"),closeButton:"box",blockScroll:!0}).open()}else{var t={batch_type:"import_form_template",loadingText:"Importing...",extraData:{template:this.model.get("id")},onCompleteCallback:function(e){if(void 0===e.form_id)return!1;window.location.href=nfAdmin.builderURL+e.form_id}};new NinjaBatchProcessor(t)}}})}),n("views/widgets/forms/newFormGrid",["models/formTemplateCollection","views/widgets/forms/newFormTemplate"],function(e,t){return Marionette.CollectionView.extend({tagName:"div",className:"template-list",collection:new e,childView:t,initialize:function(){this.listenTo(i.channel("widget-forms"),"update:filter",this.updateFilter)},updateFilter:function(e){this.setFilter(function(t,n,i){return 0<=t.get("title").toLowerCase().indexOf(e.toLowerCase())})}})}),n("views/widgets/forms/forms",["views/widgets/forms/formsFilter","views/widgets/forms/formsTable","views/widgets/forms/newFormGrid"],function(e,t,n){return Marionette.View.extend({template:"#tmpl-nf-widget-forms",regions:{filter:".filter",content:".content"},ui:{add:".add",cancel:".cancel"},initialize:function(){},onRender:function(){this.getUI("cancel").hide(),this.showChildView("filter",new e),"#new-form"==window.location.hash?(this.getUI("add").hide(),this.getUI("cancel").show(),this.showChildView("content",new n)):this.showChildView("content",new t)},events:{"click @ui.add":"showNewFormGrid","click @ui.cancel":"showFormsTable"},showNewFormGrid:function(){window.location.hash="new-form",this.showChildView("content",new n),i.channel("widget-forms").trigger("change:content"),this.getUI("add").hide(),this.getUI("cancel").show(),this.getChildView("filter").updatePlaceholder("Search Templates")},showFormsTable:function(){window.location.hash="forms",this.showChildView("content",new t),i.channel("widget-forms").trigger("change:content"),this.getUI("cancel").hide(),this.getUI("add").show(),this.getChildView("filter").updatePlaceholder("Search Forms")}})}),n("views/sections/widgets.js",["views/widgets/forms/forms"],function(e){return Marionette.View.extend({template:"#tmpl-nf-widgets",regions:{forms:".widget-forms"},onRender:function(){this.showChildView("forms",new e)}})}),n("views/services/service",[],function(){return Marionette.View.extend({template:"#tmpl-nf-service",className:function(){return"nf-extend nf-box "+this.model.get("classes")},ui:{install:".js--install",learnMore:".js--learn-more",enabled:".nf-toggle.setting",toggleEnable:".nf-toggle + label"},events:{"click @ui.install":function(){i.channel("dashboard").request("install:service",this.model)},"click @ui.learnMore":function(){this.showLearnMore()},"click @ui.toggleEnable":function(){if(null==this.model.get("enabled")&&this.model.get("link"))return window.location=this.model.get("link"),this.render();this.model.set("enabled",!this.model.get("enabled")),this.model.save("enabled"),this.render()}},initialize:function(e){this.updateOAuth(),this.listenTo(this.model,"change",this.render),i.channel("dashboard").reply("more:service:"+this.model.get("slug"),this.showLearnMore,this),this.listenTo(i.channel("dashboard"),"fetch:oauth",this.updateOAuth),this.listenTo(i.channel("dashboard"),"save:service-"+this.model.get("slug"),this.render)},showLearnMore:function(){var e=new jBox("Modal",{width:750,title:this.model.get("learnMoreTitle")||this.model.get("name"),content:this.model.get("learnMore"),closeButton:"box",blockScroll:!0});e.open(),i.channel("dashboard").reply("service:"+this.model.get("slug")+":modal",function(){return e})},updateOAuth:function(){var e=i.channel("dashboard").request("get:oauth");this.connected=e.get("connected"),this.render()},templateContext:function(){return{is_connected:this.connected}}})}),n("views/services/services",["views/services/service","models/serviceCollection"],function(e,t){return Marionette.CollectionView.extend({collection:new t,className:"wrap apps-container",childView:e,initialize:function(){this.updateCollection(),this.listenTo(i.channel("dashboard"),"fetch:services",this.updateCollection)},updateCollection:function(){this.collection=i.channel("dashboard").request("get:services"),this.render()}})}),n("views/sections/services.js",["views/services/services"],function(e){return Marionette.View.extend({template:"#tmpl-nf-services",regions:{services:".services"},onRender:function(){this.showChildView("services",new e)}})}),n("views/sections/apps.js",[],function(){return Marionette.View.extend({template:"#tmpl-nf-apps"})}),n("views/sections/memberships.js",[],function(){return Marionette.View.extend({template:"#tmpl-nf-memberships"})}),n("views/oauth.js",["models/oauthModel"],function(e){return Marionette.View.extend({model:new e,template:"#tmpl-nf-notices-oauth",className:"nf-notices--oauth",ui:{disconnect:".js--disconnect"},initialize:function(e){this.listenTo(i.channel("dashboard"),"fetch:oauth",this.updateModel)},updateModel:function(){this.model=i.channel("dashboard").request("get:oauth"),this.render()},events:{"click @ui.disconnect":function(){i.channel("dashboard").request("disconnect:oauth")}}})}),n("models/promotionModel",[],function(){return Backbone.Model.extend({defaults:{id:"",content:""},initialize:function(){}})}),n("views/promotion.js",["models/promotionModel"],function(e){return Marionette.View.extend({model:null,template:"#tmpl-nf-promotion",className:"nf-promotion",initialize:function(){var t=nfPromotions[Math.floor(Math.random()*nfPromotions.length)];this.model=new e(t)}})}),n("views/sections/requiredUpdates.js",[],function(){return Marionette.View.extend({template:"#tmpl-nf-requiredUpdates",updates:[],currentUpdate:0,totalUpdates:-1,updatesRemaining:-1,ui:{requiredUpdates:".nf-required-update"},onRender:function(){this.getRequiredUpdates()},setButtonClickEvent:function(){var e=this;jQuery("#nf-required-updates-btn").off("click").on("click",function(t){t.preventDefault(),e.doRequiredUpdates(),jQuery(this).hide()})},getRequiredUpdates:function(){var e=this;jQuery.get(ajaxurl,{action:"nf_required_update"}).then(function(t){var n=JSON.parse(t);0===n.errors.length&&(e.totalUpdates=n.data.updates.length,e.updates=n.data.updates,0<e.updates.length?(e.requiredUpdates=e.updates.length,e.constructUpdateTable(),e.setButtonClickEvent()):window.location=window.location.origin+window.location.pathname+window.location.search)})},constructUpdateTable:function(){var e=this,t=document.getElementById("nf-upgrades-table"),n=t.getElementsByTagName("thead")[0],i=document.createElement("tr"),s=document.createElement("th");s.innerHTML="Update",s.classList.add("nf-update-name-cell");var o=document.createElement("th");o.innerHTML="Progress",o.classList.add("nf-update-progress-cell"),i.appendChild(s),i.appendChild(o),n.appendChild(i);var r=t.getElementsByTagName("tbody")[0];jQuery.each(this.updates,function(t,n){var i=document.createElement("tr"),s=document.createElement("td");s.innerHTML=n.nicename;var o=document.createElement("td"),a=document.createElement("div");a.id="update-progress-"+t;var l=e.createNewProgressBar(t);a.appendChild(l),o.appendChild(a),i.appendChild(s),i.appendChild(o),r.appendChild(i)}),document.getElementById("nf-required-updates-btn").style.display="block"},doRequiredUpdates:function(){window.location.hash="#requiredUpdates";var e=this;jQuery("#nf-required-updates-btn").addClass("disabled").attr("disabled","disabled"),jQuery.post(ajaxurl,{action:"nf_required_update",security:nfAdmin.updateNonce}).then(function(t){var n=JSON.parse(t);n.updatesRemaining>0?(e.updatesRemaining!==n.updatesRemaining&&n.currentStep===n.stepsTotal?(e.finishUpdate(e.currentUpdate),e.updatesRemaining=n.updatesRemaining):(e.showProgressBars(n),e.updatesRemaining=n.updatesRemaining),e.doRequiredUpdates()):(e.finishUpdate(e.currentUpdate),nfAdmin.requiredUpdates=0,jQuery("#nf-required-updates-btn").removeClass("disabled").removeAttr("disabled").val("Go To Dashboard").off("click").on("click",function(e){e.preventDefault(),window.location=window.location.origin+window.location.pathname+window.location.search}).show(),console.log("UPDATES DONE"))})},showProgressBars:function(e){var t=this.totalUpdates-e.updatesRemaining,n=e.currentStep,i=e.stepsTotal,s=document.getElementById("nf_progressBar_"+t);null==s&&(this.currentUpdate+=1,1===this.currentUpdate&&-1===this.totalUpdates&&(this.totalUpdates=t),s=this.createNewProgressBar(t)),this.incrementProgress(t,n,i)},createNewProgressBar:function(e){var t=document.createElement("div");t.id="nf_progressBar_"+e,t.classList.add("jBox-content"),t.style.display="none";var n=document.createElement("div");n.classList.add("nf-progress-bar");var i=document.createElement("div");return i.id="nf-progress-bar-slider-"+e,i.classList.add("nf-progress-bar-slider"),n.appendChild(i),t.appendChild(n),t},incrementProgress:function(e,t,n){document.getElementById("nf_progressBar_"+e).style.display="block";var i=document.getElementById("nf-progress-bar-slider-"+e),s=Number(t)/Number(n)*100;s>i.offsetWidth/i.parentElement.offsetWidth*100&&this.setProgress(e,s)},setProgress:function(e,t){document.getElementById("nf-progress-bar-slider-"+e).style.width=t+"%",100<=t&&this.finishUpdate(e)},finishUpdate:function(e){var t=document.getElementById("nf_progressBar_"+e),n=t.parentNode;n.removeChild(t);var i=document.createElement("span");i.classList.add("dashicons"),i.classList.add("dashicons-yes"),n.appendChild(i),this.currentUpdate=this.currentUpdate+1}})}),n("views/dashboardView",["views/sections/widgets.js","views/sections/services.js","views/sections/apps.js","views/sections/memberships.js","views/oauth.js","views/promotion.js","views/sections/requiredUpdates.js"],function(e,t,n,s,o,r,a){return Marionette.View.extend({template:"#tmpl-nf-dashboard",currentView:"widgets",regions:{notices:".notices",promotions:".promotions",content:".content"},events:{"click .widgets a":function(t){"1"!=nfAdmin.requiredUpdates&&(this.showChildView("content",new e),jQuery("."+this.currentView).find("a").removeClass("active"),t.target.classList.add("active"),this.currentView="widgets")},"click .services a":function(e){"1"!=nfAdmin.requiredUpdates&&(this.showChildView("content",new t),jQuery("."+this.currentView).find("a").removeClass("active"),e.target.classList.add("active"),this.currentView="services")},"click .apps a":function(e){"1"!=nfAdmin.requiredUpdates&&(this.showChildView("content",new n),jQuery("."+this.currentView).find("a").removeClass("active"),e.target.classList.add("active"),this.currentView="apps")},"click .memberships a":function(e){"1"!=nfAdmin.requiredUpdates&&(this.showChildView("content",new s),jQuery("."+this.currentView).find("a").removeClass("active"),e.target.classList.add("active"),this.currentView="memberships")}},initialize:function(){switch("1"===nfAdmin.requiredUpdates?window.location.hash="#requiredUpdates":"#requiredUpdates"===window.location.hash&&(window.location.hash=""),window.location.hash){case"#apps":this.currentView="apps";break;case"#services":this.currentView="services";break;case"#memberships":this.currentView="memberships";break;case"#requiredUpdates":this.currentView="requiredUpdates";break;case"#widgets":default:this.currentView="widgets"}i.channel("dashboard").reply("show:widgets",function(){"1"!=nfAdmin.requiredUpdates&&(this.showChildView("content",new e),jQuery("nav.sections a.active").removeClass("active"),jQuery("nav.sections .widgets a").addClass("active"),this.currentView="widgets")},this),i.channel("dashboard").reply("show:services",function(){"1"!=nfAdmin.requiredUpdates&&(this.showChildView("content",new t),jQuery("nav.sections a.active").removeClass("active"),jQuery("nav.sections .services a").addClass("active"),this.currentView="services")},this),i.channel("dashboard").reply("show:apps",function(){"1"!=nfAdmin.requiredUpdates&&(this.showChildView("content",new n),jQuery("nav.sections a.active").removeClass("active"),jQuery("nav.sections .apps a").addClass("active"),this.currentView="apps")},this)},onRender:function(){switch(useServices&&this.showChildView("notices",new o),useServices&&"1"!==nfAdmin.requiredUpdates&&this.showChildView("promotions",new r),"0"===nfAdmin.requiredUpdates&&"#requiredUpdates"===window.location.hash&&(window.location.hash=""),window.location.hash){case"#apps":var i=new n;break;case"#memberships":var i=new s;break;case"#services":var i=new t;break;case"#requiredUpdates":var i=new a;break;case"#widgets":default:var i=new e}if(this.showChildView("content",i),"1"==nfAdmin.showOptin){var l=new jBox("Modal",{closeOnEsc:!1,closeOnClick:!1,width:400}),d=document.createElement("div");d.id="optin-modal-title";var c=document.createElement("h2");c.innerHTML="Help make Ninja Forms better!",d.appendChild(c);var u=document.createElement("div");u.classList.add("message"),u.style.padding="0px 20px 20px 20px",u.innerHTML=nfi18n.optinContent;var h=document.createElement("p");h.style.paddingBottom="10px";var m=document.createElement("input");m.id="optin-send-email",m.setAttribute("type","checkbox"),m.style.margin="7px";var p=document.createElement("label");p.setAttribute("for","optin-send-email"),p.innerHTML=nfi18n.optinYesplease,h.appendChild(m),h.appendChild(p),u.appendChild(h),h=document.createElement("p"),h.id="optin-block",h.style.padding="0px 5px 20px 5px",h.style.display="none";var f=document.createElement("input");f.id="optin-email-address",f.setAttribute("type","text"),f.setAttribute("value",nfAdmin.currentUserEmail),f.style.width="100%",f.style.fontSize="16px",h.appendChild(f),u.appendChild(h);var w=document.createElement("span");w.id="optin-spinner",w.classList.add("spinner"),w.style.display="none",u.appendChild(w);var g=document.createElement("div");g.id="optin-buttons",g.classList.add("buttons");var v=document.createElement("div");v.id="optout",v.classList.add("nf-button","secondary"),v.innerHTML=nfi18n.optinSecondary,g.appendChild(v);var b=document.createElement("div");b.id="optin",b.classList.add("nf-button","primary","pull-right"),b.innerHTML=nfi18n.optinPrimary,g.appendChild(b),u.appendChild(g);var y=document.createElement("h2");y.innerHTML=nfi18n.optinAwesome;var C=document.createElement("div");C.id="optin-thankyou",C.classList.add("message"),C.style.padding="20px",C.innerHTML=nfi18n.optinThanks,l.setContent(document.createElement("div").appendChild(u).innerHTML),l.setTitle(document.createElement("div").appendChild(d).innerHTML),l.open(),jQuery("#optin-send-email").click(function(e){jQuery(this).is(":checked")?jQuery("#optin-block").show():jQuery("#optin-block").hide()}),jQuery("#optin").click(function(e){var t;jQuery("#optin-send-email").attr("checked")?(t=1,userEmail=jQuery("#optin-email-address").val()):(t=0,userEmail=""),jQuery("#optin").unbind("click"),jQuery("#optout").unbind("click");var n=jQuery("#optin").width();jQuery("#optin").html('<span class="dashicons dashicons-update dashicons-update-spin"></span>'),jQuery("#optin").width(n),jQuery.post(ajaxurl,{action:"nf_optin",ninja_forms_opt_in:1,send_email:t,user_email:userEmail},function(e){l.setTitle(document.createElement("div").appendChild(y).innerHTML),l.setContent(document.createElement("div").appendChild(C).innerHTML),setTimeout(function(){l.close()},2e3)})}),jQuery("#optout").click(function(e){jQuery("#optin").unbind("click"),jQuery("#optout").unbind("click");var t=jQuery("#optout").width();jQuery("#optout").html('<span class="dashicons dashicons-update dashicons-update-spin"></span>'),jQuery("#optout").width(t),jQuery.post(ajaxurl,{action:"nf_optin",ninja_forms_opt_in:0},function(e){l.close()})})}if(void 0!==nfAdmin.formTelemetry&&1==nfAdmin.formTelemetry){var j={action:"nf_form_telemetry",security:nfAdmin.ajaxNonce};jQuery.post(ajaxurl,j)}},templateContext:function(){var e=this;return{renderNav:function(){var t=document.createElement("div");return _.each(nfDashItems,function(n){var i=document.createElement("li"),s=document.createElement("a");s.href="#"+n.slug,
2
+ e.currentView==n.slug&&s.classList.add("active"),s.innerHTML=n.niceName,i.classList.add(n.slug),i.appendChild(s),t.appendChild(i)}),t.innerHTML}}}})});var i=Backbone.Radio;useServices||(nfDashItems=nfDashItems.filter(function(e){return"services"!==e.slug})),jQuery(document).ready(function(e){t(["controllers/formsController","controllers/oauthController","controllers/servicesController","views/dashboardView"],function(t,n,i,s){(new(Marionette.Application.extend({region:"#ninja-forms-dashboard",controllers:{},initialize:function(e){var t=this;Marionette.Renderer.render=function(e,n){var e=t.template(e);return e(n)}},onStart:function(){this.showView(new s),this.controllers.forms=new t,useServices&&(this.controllers.oauth=new n),useServices&&(this.controllers.services=new i)},template:function(t){return _.template(e(t).html(),{evaluate:/<#([\s\S]+?)#>/g,interpolate:/\{\{\{([\s\S]+?)\}\}\}/g,escape:/\{\{([^\}]+?)\}\}(?!\})/g,variable:"data"})}}))).start()})}),jQuery('a[href="admin.php?page=ninja-forms#new-form"]').on("click",function(e){e.preventDefault(),"1"!==nfAdmin.requiredUpdates&&(window.location.hash="new-form",i.channel("dashboard").request("show:widgets"),i.channel("widget-forms").request("show:newFormsGrid"))}),jQuery('a[href="admin.php?page=ninja-forms#apps"]').on("click",function(e){e.preventDefault(),"1"!==nfAdmin.requiredUpdates&&(window.location.hash="apps",i.channel("dashboard").request("show:apps"))}),jQuery('a[href="admin.php?page=ninja-forms"]').on("click",function(e){e.preventDefault(),"1"!==nfAdmin.requiredUpdates&&(window.location.hash="forms",i.channel("dashboard").request("show:widgets"),i.channel("widget-forms").request("show:formsTable"))}),jQuery(window).on("hashchange",function(){var e=window.location.hash.substr(1);i.channel("dashboard").request("show:"+e)}),n("main",function(){})}();
3
  //# sourceMappingURL=dashboard.min.js.map
assets/js/min/dashboard.min.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"names":[],"mappings":"","sources":["views/widgets/forms/newFormTemplate.js"],"sourcesContent":["!function(){var e,t,n;!function(i){function o(e,t){return y.call(e,t)}function s(e,t){var n,i,o,s,r,a,l,c,d,u,h,m=t&&t.split(\"/\"),f=v.map,p=f&&f[\"*\"]||{};if(e&&\".\"===e.charAt(0))if(t){for(e=e.split(\"/\"),r=e.length-1,v.nodeIdCompat&&j.test(e[r])&&(e[r]=e[r].replace(j,\"\")),e=m.slice(0,m.length-1).concat(e),d=0;d<e.length;d+=1)if(\".\"===(h=e[d]))e.splice(d,1),d-=1;else if(\"..\"===h){if(1===d&&(\"..\"===e[2]||\"..\"===e[0]))break;d>0&&(e.splice(d-1,2),d-=2)}e=e.join(\"/\")}else 0===e.indexOf(\"./\")&&(e=e.substring(2));if((m||p)&&f){for(n=e.split(\"/\"),d=n.length;d>0;d-=1){if(i=n.slice(0,d).join(\"/\"),m)for(u=m.length;u>0;u-=1)if((o=f[m.slice(0,u).join(\"/\")])&&(o=o[i])){s=o,a=d;break}if(s)break;!l&&p&&p[i]&&(l=p[i],c=d)}!s&&l&&(s=l,a=c),s&&(n.splice(0,a,s),e=n.join(\"/\"))}return e}function r(e,t){return function(){var n=C.call(arguments,0);return\"string\"!=typeof n[0]&&1===n.length&&n.push(null),m.apply(i,n.concat([e,t]))}}function a(e){return function(t){return s(t,e)}}function l(e){return function(t){w[e]=t}}function c(e){if(o(g,e)){var t=g[e];delete g[e],b[e]=!0,h.apply(i,t)}if(!o(w,e)&&!o(b,e))throw new Error(\"No \"+e);return w[e]}function d(e){var t,n=e?e.indexOf(\"!\"):-1;return n>-1&&(t=e.substring(0,n),e=e.substring(n+1,e.length)),[t,e]}function u(e){return function(){return v&&v.config&&v.config[e]||{}}}var h,m,f,p,w={},g={},v={},b={},y=Object.prototype.hasOwnProperty,C=[].slice,j=/\\.js$/;f=function(e,t){var n,i=d(e),o=i[0];return e=i[1],o&&(o=s(o,t),n=c(o)),o?e=n&&n.normalize?n.normalize(e,a(t)):s(e,t):(e=s(e,t),i=d(e),o=i[0],e=i[1],o&&(n=c(o))),{f:o?o+\"!\"+e:e,n:e,pr:o,p:n}},p={require:function(e){return r(e)},exports:function(e){var t=w[e];return void 0!==t?t:w[e]={}},module:function(e){return{id:e,uri:\"\",exports:w[e],config:u(e)}}},h=function(e,t,n,s){var a,d,u,h,m,v,y=[],C=typeof n;if(s=s||e,\"undefined\"===C||\"function\"===C){for(t=!t.length&&n.length?[\"require\",\"exports\",\"module\"]:t,m=0;m<t.length;m+=1)if(h=f(t[m],s),\"require\"===(d=h.f))y[m]=p.require(e);else if(\"exports\"===d)y[m]=p.exports(e),v=!0;else if(\"module\"===d)a=y[m]=p.module(e);else if(o(w,d)||o(g,d)||o(b,d))y[m]=c(d);else{if(!h.p)throw new Error(e+\" missing \"+d);h.p.load(h.n,r(s,!0),l(d),{}),y[m]=w[d]}u=n?n.apply(w[e],y):void 0,e&&(a&&a.exports!==i&&a.exports!==w[e]?w[e]=a.exports:u===i&&v||(w[e]=u))}else e&&(w[e]=n)},e=t=m=function(e,t,n,o,s){if(\"string\"==typeof e)return p[e]?p[e](t):c(f(e,t).f);if(!e.splice){if(v=e,v.deps&&m(v.deps,v.callback),!t)return;t.splice?(e=t,t=n,n=null):e=i}return t=t||function(){},\"function\"==typeof n&&(n=o,o=s),o?h(i,e,t,n):setTimeout(function(){h(i,e,t,n)},4),m},m.config=function(e){return m(e)},e._defined=w,n=function(e,t,n){if(\"string\"!=typeof e)throw new Error(\"See almond README: incorrect module build, no module name\");t.splice||(n=t,t=[]),o(w,e)||o(g,e)||(g[e]=[e,t,n])},n.amd={jQuery:!0}}(),n(\"../../assets/js/lib/almond\",function(){}),n(\"models/formModel\",[],function(){return Backbone.Model.extend({defaults:{objectType:\"form\",id:0,title:\"unknown\",created_at:\"unknown\"},url:function(){return ajaxurl+\"?action=nf_forms&form_id=\"+this.get(\"id\")},initialize:function(){this.set(\"id\",Number(this.get(\"id\"))),this.get(\"id\")&&this.initShortcode(this.get(\"id\")),this.get(\"title\")&&this.set(\"title\",this.get(\"title\").replace(/<\\/?[^>]+(>|$)/g,\"\"))},initShortcode:function(e){var t=\"[ninja_form id=\"+e+\"]\";this.set(\"shortcode\",t)},destroy:function(){var e=this;jQuery.ajax({type:\"POST\",url:ajaxurl+\"?action=nf_forms&method_override=delete&form_id=\"+this.get(\"id\"),success:function(t){var t=JSON.parse(t);e.collection.remove(e)}})}})}),n(\"models/formCollection\",[\"models/formModel\"],function(e){return Backbone.Collection.extend({model:e,comparator:\"title\",tmpNum:1,url:function(){return ajaxurl+\"?action=nf_forms\"},initialize:function(){this.newIDs=[],this.baseUrl=window.location.href.split(\"?\")[0],this.listenTo(i.channel(\"dashboard\"),\"forms:delete\",this.modalConfirm),this.listenTo(i.channel(\"dashboard\"),\"forms:duplicate\",this.duplicate),this.modal=new jBox(\"Modal\",{width:400,addClass:\"dashboard-modal\",overlay:!0,closeOnClick:\"body\"})},parse:function(e,t){return e.data},modalConfirm:function(e){var t,n,i,o,s,r,a,l=e.model.get(\"id\"),c=e.model.get(\"title\");n=document.createElement(\"div\"),n.style.paddingRight=\"20px\",n.style.paddingLeft=\"20px\",n.style.paddingBottom=\"20px\",i=document.createElement(\"p\"),o=document.createElement(\"em\"),s=document.createElement(\"div\"),r=document.createElement(\"button\"),a=document.createElement(\"div\"),n.classList.add(\"message\"),o.innerHTML=c,i.innerHTML+=nfi18n.deleteWarningA+\" (<strong>\"+c+\"</strong>). \"+nfi18n.deleteWarningB,i.appendChild(document.createElement(\"br\")),i.appendChild(document.createElement(\"br\"));var d=document.createElement(\"a\");d.href=this.baseUrl+\"?page=nf-import-export&exportFormId=\"+l,d.innerHTML='<i class=\"fa fa-download\" style=\"padding:5px;\"></i>'+nfi18n.deleteXForm,d.target=\"_blank\",i.appendChild(d),i.appendChild(document.createElement(\"br\"));var u=document.createElement(\"a\");u.href=this.baseUrl+\"?page=nf-processing&action=download_all_subs&form_id=\"+l+\"&redirect=\"+encodeURIComponent(this.baseUrl.replace(\"admin.php\",\"edit.php\")+\"?post_status=all&post_type=nf_sub&form_id=\"+l),u.target=\"_blank\",u.innerHTML='<i class=\"fa fa-download\" style=\"padding:5px;\"></i>'+nfi18n.deleteXSubs,i.appendChild(u),i.appendChild(document.createElement(\"br\")),n.appendChild(i);var h=document.createElement(\"label\");h.for=\"confirmDeleteFormInput\",h.innerHTML=nfi18n.deleteConfirmA+' <span style=\"color:red;\">DELETE</span> '+nfi18n.deleteConfirmB;var m=document.createElement(\"input\");m.type=\"text\",m.id=\"confirmDeleteFormInput\",m.style.marginTop=\"10px\",m.style.width=\"100%\",m.style.height=\"2.5em\",m.style.fontSize=\"1em\",n.appendChild(h),n.appendChild(document.createElement(\"br\")),n.appendChild(m),n.appendChild(document.createElement(\"br\")),n.appendChild(document.createElement(\"br\")),r.innerHTML=nfi18n.delete,r.classList.add(\"confirm\",\"nf-button\",\"primary\",\"pull-right\"),a.innerHTML=nfi18n.cancel,a.classList.add(\"cancel\",\"nf-button\",\"secondary\"),s.appendChild(a),s.appendChild(r),s.classList.add(\"buttons\"),n.appendChild(s),t=document.createElement(\"div\"),t.appendChild(n),this.modal.setContent(t.innerHTML),this.modal.setTitle(nfi18n.deleteTitle),this.modal.open();var f=this;this.modal.container[0].getElementsByClassName(\"cancel\")[0].addEventListener(\"click\",function(){f.modalClose()}),this.modal.container[0].getElementsByClassName(\"confirm\")[0].addEventListener(\"click\",function(t){t.preventDefault(),\"DELETE\"===document.getElementById(\"confirmDeleteFormInput\").value?f.confirmDelete(e):f.modalClose()})},modalClose:function(){this.modal.close()},confirmDelete:function(e){jQuery(e.el).removeClass(\"show-actions\"),jQuery(e.el).addClass(\"deleting\"),jQuery(e.el).animate({opacity:0,\"line-height\":0,display:\"none\"},500),console.log(e),e.model.destroy(),this.modalClose()},duplicate:function(e){var t='<div class=\"message\">Duplicating <em>'+e.model.get(\"title\")+'</em>...<div class=\"nf-loading-spinner\"></div></div>';this.modal.setContent(t),this.modal.setTitle(\"Please Wait\"),this.modal.open();var n=this;jQuery.ajax({type:\"POST\",url:ajaxurl+\"?action=nf_forms&clone_id=\"+e.model.get(\"id\"),success:function(t){var t=JSON.parse(t),i=t.data.new_form_id,o=e.model.clone();o.set({id:i,title:o.get(\"title\")+\" - copy\",created_at:new Date}),o.initShortcode(i),e.model.collection.add(o),n.modalClose()}})}})}),n(\"controllers/formsController\",[\"models/formModel\",\"models/formCollection\"],function(e,t){return Marionette.Object.extend({initialize:function(){this.forms=new t,i.channel(\"dashboard\").reply(\"get:forms\",this.getForms,this),this.forms.fetch({success:function(e){i.channel(\"dashboard\").trigger(\"fetch:forms\",e)}})},getForms:function(){return this.forms}})}),n(\"models/oauthModel\",[],function(){return Backbone.Model.extend({defaults:{connected:null,connect_url:\"\"},url:function(){return ajaxurl+\"?action=nf_oauth\"},initialize:function(){},parse:function(e,t){return e.data}})}),n(\"controllers/oauthController\",[\"models/oauthModel\"],function(e){return Marionette.Object.extend({initialize:function(){this.oauth=new e,i.channel(\"dashboard\").reply(\"get:oauth\",this.getOAuth,this),i.channel(\"dashboard\").reply(\"disconnect:oauth\",this.disconnect,this),i.channel(\"dashboard\").reply(\"oauth:learn-more\",this.learnMoreModal,this),this.initOAuth()},getOAuth:function(){return this.oauth},initOAuth:function(){this.oauth.fetch({success:function(e){i.channel(\"dashboard\").trigger(\"fetch:oauth\")}})},disconnect:function(){var e=this;new jBox(\"Confirm\",{width:750,content:nfi18n.oauthDisconnectContent,confirmButton:nfi18n.oauthDisconnectConfirm,cancelButton:nfi18n.oauthDisconnectCancel,closeOnConfirm:!0,confirm:function(){jQuery.ajax({type:\"POST\",url:ajaxurl+\"?action=nf_oauth_disconnect\",success:function(t){console.log(t),e.initOAuth()}})}}).open()},learnMoreModal:function(){new jBox(\"Modal\",{width:500,content:nfi18n.oauthLearnMoreContent}).open()}})}),n(\"models/serviceModel\",[],function(){return Backbone.Model.extend({defaults:{objectType:\"service\",name:\"\",slug:\"\",installPath:\"\",description:\"\",enabled:null,infoLink:null,serviceLink:null,is_installing:!1,classes:\"\"},url:function(){return ajaxurl+\"?action=nf_service_\"+this.get(\"slug\")},initialize:function(){this.get(\"slug\")==serviceSuccess&&this.get(\"successMessage\")&&new jBox(\"Modal\",{width:300,addClass:\"dashboard-modal\",overlay:!0,closeOnClick:!0,content:this.get(\"successMessage\"),title:this.get(\"successMessageTitle\"),closeButton:\"box\"}).open();var e=this;i.channel(\"dashboard\").reply(\"install:service:\"+this.get(\"slug\"),function(){if(e.get(\"serviceLink\")&&e.get(\"serviceLink\").href){var t=e.get(\"serviceLink\").href;new jBox(\"Modal\",{width:300,addClass:\"dashboard-modal\",overlay:!0,closeOnClick:\"body\",content:nfi18n.serviceRedirect}).open();var n=i.channel(\"dashboard\").request(\"get:oauth\");if(n.get(\"connected\"))window.location=t;else{if(e.get(\"connect_url\"))return window.location=e.get(\"connect_url\")+\"&redirect=\"+t;window.location=n.get(\"connect_url\")+\"&redirect=\"+t}}})},save:function(){var e=this;jQuery.ajax({type:\"POST\",url:this.url(),data:this.toJSON()}).done(function(t){var n=JSON.parse(t);void 0!==n.error&&(alert(nfi18n.serviceUpdateError+\" \"+n.error),e.set(\"enabled\",!e.get(\"enabled\"))),i.channel(\"dashboard\").trigger(\"save:service-\"+e.get(\"slug\"))})}})}),n(\"models/serviceCollection\",[\"models/serviceModel\"],function(e){return Backbone.Collection.extend({model:e,comparator:\"name\",url:function(){return ajaxurl+\"?action=nf_services\"},initialize:function(){},parse:function(e,t){return e.data}})}),n(\"controllers/servicesController\",[\"models/serviceCollection\"],function(e){return Marionette.Object.extend({initialize:function(){this.services=new e,i.channel(\"dashboard\").reply(\"install:service\",this.installService,this),i.channel(\"dashboard\").reply(\"get:services\",this.getServices,this),this.fetchServices()},getServices:function(){return this.services},fetchServices:function(e){this.services.fetch({success:function(t){e&&e(t),i.channel(\"dashboard\").trigger(\"fetch:services\")}})},installService:function(e){var t=this;if(!(e instanceof Backbone.Model))var e=this.services.find(function(t){return e==t.get(\"slug\")});e.set(\"is_installing\",!0);var n=e.get(\"slug\"),o=e.get(\"installPath\");jQuery.post(ajaxurl,{action:\"nf_services_install\",plugin:n,install_path:o},function(e){t.fetchServices(function(){i.channel(\"dashboard\").request(\"install:service:\"+n)})})}})}),n(\"views/widgets/forms/formsFilter\",[],function(){return Marionette.View.extend({template:\"#tmpl-nf-widget-forms-filter\",ui:{input:\"input\"},events:{\"keyup @ui.input\":\"updateFilter\"},initialize:function(){this.listenTo(i.channel(\"widget-forms\"),\"change:content\",this.clearFilter)},updateFilter:function(){var e=this.getUI(\"input\").val();i.channel(\"widget-forms\").trigger(\"update:filter\",e)},clearFilter:function(){this.getUI(\"input\").val(\"\")},updatePlaceholder:function(e){this.getUI(\"input\").attr(\"placeholder\",e)}})}),n(\"views/widgets/forms/formsTableRow\",[],function(){return Marionette.View.extend({template:\"#tmpl-nf-widget-forms-table-row\",tagName:\"tr\",replaceElement:!0,ui:{delete:\".delete\",duplicate:\".duplicate\",edit:\".nf-item-edit\"},events:{\"click @ui.delete\":function(){i.channel(\"dashboard\").trigger(\"forms:delete\",this)},\"click @ui.duplicate\":function(){i.channel(\"dashboard\").trigger(\"forms:duplicate\",this)},\"click @ui.edit\":function(e){this.$el.toggleClass(\"show-actions\").siblings().removeClass(\"show-actions\")}},templateContext:function(){var e=this.model;return{created_at:moment(e.get(\"created_at\")).format(\"MM/DD/YY h:mm A\")}}})}),n(\"views/widgets/forms/formsTableEmpty\",[],function(){return Marionette.View.extend({template:\"#tmpl-nf-widget-forms-table-empty\",tagName:\"tr\"})}),n(\"views/widgets/forms/formsTableBody\",[\"views/widgets/forms/formsTableRow\",\"views/widgets/forms/formsTableEmpty\"],function(e,t){return Marionette.CollectionView.extend({childView:e,emptyView:t,className:\"forms-collection\",tagName:\"tbody\",initialize:function(){this.listenTo(i.channel(\"widget-forms\"),\"update:filter\",this.updateFilter)},updateFilter:function(e){this.setFilter(function(t,n,i){return 0<=t.get(\"title\").toLowerCase().indexOf(e.toLowerCase())})}})}),n(\"views/widgets/forms/formsTableLoading\",[],function(){return Marionette.View.extend({template:\"#tmpl-nf-widget-forms-table-loading\",tagName:\"tr\"})}),n(\"views/widgets/forms/formsTable\",[\"views/widgets/forms/formsTableBody\",\"views/widgets/forms/formsTableLoading\",\"models/formCollection\"],function(e,t,n){return Marionette.View.extend({template:\"#tmpl-nf-widget-forms-table\",className:\"nf-table-display\",tagName:\"table\",initialize:function(){var t=this;this.listenTo(i.channel(\"dashboard\"),\"fetch:forms\",function(n){t.showChildView(\"body\",new e({collection:n}))})},regions:{body:{el:\"tbody\",replaceElement:!0}},ui:{sortable:\".sortable\",body:\"tbody\",action2:\".action2\",more:\".more\",less:\".less\"},onRender:function(){this.getUI(\"less\").hide();var n=i.channel(\"dashboard\").request(\"get:forms\");void 0===n?this.showChildView(\"body\",new t):this.showChildView(\"body\",new e({collection:n})),this.maybeHideMoreButton()},events:{\"click @ui.sortable\":\"sortFormsTable\",\"click @ui.more\":\"showMore\",\"click @ui.less\":\"showLess\"},sortFormsTable:function(e){this.getUI(\"sortable\").removeClass(\"sorted-asc\"),this.getUI(\"sortable\").removeClass(\"sorted-desc\");var t=jQuery(e.target).data(\"sort\"),n=jQuery(e.target).data(\"reverse\")||0;n?(jQuery(e.target).addClass(\"sorted-desc\"),jQuery(e.target).removeClass(\"sorted-asc\")):(jQuery(e.target).addClass(\"sorted-asc\"),jQuery(e.target).removeClass(\"sorted-desc\"));var i=this.getChildView(\"body\").collection;i.comparator=function(e,i){return name1=e.get(t).toLowerCase(),name2=i.get(t).toLowerCase(),name1<name2?ret=-1:name1>name2?ret=1:ret=0,n&&(ret=-ret),ret},i.sort(),n?(i.models.reverse(),jQuery(e.target).data(\"reverse\",0)):jQuery(e.target).data(\"reverse\",1)},showMore:function(){this.getUI(\"more\").hide(),this.getUI(\"less\").show(),this.getUI(\"body\").addClass(\"more\")},showLess:function(){this.getUI(\"less\").hide(),this.getUI(\"more\").show(),this.getUI(\"body\").removeClass(\"more\")},maybeHideMoreButton:function(){void 0!==this.collection&&10<this.collection.length||this.getUI(\"action2\").hide()}})}),n(\"models/formTemplateModel\",[],function(){return Backbone.Model.extend({defaults:{objectType:\"template\",id:\"none\",title:\"unknown\",type:\"\"},initialize:function(){this.set(\"desc\",this.get(\"template-desc\")),this.set(\"modal-content\",this.get(\"modal-content\")),this.set(\"modal-title\",this.get(\"modal-title\"))}})}),n(\"models/formTemplateCollection\",[\"models/formTemplateModel\"],function(e){return Backbone.Collection.extend({model:e,tmpNum:1,url:function(){return ajaxurl+\"?action=nf_new_form_templates\"},parse:function(e,t){return e.data},initialize:function(){this.fetch({success:function(e){},error:function(e){}})}})}),n(\"views/widgets/forms/newFormTemplate\",[],function(){return Marionette.View.extend({template:\"#tmpl-nf-widget-forms-template\",events:{click:\"maybeOpenModal\"},maybeOpenModal:function(e){if(\"ad\"!=this.model.get(\"type\"))return!0;e.preventDefault(),new jBox(\"Modal\",{width:450,title:this.model.get(\"modal-title\"),content:this.model.get(\"modal-content\"),closeButton:\"box\",blockScroll:!0}).open()}})}),n(\"views/widgets/forms/newFormGrid\",[\"models/formTemplateCollection\",\"views/widgets/forms/newFormTemplate\"],function(e,t){return Marionette.CollectionView.extend({tagName:\"div\",className:\"template-list\",collection:new e,childView:t,initialize:function(){this.listenTo(i.channel(\"widget-forms\"),\"update:filter\",this.updateFilter)},updateFilter:function(e){this.setFilter(function(t,n,i){return 0<=t.get(\"title\").toLowerCase().indexOf(e.toLowerCase())})}})}),n(\"views/widgets/forms/forms\",[\"views/widgets/forms/formsFilter\",\"views/widgets/forms/formsTable\",\"views/widgets/forms/newFormGrid\"],function(e,t,n){return Marionette.View.extend({template:\"#tmpl-nf-widget-forms\",regions:{filter:\".filter\",content:\".content\"},ui:{add:\".add\",cancel:\".cancel\"},initialize:function(){i.channel(\"widget-forms\").reply(\"show:newFormsGrid\",this.showNewFormGrid,this),i.channel(\"widget-forms\").reply(\"show:formsTable\",this.showFormsTable,this)},onRender:function(){this.getUI(\"cancel\").hide(),this.showChildView(\"filter\",new e),\"#new-form\"==window.location.hash?(this.getUI(\"add\").hide(),this.getUI(\"cancel\").show(),this.showChildView(\"content\",new n)):this.showChildView(\"content\",new t)},events:{\"click @ui.add\":\"showNewFormGrid\",\"click @ui.cancel\":\"showFormsTable\"},showNewFormGrid:function(){window.location.hash=\"new-form\",this.showChildView(\"content\",new n),i.channel(\"widget-forms\").trigger(\"change:content\"),this.getUI(\"add\").hide(),this.getUI(\"cancel\").show(),this.getChildView(\"filter\").updatePlaceholder(\"Search Templates\")},showFormsTable:function(){console.log(this),window.location.hash=\"forms\",this.showChildView(\"content\",new t),i.channel(\"widget-forms\").trigger(\"change:content\"),this.getUI(\"cancel\").hide(),this.getUI(\"add\").show(),this.getChildView(\"filter\").updatePlaceholder(\"Search Forms\")}})}),n(\"views/sections/widgets.js\",[\"views/widgets/forms/forms\"],function(e){return Marionette.View.extend({template:\"#tmpl-nf-widgets\",regions:{forms:\".widget-forms\"},onRender:function(){this.showChildView(\"forms\",new e)}})}),n(\"views/services/service\",[],function(){return Marionette.View.extend({template:\"#tmpl-nf-service\",className:function(){return\"nf-extend nf-box \"+this.model.get(\"classes\")},ui:{install:\".js--install\",learnMore:\".js--learn-more\",enabled:\".nf-toggle.setting\",toggleEnable:\".nf-toggle + label\"},events:{\"click @ui.install\":function(){i.channel(\"dashboard\").request(\"install:service\",this.model)},\"click @ui.learnMore\":function(){this.showLearnMore()},\"click @ui.toggleEnable\":function(){if(null==this.model.get(\"enabled\")&&this.model.get(\"link\"))return window.location=this.model.get(\"link\"),this.render();this.model.set(\"enabled\",!this.model.get(\"enabled\")),this.model.save(\"enabled\"),this.render()}},initialize:function(e){this.updateOAuth(),this.listenTo(this.model,\"change\",this.render),i.channel(\"dashboard\").reply(\"more:service:\"+this.model.get(\"slug\"),this.showLearnMore,this),this.listenTo(i.channel(\"dashboard\"),\"fetch:oauth\",this.updateOAuth),this.listenTo(i.channel(\"dashboard\"),\"save:service-\"+this.model.get(\"slug\"),this.render)},showLearnMore:function(){var e=new jBox(\"Modal\",{width:750,title:this.model.get(\"learnMoreTitle\")||this.model.get(\"name\"),content:this.model.get(\"learnMore\"),closeButton:\"box\",blockScroll:!0});e.open(),i.channel(\"dashboard\").reply(\"service:\"+this.model.get(\"slug\")+\":modal\",function(){return e})},updateOAuth:function(){var e=i.channel(\"dashboard\").request(\"get:oauth\");this.connected=e.get(\"connected\"),this.render()},templateContext:function(){return{is_connected:this.connected}}})}),n(\"views/services/services\",[\"views/services/service\",\"models/serviceCollection\"],function(e,t){return Marionette.CollectionView.extend({collection:new t,className:\"wrap apps-container\",childView:e,initialize:function(){this.updateCollection(),this.listenTo(i.channel(\"dashboard\"),\"fetch:services\",this.updateCollection)},updateCollection:function(){this.collection=i.channel(\"dashboard\").request(\"get:services\"),this.render()}})}),n(\"views/sections/services.js\",[\"views/services/services\"],function(e){return Marionette.View.extend({template:\"#tmpl-nf-services\",regions:{services:\".services\"},onRender:function(){this.showChildView(\"services\",new e)}})}),n(\"views/sections/apps.js\",[],function(){return Marionette.View.extend({template:\"#tmpl-nf-apps\"})}),n(\"views/sections/memberships.js\",[],function(){return Marionette.View.extend({template:\"#tmpl-nf-memberships\"})}),n(\"views/oauth.js\",[\"models/oauthModel\"],function(e){return Marionette.View.extend({model:new e,template:\"#tmpl-nf-notices-oauth\",className:\"nf-notices--oauth\",ui:{disconnect:\".js--disconnect\"},initialize:function(e){this.listenTo(i.channel(\"dashboard\"),\"fetch:oauth\",this.updateModel)},updateModel:function(){this.model=i.channel(\"dashboard\").request(\"get:oauth\"),this.render()},events:{\"click @ui.disconnect\":function(){i.channel(\"dashboard\").request(\"disconnect:oauth\")}}})}),n(\"models/promotionModel\",[],function(){return Backbone.Model.extend({defaults:{id:\"\",content:\"\"},initialize:function(){}})}),n(\"views/promotion.js\",[\"models/promotionModel\"],function(e){return Marionette.View.extend({model:null,template:\"#tmpl-nf-promotion\",className:\"nf-promotion\",initialize:function(){var t=nfPromotions[Math.floor(Math.random()*nfPromotions.length)];this.model=new e(t)}})}),n(\"views/dashboardView\",[\"views/sections/widgets.js\",\"views/sections/services.js\",\"views/sections/apps.js\",\"views/sections/memberships.js\",\"views/oauth.js\",\"views/promotion.js\"],function(e,t,n,o,s,r){return Marionette.View.extend({template:\"#tmpl-nf-dashboard\",currentView:\"widgets\",regions:{notices:\".notices\",promotions:\".promotions\",content:\".content\"},events:{\"click .widgets a\":function(t){this.showChildView(\"content\",new e),jQuery(\".\"+this.currentView).find(\"a\").removeClass(\"active\"),t.target.classList.add(\"active\"),this.currentView=\"widgets\"},\"click .services a\":function(e){this.showChildView(\"content\",new t),jQuery(\".\"+this.currentView).find(\"a\").removeClass(\"active\"),e.target.classList.add(\"active\"),this.currentView=\"services\"},\"click .apps a\":function(e){this.showChildView(\"content\",new n),jQuery(\".\"+this.currentView).find(\"a\").removeClass(\"active\"),e.target.classList.add(\"active\"),this.currentView=\"apps\"},\"click .memberships a\":function(e){this.showChildView(\"content\",new o),jQuery(\".\"+this.currentView).find(\"a\").removeClass(\"active\"),e.target.classList.add(\"active\"),this.currentView=\"memberships\"}},initialize:function(){switch(window.location.hash){case\"#apps\":this.currentView=\"apps\";break;case\"#services\":this.currentView=\"services\";break;case\"#memberships\":this.currentView=\"memberships\";break;case\"#widgets\":default:this.currentView=\"widgets\"}i.channel(\"dashboard\").reply(\"show:widgets\",function(){this.showChildView(\"content\",new e),jQuery(\"nav.sections a.active\").removeClass(\"active\"),jQuery(\"nav.sections .widgets a\").addClass(\"active\"),this.currentView=\"widgets\"},this),i.channel(\"dashboard\").reply(\"show:services\",function(){this.showChildView(\"content\",new t),jQuery(\"nav.sections a.active\").removeClass(\"active\"),jQuery(\"nav.sections .services a\").addClass(\"active\"),this.currentView=\"services\"},this),i.channel(\"dashboard\").reply(\"show:apps\",function(){this.showChildView(\"content\",new n),jQuery(\"nav.sections a.active\").removeClass(\"active\"),jQuery(\"nav.sections .apps a\").addClass(\"active\"),this.currentView=\"apps\"},this)},onRender:function(){switch(useServices&&this.showChildView(\"notices\",new s),useServices&&this.showChildView(\"promotions\",new r),window.location.hash){case\"#apps\":var i=new n;break;case\"#memberships\":var i=new o;break;case\"#services\":var i=new t;break;case\"#widgets\":default:var i=new e}if(this.showChildView(\"content\",i),\"1\"==nfAdmin.showOptin){var a=new jBox(\"Modal\",{closeOnEsc:!1,closeOnClick:!1,width:400}),l=document.createElement(\"div\");l.id=\"optin-modal-title\";var c=document.createElement(\"h2\");c.innerHTML=\"Help make Ninja Forms better!\",l.appendChild(c);var d=document.createElement(\"div\");d.classList.add(\"message\"),d.style.padding=\"0px 20px 20px 20px\",d.innerHTML=nfi18n.optinContent;var u=document.createElement(\"p\");u.style.paddingBottom=\"10px\";var h=document.createElement(\"input\");h.id=\"optin-send-email\",h.setAttribute(\"type\",\"checkbox\"),h.style.margin=\"7px\";var m=document.createElement(\"label\");m.setAttribute(\"for\",\"optin-send-email\"),m.innerHTML=nfi18n.optinYesplease,u.appendChild(h),u.appendChild(m),d.appendChild(u),u=document.createElement(\"p\"),u.id=\"optin-block\",u.style.padding=\"0px 5px 20px 5px\",u.style.display=\"none\";var f=document.createElement(\"input\");f.id=\"optin-email-address\",f.setAttribute(\"type\",\"text\"),f.setAttribute(\"value\",nfAdmin.currentUserEmail),f.style.width=\"100%\",f.style.fontSize=\"16px\",u.appendChild(f),d.appendChild(u);var p=document.createElement(\"span\");p.id=\"optin-spinner\",p.classList.add(\"spinner\"),p.style.display=\"none\",d.appendChild(p);var w=document.createElement(\"div\");w.id=\"optin-buttons\",w.classList.add(\"buttons\");var g=document.createElement(\"div\");g.id=\"optout\",g.classList.add(\"nf-button\",\"secondary\"),g.innerHTML=nfi18n.optinSecondary,w.appendChild(g);var v=document.createElement(\"div\");v.id=\"optin\",v.classList.add(\"nf-button\",\"primary\",\"pull-right\"),v.innerHTML=nfi18n.optinPrimary,w.appendChild(v),d.appendChild(w);var b=document.createElement(\"h2\");b.innerHTML=nfi18n.optinAwesome;var y=document.createElement(\"div\");y.id=\"optin-thankyou\",y.classList.add(\"message\"),y.style.padding=\"20px\",y.innerHTML=nfi18n.optinThanks,a.setContent(document.createElement(\"div\").appendChild(d).innerHTML),a.setTitle(document.createElement(\"div\").appendChild(l).innerHTML),a.open(),jQuery(\"#optin-send-email\").click(function(e){jQuery(this).is(\":checked\")?jQuery(\"#optin-block\").show():jQuery(\"#optin-block\").hide()}),jQuery(\"#optin\").click(function(e){var t;jQuery(\"#optin-send-email\").attr(\"checked\")?(t=1,userEmail=jQuery(\"#optin-email-address\").val()):(t=0,userEmail=\"\"),jQuery(\"#optin\").unbind(\"click\"),jQuery(\"#optout\").unbind(\"click\");var n=jQuery(\"#optin\").width();jQuery(\"#optin\").html('<span class=\"dashicons dashicons-update dashicons-update-spin\"></span>'),jQuery(\"#optin\").width(n),jQuery.post(ajaxurl,{action:\"nf_optin\",ninja_forms_opt_in:1,send_email:t,user_email:userEmail},function(e){a.setTitle(document.createElement(\"div\").appendChild(b).innerHTML),a.setContent(document.createElement(\"div\").appendChild(y).innerHTML),setTimeout(function(){a.close()},2e3)})}),jQuery(\"#optout\").click(function(e){jQuery(\"#optin\").unbind(\"click\"),jQuery(\"#optout\").unbind(\"click\");var t=jQuery(\"#optout\").width();jQuery(\"#optout\").html('<span class=\"dashicons dashicons-update dashicons-update-spin\"></span>'),jQuery(\"#optout\").width(t),jQuery.post(ajaxurl,{action:\"nf_optin\",ninja_forms_opt_in:0},function(e){a.close()})})}else if(\"1\"==nfAdmin.doingCleanup)var C=this,j={width:450,closeOnClick:!1,closeOnEsc:!1,content:nfi18n.cleanupContent,useProgressBar:!0,loadingText:nfi18n.cleanupLoading,btnSecondary:{text:nfi18n.cleanupSecondary,callback:function(){x.toggleModal(!1)}},btnPrimary:{text:nfi18n.cleanupPrimary,callback:function(){jQuery(window).bind(\"beforeunload\",function(){return\"Are you sure? Leaving before the process completes could cause damage to your data.\"}),x.maybeShowActions(!1),x.maybeShowProgress(!0),C.cleanupProcess(C,-1,x)}}},x=new NinjaModal(j);if(void 0!==nfAdmin.formTelemetry&&1==nfAdmin.formTelemetry){var M={action:\"nf_form_telemetry\",security:nfAdmin.ajaxNonce};jQuery.post(ajaxurl,M)}},templateContext:function(){var e=this;return{renderNav:function(){var t=document.createElement(\"div\");return _.each(nfDashItems,function(n){var i=document.createElement(\"li\"),o=document.createElement(\"a\");o.href=\"#\"+n.slug,e.currentView==n.slug&&o.classList.add(\"active\"),o.innerHTML=n.niceName,i.classList.add(n.slug),i.appendChild(o),t.appendChild(i)}),t.innerHTML}}},cleanupProcess:function(e,t,n){var i={action:\"nf_batch_process\",batch_type:\"data_cleanup\",security:nfAdmin.batchNonce};jQuery.post(ajaxurl,i,function(i){if(i=JSON.parse(i),i.batch_complete)return n.setProgress(100),jQuery(window).unbind(\"beforeunload\"),n.toggleModal(!1),!1;-1==t&&(t=void 0!==i.step_total?i.step_total:i.step_remaining);var o=t-i.step_remaining,s=Math.round(o/t*100);n.incrementProgress(s),e.cleanupProcess(e,t,n)})}})});var i=Backbone.Radio;useServices||(nfDashItems=nfDashItems.filter(function(e){return\"services\"!==e.slug})),jQuery(document).ready(function(e){t([\"controllers/formsController\",\"controllers/oauthController\",\"controllers/servicesController\",\"views/dashboardView\"],function(t,n,i,o){(new(Marionette.Application.extend({region:\"#ninja-forms-dashboard\",controllers:{},initialize:function(e){var t=this;Marionette.Renderer.render=function(e,n){var e=t.template(e);return e(n)}},onStart:function(){this.showView(new o),this.controllers.forms=new t,useServices&&(this.controllers.oauth=new n),useServices&&(this.controllers.services=new i)},template:function(t){return _.template(e(t).html(),{evaluate:/<#([\\s\\S]+?)#>/g,interpolate:/\\{\\{\\{([\\s\\S]+?)\\}\\}\\}/g,escape:/\\{\\{([^\\}]+?)\\}\\}(?!\\})/g,variable:\"data\"})}}))).start()})}),jQuery('a[href=\"admin.php?page=ninja-forms#new-form\"]').on(\"click\",function(e){e.preventDefault(),window.location.hash=\"new-form\",i.channel(\"dashboard\").request(\"show:widgets\"),i.channel(\"widget-forms\").request(\"show:newFormsGrid\")}),jQuery('a[href=\"admin.php?page=ninja-forms#apps\"]').on(\"click\",function(e){e.preventDefault(),window.location.hash=\"apps\",i.channel(\"dashboard\").request(\"show:apps\")}),jQuery('a[href=\"admin.php?page=ninja-forms\"]').on(\"click\",function(e){e.preventDefault(),window.location.hash=\"forms\",i.channel(\"dashboard\").request(\"show:widgets\"),i.channel(\"widget-forms\").request(\"show:formsTable\")}),jQuery(window).on(\"hashchange\",function(){var e=window.location.hash.substr(1);i.channel(\"dashboard\").request(\"show:\"+e)}),n(\"main\",function(){})}();"],"file":"dashboard.min.js"}
1
+ {"version":3,"names":[],"mappings":"","sources":["views/widgets/forms/newFormTemplate.js"],"sourcesContent":["!function(){var e,t,n;!function(i){function s(e,t){return y.call(e,t)}function o(e,t){var n,i,s,o,r,a,l,d,c,u,h,m=t&&t.split(\"/\"),p=v.map,f=p&&p[\"*\"]||{};if(e&&\".\"===e.charAt(0))if(t){for(e=e.split(\"/\"),r=e.length-1,v.nodeIdCompat&&j.test(e[r])&&(e[r]=e[r].replace(j,\"\")),e=m.slice(0,m.length-1).concat(e),c=0;c<e.length;c+=1)if(\".\"===(h=e[c]))e.splice(c,1),c-=1;else if(\"..\"===h){if(1===c&&(\"..\"===e[2]||\"..\"===e[0]))break;c>0&&(e.splice(c-1,2),c-=2)}e=e.join(\"/\")}else 0===e.indexOf(\"./\")&&(e=e.substring(2));if((m||f)&&p){for(n=e.split(\"/\"),c=n.length;c>0;c-=1){if(i=n.slice(0,c).join(\"/\"),m)for(u=m.length;u>0;u-=1)if((s=p[m.slice(0,u).join(\"/\")])&&(s=s[i])){o=s,a=c;break}if(o)break;!l&&f&&f[i]&&(l=f[i],d=c)}!o&&l&&(o=l,a=d),o&&(n.splice(0,a,o),e=n.join(\"/\"))}return e}function r(e,t){return function(){var n=C.call(arguments,0);return\"string\"!=typeof n[0]&&1===n.length&&n.push(null),m.apply(i,n.concat([e,t]))}}function a(e){return function(t){return o(t,e)}}function l(e){return function(t){w[e]=t}}function d(e){if(s(g,e)){var t=g[e];delete g[e],b[e]=!0,h.apply(i,t)}if(!s(w,e)&&!s(b,e))throw new Error(\"No \"+e);return w[e]}function c(e){var t,n=e?e.indexOf(\"!\"):-1;return n>-1&&(t=e.substring(0,n),e=e.substring(n+1,e.length)),[t,e]}function u(e){return function(){return v&&v.config&&v.config[e]||{}}}var h,m,p,f,w={},g={},v={},b={},y=Object.prototype.hasOwnProperty,C=[].slice,j=/\\.js$/;p=function(e,t){var n,i=c(e),s=i[0];return e=i[1],s&&(s=o(s,t),n=d(s)),s?e=n&&n.normalize?n.normalize(e,a(t)):o(e,t):(e=o(e,t),i=c(e),s=i[0],e=i[1],s&&(n=d(s))),{f:s?s+\"!\"+e:e,n:e,pr:s,p:n}},f={require:function(e){return r(e)},exports:function(e){var t=w[e];return void 0!==t?t:w[e]={}},module:function(e){return{id:e,uri:\"\",exports:w[e],config:u(e)}}},h=function(e,t,n,o){var a,c,u,h,m,v,y=[],C=typeof n;if(o=o||e,\"undefined\"===C||\"function\"===C){for(t=!t.length&&n.length?[\"require\",\"exports\",\"module\"]:t,m=0;m<t.length;m+=1)if(h=p(t[m],o),\"require\"===(c=h.f))y[m]=f.require(e);else if(\"exports\"===c)y[m]=f.exports(e),v=!0;else if(\"module\"===c)a=y[m]=f.module(e);else if(s(w,c)||s(g,c)||s(b,c))y[m]=d(c);else{if(!h.p)throw new Error(e+\" missing \"+c);h.p.load(h.n,r(o,!0),l(c),{}),y[m]=w[c]}u=n?n.apply(w[e],y):void 0,e&&(a&&a.exports!==i&&a.exports!==w[e]?w[e]=a.exports:u===i&&v||(w[e]=u))}else e&&(w[e]=n)},e=t=m=function(e,t,n,s,o){if(\"string\"==typeof e)return f[e]?f[e](t):d(p(e,t).f);if(!e.splice){if(v=e,v.deps&&m(v.deps,v.callback),!t)return;t.splice?(e=t,t=n,n=null):e=i}return t=t||function(){},\"function\"==typeof n&&(n=s,s=o),s?h(i,e,t,n):setTimeout(function(){h(i,e,t,n)},4),m},m.config=function(e){return m(e)},e._defined=w,n=function(e,t,n){if(\"string\"!=typeof e)throw new Error(\"See almond README: incorrect module build, no module name\");t.splice||(n=t,t=[]),s(w,e)||s(g,e)||(g[e]=[e,t,n])},n.amd={jQuery:!0}}(),n(\"../../assets/js/lib/almond\",function(){}),n(\"models/formModel\",[],function(){return Backbone.Model.extend({defaults:{objectType:\"form\",id:0,title:\"unknown\",created_at:\"unknown\"},url:function(){return ajaxurl+\"?action=nf_forms&form_id=\"+this.get(\"id\")},initialize:function(){this.set(\"id\",Number(this.get(\"id\"))),this.get(\"id\")&&this.initShortcode(this.get(\"id\")),this.get(\"title\")&&this.set(\"title\",this.get(\"title\").replace(/<\\/?[^>]+(>|$)/g,\"\"))},initShortcode:function(e){var t=\"[ninja_form id=\"+e+\"]\";this.set(\"shortcode\",t)},destroy:function(){var e=this;jQuery.ajax({type:\"POST\",url:ajaxurl+\"?action=nf_forms&method_override=delete&form_id=\"+this.get(\"id\"),success:function(t){var t=JSON.parse(t);e.collection.remove(e)}})}})}),n(\"models/formCollection\",[\"models/formModel\"],function(e){return Backbone.Collection.extend({model:e,comparator:\"title\",tmpNum:1,url:function(){return ajaxurl+\"?action=nf_forms\"},initialize:function(){this.newIDs=[],this.baseUrl=window.location.href.split(\"?\")[0],this.listenTo(i.channel(\"dashboard\"),\"forms:delete\",this.modalConfirm),this.listenTo(i.channel(\"dashboard\"),\"forms:duplicate\",this.duplicate),this.modal=new jBox(\"Modal\",{width:400,addClass:\"dashboard-modal\",overlay:!0,closeOnClick:\"body\"})},parse:function(e,t){return e.data},modalConfirm:function(e){var t,n,i,s,o,r,a,l=e.model.get(\"id\"),d=e.model.get(\"title\");n=document.createElement(\"div\"),n.style.paddingRight=\"20px\",n.style.paddingLeft=\"20px\",n.style.paddingBottom=\"20px\",i=document.createElement(\"p\"),s=document.createElement(\"em\"),o=document.createElement(\"div\"),r=document.createElement(\"button\"),a=document.createElement(\"div\"),n.classList.add(\"message\"),s.innerHTML=d,i.innerHTML+=nfi18n.deleteWarningA+\" (<strong>\"+d+\"</strong>). \"+nfi18n.deleteWarningB,i.appendChild(document.createElement(\"br\")),i.appendChild(document.createElement(\"br\"));var c=document.createElement(\"a\");c.href=this.baseUrl+\"?page=nf-import-export&exportFormId=\"+l,c.innerHTML='<i class=\"fa fa-download\" style=\"padding:5px;\"></i>'+nfi18n.deleteXForm,c.target=\"_blank\",i.appendChild(c),i.appendChild(document.createElement(\"br\"));var u=document.createElement(\"a\");u.href=this.baseUrl+\"?page=nf-processing&action=download_all_subs&form_id=\"+l+\"&redirect=\"+encodeURIComponent(this.baseUrl.replace(\"admin.php\",\"edit.php\")+\"?post_status=all&post_type=nf_sub&form_id=\"+l),u.target=\"_blank\",u.innerHTML='<i class=\"fa fa-download\" style=\"padding:5px;\"></i>'+nfi18n.deleteXSubs,i.appendChild(u),i.appendChild(document.createElement(\"br\")),n.appendChild(i);var h=document.createElement(\"label\");h.for=\"confirmDeleteFormInput\",h.innerHTML=nfi18n.deleteConfirmA+' <span style=\"color:red;\">DELETE</span> '+nfi18n.deleteConfirmB;var m=document.createElement(\"input\");m.type=\"text\",m.id=\"confirmDeleteFormInput\",m.style.marginTop=\"10px\",m.style.width=\"100%\",m.style.height=\"2.5em\",m.style.fontSize=\"1em\",n.appendChild(h),n.appendChild(document.createElement(\"br\")),n.appendChild(m),n.appendChild(document.createElement(\"br\")),n.appendChild(document.createElement(\"br\")),r.innerHTML=nfi18n.delete,r.classList.add(\"confirm\",\"nf-button\",\"primary\",\"pull-right\"),a.innerHTML=nfi18n.cancel,a.classList.add(\"cancel\",\"nf-button\",\"secondary\"),o.appendChild(a),o.appendChild(r),o.classList.add(\"buttons\"),n.appendChild(o),t=document.createElement(\"div\"),t.appendChild(n),this.modal.setContent(t.innerHTML),this.modal.setTitle(nfi18n.deleteTitle),this.modal.open();var p=this;this.modal.container[0].getElementsByClassName(\"cancel\")[0].addEventListener(\"click\",function(){p.modalClose()}),this.modal.container[0].getElementsByClassName(\"confirm\")[0].addEventListener(\"click\",function(t){t.preventDefault(),\"DELETE\"===document.getElementById(\"confirmDeleteFormInput\").value?p.confirmDelete(e):p.modalClose()})},modalClose:function(){this.modal.close()},confirmDelete:function(e){jQuery(e.el).removeClass(\"show-actions\"),jQuery(e.el).addClass(\"deleting\"),jQuery(e.el).animate({opacity:0,\"line-height\":0,display:\"none\"},500),console.log(e),e.model.destroy(),this.modalClose()},duplicate:function(e){var t='<div class=\"message\">Duplicating <em>'+e.model.get(\"title\")+'</em>...<div class=\"nf-loading-spinner\"></div></div>';this.modal.setContent(t),this.modal.setTitle(\"Please Wait\"),this.modal.open();var n=this;jQuery.ajax({type:\"POST\",url:ajaxurl+\"?action=nf_forms&clone_id=\"+e.model.get(\"id\"),success:function(t){var t=JSON.parse(t),i=t.data.new_form_id,s=e.model.clone();s.set({id:i,title:s.get(\"title\")+\" - copy\",created_at:new Date}),s.initShortcode(i),e.model.collection.add(s),n.modalClose()}})}})}),n(\"controllers/formsController\",[\"models/formModel\",\"models/formCollection\"],function(e,t){return Marionette.Object.extend({initialize:function(){this.forms=new t,i.channel(\"dashboard\").reply(\"get:forms\",this.getForms,this),this.forms.fetch({success:function(e){i.channel(\"dashboard\").trigger(\"fetch:forms\",e)}})},getForms:function(){return this.forms}})}),n(\"models/oauthModel\",[],function(){return Backbone.Model.extend({defaults:{connected:null,connect_url:\"\"},url:function(){return ajaxurl+\"?action=nf_oauth\"},initialize:function(){},parse:function(e,t){return e.data}})}),n(\"controllers/oauthController\",[\"models/oauthModel\"],function(e){return Marionette.Object.extend({initialize:function(){this.oauth=new e,i.channel(\"dashboard\").reply(\"get:oauth\",this.getOAuth,this),i.channel(\"dashboard\").reply(\"disconnect:oauth\",this.disconnect,this),i.channel(\"dashboard\").reply(\"oauth:learn-more\",this.learnMoreModal,this),this.initOAuth()},getOAuth:function(){return this.oauth},initOAuth:function(){this.oauth.fetch({success:function(e){i.channel(\"dashboard\").trigger(\"fetch:oauth\")}})},disconnect:function(){var e=this;new jBox(\"Confirm\",{width:750,content:nfi18n.oauthDisconnectContent,confirmButton:nfi18n.oauthDisconnectConfirm,cancelButton:nfi18n.oauthDisconnectCancel,closeOnConfirm:!0,confirm:function(){jQuery.ajax({type:\"POST\",url:ajaxurl+\"?action=nf_oauth_disconnect\",success:function(t){console.log(t),e.initOAuth()}})}}).open()},learnMoreModal:function(){new jBox(\"Modal\",{width:500,content:nfi18n.oauthLearnMoreContent}).open()}})}),n(\"models/serviceModel\",[],function(){return Backbone.Model.extend({defaults:{objectType:\"service\",name:\"\",slug:\"\",installPath:\"\",description:\"\",enabled:null,infoLink:null,serviceLink:null,is_installing:!1,classes:\"\"},url:function(){return ajaxurl+\"?action=nf_service_\"+this.get(\"slug\")},initialize:function(){this.get(\"slug\")==serviceSuccess&&this.get(\"successMessage\")&&new jBox(\"Modal\",{width:300,addClass:\"dashboard-modal\",overlay:!0,closeOnClick:!0,content:this.get(\"successMessage\"),title:this.get(\"successMessageTitle\"),closeButton:\"box\"}).open();var e=this;i.channel(\"dashboard\").reply(\"install:service:\"+this.get(\"slug\"),function(){if(e.get(\"serviceLink\")&&e.get(\"serviceLink\").href){var t=e.get(\"serviceLink\").href;new jBox(\"Modal\",{width:300,addClass:\"dashboard-modal\",overlay:!0,closeOnClick:\"body\",content:nfi18n.serviceRedirect}).open();var n=i.channel(\"dashboard\").request(\"get:oauth\");if(n.get(\"connected\"))window.location=t;else{if(e.get(\"connect_url\"))return window.location=e.get(\"connect_url\")+\"&redirect=\"+t;window.location=n.get(\"connect_url\")+\"&redirect=\"+t}}})},save:function(){var e=this;jQuery.ajax({type:\"POST\",url:this.url(),data:this.toJSON()}).done(function(t){var n=JSON.parse(t);void 0!==n.error&&(alert(nfi18n.serviceUpdateError+\" \"+n.error),e.set(\"enabled\",!e.get(\"enabled\"))),i.channel(\"dashboard\").trigger(\"save:service-\"+e.get(\"slug\"))})}})}),n(\"models/serviceCollection\",[\"models/serviceModel\"],function(e){return Backbone.Collection.extend({model:e,comparator:\"name\",url:function(){return ajaxurl+\"?action=nf_services\"},initialize:function(){},parse:function(e,t){return e.data}})}),n(\"controllers/servicesController\",[\"models/serviceCollection\"],function(e){return Marionette.Object.extend({initialize:function(){this.services=new e,i.channel(\"dashboard\").reply(\"install:service\",this.installService,this),i.channel(\"dashboard\").reply(\"get:services\",this.getServices,this),this.fetchServices()},getServices:function(){return this.services},fetchServices:function(e){this.services.fetch({success:function(t){e&&e(t),i.channel(\"dashboard\").trigger(\"fetch:services\")}})},installService:function(e){var t=this;if(!(e instanceof Backbone.Model))var e=this.services.find(function(t){return e==t.get(\"slug\")});e.set(\"is_installing\",!0);var n=e.get(\"slug\"),s=e.get(\"installPath\");jQuery.post(ajaxurl,{action:\"nf_services_install\",plugin:n,install_path:s},function(e){t.fetchServices(function(){i.channel(\"dashboard\").request(\"install:service:\"+n)})})}})}),n(\"views/widgets/forms/formsFilter\",[],function(){return Marionette.View.extend({template:\"#tmpl-nf-widget-forms-filter\",ui:{input:\"input\"},events:{\"keyup @ui.input\":\"updateFilter\"},initialize:function(){this.listenTo(i.channel(\"widget-forms\"),\"change:content\",this.clearFilter)},updateFilter:function(){var e=this.getUI(\"input\").val();i.channel(\"widget-forms\").trigger(\"update:filter\",e)},clearFilter:function(){this.getUI(\"input\").val(\"\")},updatePlaceholder:function(e){this.getUI(\"input\").attr(\"placeholder\",e)}})}),n(\"views/widgets/forms/formsTableRow\",[],function(){return Marionette.View.extend({template:\"#tmpl-nf-widget-forms-table-row\",tagName:\"tr\",replaceElement:!0,ui:{delete:\".delete\",duplicate:\".duplicate\",edit:\".nf-item-edit\"},events:{\"click @ui.delete\":function(){i.channel(\"dashboard\").trigger(\"forms:delete\",this)},\"click @ui.duplicate\":function(){i.channel(\"dashboard\").trigger(\"forms:duplicate\",this)},\"click @ui.edit\":function(e){this.$el.toggleClass(\"show-actions\").siblings().removeClass(\"show-actions\")}},templateContext:function(){var e=this.model;return{created_at:moment(e.get(\"created_at\")).format(\"MM/DD/YY h:mm A\")}}})}),n(\"views/widgets/forms/formsTableEmpty\",[],function(){return Marionette.View.extend({template:\"#tmpl-nf-widget-forms-table-empty\",tagName:\"tr\"})}),n(\"views/widgets/forms/formsTableBody\",[\"views/widgets/forms/formsTableRow\",\"views/widgets/forms/formsTableEmpty\"],function(e,t){return Marionette.CollectionView.extend({childView:e,emptyView:t,className:\"forms-collection\",tagName:\"tbody\",initialize:function(){this.listenTo(i.channel(\"widget-forms\"),\"update:filter\",this.updateFilter)},updateFilter:function(e){this.setFilter(function(t,n,i){return 0<=t.get(\"title\").toLowerCase().indexOf(e.toLowerCase())})}})}),n(\"views/widgets/forms/formsTableLoading\",[],function(){return Marionette.View.extend({template:\"#tmpl-nf-widget-forms-table-loading\",tagName:\"tr\"})}),n(\"views/widgets/forms/formsTable\",[\"views/widgets/forms/formsTableBody\",\"views/widgets/forms/formsTableLoading\",\"models/formCollection\"],function(e,t,n){return Marionette.View.extend({template:\"#tmpl-nf-widget-forms-table\",className:\"nf-table-display\",tagName:\"table\",initialize:function(){var t=this;this.listenTo(i.channel(\"dashboard\"),\"fetch:forms\",function(n){t.showChildView(\"body\",new e({collection:n}))})},regions:{body:{el:\"tbody\",replaceElement:!0}},ui:{sortable:\".sortable\",body:\"tbody\",action2:\".action2\",more:\".more\",less:\".less\"},onRender:function(){this.getUI(\"less\").hide();var n=i.channel(\"dashboard\").request(\"get:forms\");void 0===n?this.showChildView(\"body\",new t):this.showChildView(\"body\",new e({collection:n})),this.maybeHideMoreButton()},events:{\"click @ui.sortable\":\"sortFormsTable\",\"click @ui.more\":\"showMore\",\"click @ui.less\":\"showLess\"},sortFormsTable:function(e){this.getUI(\"sortable\").removeClass(\"sorted-asc\"),this.getUI(\"sortable\").removeClass(\"sorted-desc\");var t=jQuery(e.target).data(\"sort\"),n=jQuery(e.target).data(\"reverse\")||0;n?(jQuery(e.target).addClass(\"sorted-desc\"),jQuery(e.target).removeClass(\"sorted-asc\")):(jQuery(e.target).addClass(\"sorted-asc\"),jQuery(e.target).removeClass(\"sorted-desc\"));var i=this.getChildView(\"body\").collection;i.comparator=function(e,i){return name1=e.get(t).toLowerCase(),name2=i.get(t).toLowerCase(),name1<name2?ret=-1:name1>name2?ret=1:ret=0,n&&(ret=-ret),ret},i.sort(),n?(i.models.reverse(),jQuery(e.target).data(\"reverse\",0)):jQuery(e.target).data(\"reverse\",1)},showMore:function(){this.getUI(\"more\").hide(),this.getUI(\"less\").show(),this.getUI(\"body\").addClass(\"more\")},showLess:function(){this.getUI(\"less\").hide(),this.getUI(\"more\").show(),this.getUI(\"body\").removeClass(\"more\")},maybeHideMoreButton:function(){void 0!==this.collection&&10<this.collection.length||this.getUI(\"action2\").hide()}})}),n(\"models/formTemplateModel\",[],function(){return Backbone.Model.extend({defaults:{objectType:\"template\",id:\"none\",title:\"unknown\",type:\"\"},initialize:function(){this.set(\"desc\",this.get(\"template-desc\")),this.set(\"modal-content\",this.get(\"modal-content\")),this.set(\"modal-title\",this.get(\"modal-title\"))}})}),n(\"models/formTemplateCollection\",[\"models/formTemplateModel\"],function(e){return Backbone.Collection.extend({model:e,tmpNum:1,url:function(){return ajaxurl+\"?action=nf_new_form_templates\"},parse:function(e,t){return e.data},initialize:function(){this.fetch({success:function(e){},error:function(e){}})}})}),n(\"views/widgets/forms/newFormTemplate\",[],function(){return Marionette.View.extend({template:\"#tmpl-nf-widget-forms-template\",events:{click:\"maybeOpenModal\"},maybeOpenModal:function(e){if(e.preventDefault(),\"ad\"==this.model.get(\"type\")){new jBox(\"Modal\",{width:450,title:this.model.get(\"modal-title\"),content:this.model.get(\"modal-content\"),closeButton:\"box\",blockScroll:!0}).open()}else{var t={batch_type:\"import_form_template\",loadingText:\"Importing...\",extraData:{template:this.model.get(\"id\")},onCompleteCallback:function(e){if(void 0===e.form_id)return!1;window.location.href=nfAdmin.builderURL+e.form_id}};new NinjaBatchProcessor(t)}}})}),n(\"views/widgets/forms/newFormGrid\",[\"models/formTemplateCollection\",\"views/widgets/forms/newFormTemplate\"],function(e,t){return Marionette.CollectionView.extend({tagName:\"div\",className:\"template-list\",collection:new e,childView:t,initialize:function(){this.listenTo(i.channel(\"widget-forms\"),\"update:filter\",this.updateFilter)},updateFilter:function(e){this.setFilter(function(t,n,i){return 0<=t.get(\"title\").toLowerCase().indexOf(e.toLowerCase())})}})}),n(\"views/widgets/forms/forms\",[\"views/widgets/forms/formsFilter\",\"views/widgets/forms/formsTable\",\"views/widgets/forms/newFormGrid\"],function(e,t,n){return Marionette.View.extend({template:\"#tmpl-nf-widget-forms\",regions:{filter:\".filter\",content:\".content\"},ui:{add:\".add\",cancel:\".cancel\"},initialize:function(){},onRender:function(){this.getUI(\"cancel\").hide(),this.showChildView(\"filter\",new e),\"#new-form\"==window.location.hash?(this.getUI(\"add\").hide(),this.getUI(\"cancel\").show(),this.showChildView(\"content\",new n)):this.showChildView(\"content\",new t)},events:{\"click @ui.add\":\"showNewFormGrid\",\"click @ui.cancel\":\"showFormsTable\"},showNewFormGrid:function(){window.location.hash=\"new-form\",this.showChildView(\"content\",new n),i.channel(\"widget-forms\").trigger(\"change:content\"),this.getUI(\"add\").hide(),this.getUI(\"cancel\").show(),this.getChildView(\"filter\").updatePlaceholder(\"Search Templates\")},showFormsTable:function(){window.location.hash=\"forms\",this.showChildView(\"content\",new t),i.channel(\"widget-forms\").trigger(\"change:content\"),this.getUI(\"cancel\").hide(),this.getUI(\"add\").show(),this.getChildView(\"filter\").updatePlaceholder(\"Search Forms\")}})}),n(\"views/sections/widgets.js\",[\"views/widgets/forms/forms\"],function(e){return Marionette.View.extend({template:\"#tmpl-nf-widgets\",regions:{forms:\".widget-forms\"},onRender:function(){this.showChildView(\"forms\",new e)}})}),n(\"views/services/service\",[],function(){return Marionette.View.extend({template:\"#tmpl-nf-service\",className:function(){return\"nf-extend nf-box \"+this.model.get(\"classes\")},ui:{install:\".js--install\",learnMore:\".js--learn-more\",enabled:\".nf-toggle.setting\",toggleEnable:\".nf-toggle + label\"},events:{\"click @ui.install\":function(){i.channel(\"dashboard\").request(\"install:service\",this.model)},\"click @ui.learnMore\":function(){this.showLearnMore()},\"click @ui.toggleEnable\":function(){if(null==this.model.get(\"enabled\")&&this.model.get(\"link\"))return window.location=this.model.get(\"link\"),this.render();this.model.set(\"enabled\",!this.model.get(\"enabled\")),this.model.save(\"enabled\"),this.render()}},initialize:function(e){this.updateOAuth(),this.listenTo(this.model,\"change\",this.render),i.channel(\"dashboard\").reply(\"more:service:\"+this.model.get(\"slug\"),this.showLearnMore,this),this.listenTo(i.channel(\"dashboard\"),\"fetch:oauth\",this.updateOAuth),this.listenTo(i.channel(\"dashboard\"),\"save:service-\"+this.model.get(\"slug\"),this.render)},showLearnMore:function(){var e=new jBox(\"Modal\",{width:750,title:this.model.get(\"learnMoreTitle\")||this.model.get(\"name\"),content:this.model.get(\"learnMore\"),closeButton:\"box\",blockScroll:!0});e.open(),i.channel(\"dashboard\").reply(\"service:\"+this.model.get(\"slug\")+\":modal\",function(){return e})},updateOAuth:function(){var e=i.channel(\"dashboard\").request(\"get:oauth\");this.connected=e.get(\"connected\"),this.render()},templateContext:function(){return{is_connected:this.connected}}})}),n(\"views/services/services\",[\"views/services/service\",\"models/serviceCollection\"],function(e,t){return Marionette.CollectionView.extend({collection:new t,className:\"wrap apps-container\",childView:e,initialize:function(){this.updateCollection(),this.listenTo(i.channel(\"dashboard\"),\"fetch:services\",this.updateCollection)},updateCollection:function(){this.collection=i.channel(\"dashboard\").request(\"get:services\"),this.render()}})}),n(\"views/sections/services.js\",[\"views/services/services\"],function(e){return Marionette.View.extend({template:\"#tmpl-nf-services\",regions:{services:\".services\"},onRender:function(){this.showChildView(\"services\",new e)}})}),n(\"views/sections/apps.js\",[],function(){return Marionette.View.extend({template:\"#tmpl-nf-apps\"})}),n(\"views/sections/memberships.js\",[],function(){return Marionette.View.extend({template:\"#tmpl-nf-memberships\"})}),n(\"views/oauth.js\",[\"models/oauthModel\"],function(e){return Marionette.View.extend({model:new e,template:\"#tmpl-nf-notices-oauth\",className:\"nf-notices--oauth\",ui:{disconnect:\".js--disconnect\"},initialize:function(e){this.listenTo(i.channel(\"dashboard\"),\"fetch:oauth\",this.updateModel)},updateModel:function(){this.model=i.channel(\"dashboard\").request(\"get:oauth\"),this.render()},events:{\"click @ui.disconnect\":function(){i.channel(\"dashboard\").request(\"disconnect:oauth\")}}})}),n(\"models/promotionModel\",[],function(){return Backbone.Model.extend({defaults:{id:\"\",content:\"\"},initialize:function(){}})}),n(\"views/promotion.js\",[\"models/promotionModel\"],function(e){return Marionette.View.extend({model:null,template:\"#tmpl-nf-promotion\",className:\"nf-promotion\",initialize:function(){var t=nfPromotions[Math.floor(Math.random()*nfPromotions.length)];this.model=new e(t)}})}),n(\"views/sections/requiredUpdates.js\",[],function(){return Marionette.View.extend({template:\"#tmpl-nf-requiredUpdates\",updates:[],currentUpdate:0,totalUpdates:-1,updatesRemaining:-1,ui:{requiredUpdates:\".nf-required-update\"},onRender:function(){this.getRequiredUpdates()},setButtonClickEvent:function(){var e=this;jQuery(\"#nf-required-updates-btn\").off(\"click\").on(\"click\",function(t){t.preventDefault(),e.doRequiredUpdates(),jQuery(this).hide()})},getRequiredUpdates:function(){var e=this;jQuery.get(ajaxurl,{action:\"nf_required_update\"}).then(function(t){var n=JSON.parse(t);0===n.errors.length&&(e.totalUpdates=n.data.updates.length,e.updates=n.data.updates,0<e.updates.length?(e.requiredUpdates=e.updates.length,e.constructUpdateTable(),e.setButtonClickEvent()):window.location=window.location.origin+window.location.pathname+window.location.search)})},constructUpdateTable:function(){var e=this,t=document.getElementById(\"nf-upgrades-table\"),n=t.getElementsByTagName(\"thead\")[0],i=document.createElement(\"tr\"),s=document.createElement(\"th\");s.innerHTML=\"Update\",s.classList.add(\"nf-update-name-cell\");var o=document.createElement(\"th\");o.innerHTML=\"Progress\",o.classList.add(\"nf-update-progress-cell\"),i.appendChild(s),i.appendChild(o),n.appendChild(i);var r=t.getElementsByTagName(\"tbody\")[0];jQuery.each(this.updates,function(t,n){var i=document.createElement(\"tr\"),s=document.createElement(\"td\");s.innerHTML=n.nicename;var o=document.createElement(\"td\"),a=document.createElement(\"div\");a.id=\"update-progress-\"+t;var l=e.createNewProgressBar(t);a.appendChild(l),o.appendChild(a),i.appendChild(s),i.appendChild(o),r.appendChild(i)}),document.getElementById(\"nf-required-updates-btn\").style.display=\"block\"},doRequiredUpdates:function(){window.location.hash=\"#requiredUpdates\";var e=this;jQuery(\"#nf-required-updates-btn\").addClass(\"disabled\").attr(\"disabled\",\"disabled\"),jQuery.post(ajaxurl,{action:\"nf_required_update\",security:nfAdmin.updateNonce}).then(function(t){var n=JSON.parse(t);n.updatesRemaining>0?(e.updatesRemaining!==n.updatesRemaining&&n.currentStep===n.stepsTotal?(e.finishUpdate(e.currentUpdate),e.updatesRemaining=n.updatesRemaining):(e.showProgressBars(n),e.updatesRemaining=n.updatesRemaining),e.doRequiredUpdates()):(e.finishUpdate(e.currentUpdate),nfAdmin.requiredUpdates=0,jQuery(\"#nf-required-updates-btn\").removeClass(\"disabled\").removeAttr(\"disabled\").val(\"Go To Dashboard\").off(\"click\").on(\"click\",function(e){e.preventDefault(),window.location=window.location.origin+window.location.pathname+window.location.search}).show(),console.log(\"UPDATES DONE\"))})},showProgressBars:function(e){var t=this.totalUpdates-e.updatesRemaining,n=e.currentStep,i=e.stepsTotal,s=document.getElementById(\"nf_progressBar_\"+t);null==s&&(this.currentUpdate+=1,1===this.currentUpdate&&-1===this.totalUpdates&&(this.totalUpdates=t),s=this.createNewProgressBar(t)),this.incrementProgress(t,n,i)},createNewProgressBar:function(e){var t=document.createElement(\"div\");t.id=\"nf_progressBar_\"+e,t.classList.add(\"jBox-content\"),t.style.display=\"none\";var n=document.createElement(\"div\");n.classList.add(\"nf-progress-bar\");var i=document.createElement(\"div\");return i.id=\"nf-progress-bar-slider-\"+e,i.classList.add(\"nf-progress-bar-slider\"),n.appendChild(i),t.appendChild(n),t},incrementProgress:function(e,t,n){document.getElementById(\"nf_progressBar_\"+e).style.display=\"block\";var i=document.getElementById(\"nf-progress-bar-slider-\"+e),s=Number(t)/Number(n)*100;s>i.offsetWidth/i.parentElement.offsetWidth*100&&this.setProgress(e,s)},setProgress:function(e,t){document.getElementById(\"nf-progress-bar-slider-\"+e).style.width=t+\"%\",100<=t&&this.finishUpdate(e)},finishUpdate:function(e){var t=document.getElementById(\"nf_progressBar_\"+e),n=t.parentNode;n.removeChild(t);var i=document.createElement(\"span\");i.classList.add(\"dashicons\"),i.classList.add(\"dashicons-yes\"),n.appendChild(i),this.currentUpdate=this.currentUpdate+1}})}),n(\"views/dashboardView\",[\"views/sections/widgets.js\",\"views/sections/services.js\",\"views/sections/apps.js\",\"views/sections/memberships.js\",\"views/oauth.js\",\"views/promotion.js\",\"views/sections/requiredUpdates.js\"],function(e,t,n,s,o,r,a){return Marionette.View.extend({template:\"#tmpl-nf-dashboard\",currentView:\"widgets\",regions:{notices:\".notices\",promotions:\".promotions\",content:\".content\"},events:{\"click .widgets a\":function(t){\"1\"!=nfAdmin.requiredUpdates&&(this.showChildView(\"content\",new e),jQuery(\".\"+this.currentView).find(\"a\").removeClass(\"active\"),t.target.classList.add(\"active\"),this.currentView=\"widgets\")},\"click .services a\":function(e){\"1\"!=nfAdmin.requiredUpdates&&(this.showChildView(\"content\",new t),jQuery(\".\"+this.currentView).find(\"a\").removeClass(\"active\"),e.target.classList.add(\"active\"),this.currentView=\"services\")},\"click .apps a\":function(e){\"1\"!=nfAdmin.requiredUpdates&&(this.showChildView(\"content\",new n),jQuery(\".\"+this.currentView).find(\"a\").removeClass(\"active\"),e.target.classList.add(\"active\"),this.currentView=\"apps\")},\"click .memberships a\":function(e){\"1\"!=nfAdmin.requiredUpdates&&(this.showChildView(\"content\",new s),jQuery(\".\"+this.currentView).find(\"a\").removeClass(\"active\"),e.target.classList.add(\"active\"),this.currentView=\"memberships\")}},initialize:function(){switch(\"1\"===nfAdmin.requiredUpdates?window.location.hash=\"#requiredUpdates\":\"#requiredUpdates\"===window.location.hash&&(window.location.hash=\"\"),window.location.hash){case\"#apps\":this.currentView=\"apps\";break;case\"#services\":this.currentView=\"services\";break;case\"#memberships\":this.currentView=\"memberships\";break;case\"#requiredUpdates\":this.currentView=\"requiredUpdates\";break;case\"#widgets\":default:this.currentView=\"widgets\"}i.channel(\"dashboard\").reply(\"show:widgets\",function(){\"1\"!=nfAdmin.requiredUpdates&&(this.showChildView(\"content\",new e),jQuery(\"nav.sections a.active\").removeClass(\"active\"),jQuery(\"nav.sections .widgets a\").addClass(\"active\"),this.currentView=\"widgets\")},this),i.channel(\"dashboard\").reply(\"show:services\",function(){\"1\"!=nfAdmin.requiredUpdates&&(this.showChildView(\"content\",new t),jQuery(\"nav.sections a.active\").removeClass(\"active\"),jQuery(\"nav.sections .services a\").addClass(\"active\"),this.currentView=\"services\")},this),i.channel(\"dashboard\").reply(\"show:apps\",function(){\"1\"!=nfAdmin.requiredUpdates&&(this.showChildView(\"content\",new n),jQuery(\"nav.sections a.active\").removeClass(\"active\"),jQuery(\"nav.sections .apps a\").addClass(\"active\"),this.currentView=\"apps\")},this)},onRender:function(){switch(useServices&&this.showChildView(\"notices\",new o),useServices&&\"1\"!==nfAdmin.requiredUpdates&&this.showChildView(\"promotions\",new r),\"0\"===nfAdmin.requiredUpdates&&\"#requiredUpdates\"===window.location.hash&&(window.location.hash=\"\"),window.location.hash){case\"#apps\":var i=new n;break;case\"#memberships\":var i=new s;break;case\"#services\":var i=new t;break;case\"#requiredUpdates\":var i=new a;break;case\"#widgets\":default:var i=new e}if(this.showChildView(\"content\",i),\"1\"==nfAdmin.showOptin){var l=new jBox(\"Modal\",{closeOnEsc:!1,closeOnClick:!1,width:400}),d=document.createElement(\"div\");d.id=\"optin-modal-title\";var c=document.createElement(\"h2\");c.innerHTML=\"Help make Ninja Forms better!\",d.appendChild(c);var u=document.createElement(\"div\");u.classList.add(\"message\"),u.style.padding=\"0px 20px 20px 20px\",u.innerHTML=nfi18n.optinContent;var h=document.createElement(\"p\");h.style.paddingBottom=\"10px\";var m=document.createElement(\"input\");m.id=\"optin-send-email\",m.setAttribute(\"type\",\"checkbox\"),m.style.margin=\"7px\";var p=document.createElement(\"label\");p.setAttribute(\"for\",\"optin-send-email\"),p.innerHTML=nfi18n.optinYesplease,h.appendChild(m),h.appendChild(p),u.appendChild(h),h=document.createElement(\"p\"),h.id=\"optin-block\",h.style.padding=\"0px 5px 20px 5px\",h.style.display=\"none\";var f=document.createElement(\"input\");f.id=\"optin-email-address\",f.setAttribute(\"type\",\"text\"),f.setAttribute(\"value\",nfAdmin.currentUserEmail),f.style.width=\"100%\",f.style.fontSize=\"16px\",h.appendChild(f),u.appendChild(h);var w=document.createElement(\"span\");w.id=\"optin-spinner\",w.classList.add(\"spinner\"),w.style.display=\"none\",u.appendChild(w);var g=document.createElement(\"div\");g.id=\"optin-buttons\",g.classList.add(\"buttons\");var v=document.createElement(\"div\");v.id=\"optout\",v.classList.add(\"nf-button\",\"secondary\"),v.innerHTML=nfi18n.optinSecondary,g.appendChild(v);var b=document.createElement(\"div\");b.id=\"optin\",b.classList.add(\"nf-button\",\"primary\",\"pull-right\"),b.innerHTML=nfi18n.optinPrimary,g.appendChild(b),u.appendChild(g);var y=document.createElement(\"h2\");y.innerHTML=nfi18n.optinAwesome;var C=document.createElement(\"div\");C.id=\"optin-thankyou\",C.classList.add(\"message\"),C.style.padding=\"20px\",C.innerHTML=nfi18n.optinThanks,l.setContent(document.createElement(\"div\").appendChild(u).innerHTML),l.setTitle(document.createElement(\"div\").appendChild(d).innerHTML),l.open(),jQuery(\"#optin-send-email\").click(function(e){jQuery(this).is(\":checked\")?jQuery(\"#optin-block\").show():jQuery(\"#optin-block\").hide()}),jQuery(\"#optin\").click(function(e){var t;jQuery(\"#optin-send-email\").attr(\"checked\")?(t=1,userEmail=jQuery(\"#optin-email-address\").val()):(t=0,userEmail=\"\"),jQuery(\"#optin\").unbind(\"click\"),jQuery(\"#optout\").unbind(\"click\");var n=jQuery(\"#optin\").width();jQuery(\"#optin\").html('<span class=\"dashicons dashicons-update dashicons-update-spin\"></span>'),jQuery(\"#optin\").width(n),jQuery.post(ajaxurl,{action:\"nf_optin\",ninja_forms_opt_in:1,send_email:t,user_email:userEmail},function(e){l.setTitle(document.createElement(\"div\").appendChild(y).innerHTML),l.setContent(document.createElement(\"div\").appendChild(C).innerHTML),setTimeout(function(){l.close()},2e3)})}),jQuery(\"#optout\").click(function(e){jQuery(\"#optin\").unbind(\"click\"),jQuery(\"#optout\").unbind(\"click\");var t=jQuery(\"#optout\").width();jQuery(\"#optout\").html('<span class=\"dashicons dashicons-update dashicons-update-spin\"></span>'),jQuery(\"#optout\").width(t),jQuery.post(ajaxurl,{action:\"nf_optin\",ninja_forms_opt_in:0},function(e){l.close()})})}if(void 0!==nfAdmin.formTelemetry&&1==nfAdmin.formTelemetry){var j={action:\"nf_form_telemetry\",security:nfAdmin.ajaxNonce};jQuery.post(ajaxurl,j)}},templateContext:function(){var e=this;return{renderNav:function(){var t=document.createElement(\"div\");return _.each(nfDashItems,function(n){var i=document.createElement(\"li\"),s=document.createElement(\"a\");s.href=\"#\"+n.slug,\ne.currentView==n.slug&&s.classList.add(\"active\"),s.innerHTML=n.niceName,i.classList.add(n.slug),i.appendChild(s),t.appendChild(i)}),t.innerHTML}}}})});var i=Backbone.Radio;useServices||(nfDashItems=nfDashItems.filter(function(e){return\"services\"!==e.slug})),jQuery(document).ready(function(e){t([\"controllers/formsController\",\"controllers/oauthController\",\"controllers/servicesController\",\"views/dashboardView\"],function(t,n,i,s){(new(Marionette.Application.extend({region:\"#ninja-forms-dashboard\",controllers:{},initialize:function(e){var t=this;Marionette.Renderer.render=function(e,n){var e=t.template(e);return e(n)}},onStart:function(){this.showView(new s),this.controllers.forms=new t,useServices&&(this.controllers.oauth=new n),useServices&&(this.controllers.services=new i)},template:function(t){return _.template(e(t).html(),{evaluate:/<#([\\s\\S]+?)#>/g,interpolate:/\\{\\{\\{([\\s\\S]+?)\\}\\}\\}/g,escape:/\\{\\{([^\\}]+?)\\}\\}(?!\\})/g,variable:\"data\"})}}))).start()})}),jQuery('a[href=\"admin.php?page=ninja-forms#new-form\"]').on(\"click\",function(e){e.preventDefault(),\"1\"!==nfAdmin.requiredUpdates&&(window.location.hash=\"new-form\",i.channel(\"dashboard\").request(\"show:widgets\"),i.channel(\"widget-forms\").request(\"show:newFormsGrid\"))}),jQuery('a[href=\"admin.php?page=ninja-forms#apps\"]').on(\"click\",function(e){e.preventDefault(),\"1\"!==nfAdmin.requiredUpdates&&(window.location.hash=\"apps\",i.channel(\"dashboard\").request(\"show:apps\"))}),jQuery('a[href=\"admin.php?page=ninja-forms\"]').on(\"click\",function(e){e.preventDefault(),\"1\"!==nfAdmin.requiredUpdates&&(window.location.hash=\"forms\",i.channel(\"dashboard\").request(\"show:widgets\"),i.channel(\"widget-forms\").request(\"show:formsTable\"))}),jQuery(window).on(\"hashchange\",function(){var e=window.location.hash.substr(1);i.channel(\"dashboard\").request(\"show:\"+e)}),n(\"main\",function(){})}();"],"file":"dashboard.min.js"}
client/dashboard/main.js CHANGED
@@ -80,23 +80,32 @@ jQuery( document ).ready( function( $ ) {
80
 
81
  jQuery( 'a[href="admin.php?page=ninja-forms#new-form"]' ).on( 'click', function( event ){
82
  event.preventDefault();
83
- window.location.hash = 'new-form';
84
- nfRadio.channel( 'dashboard' ).request( 'show:widgets' );
85
- nfRadio.channel( 'widget-forms' ).request( 'show:newFormsGrid' );
 
 
 
86
  } );
87
 
88
  jQuery( 'a[href="admin.php?page=ninja-forms#apps"]' ).on( 'click', function( event ){
89
  event.preventDefault();
90
- window.location.hash = 'apps';
91
- nfRadio.channel( 'dashboard' ).request( 'show:apps' );
 
 
 
92
 
93
  } );
94
 
95
  jQuery( 'a[href="admin.php?page=ninja-forms"]' ).on( 'click', function( event ){
96
  event.preventDefault();
97
- window.location.hash = 'forms';
98
- nfRadio.channel( 'dashboard' ).request( 'show:widgets' );
99
- nfRadio.channel( 'widget-forms' ).request( 'show:formsTable' );
 
 
 
100
  } );
101
 
102
  /**
80
 
81
  jQuery( 'a[href="admin.php?page=ninja-forms#new-form"]' ).on( 'click', function( event ){
82
  event.preventDefault();
83
+ // We won't allow access to add forms if there are required updates
84
+ if( "1" !== nfAdmin.requiredUpdates ) {
85
+ window.location.hash = 'new-form';
86
+ nfRadio.channel( 'dashboard' ).request( 'show:widgets' );
87
+ nfRadio.channel( 'widget-forms' ).request( 'show:newFormsGrid' );
88
+ }
89
  } );
90
 
91
  jQuery( 'a[href="admin.php?page=ninja-forms#apps"]' ).on( 'click', function( event ){
92
  event.preventDefault();
93
+ // We won't allow access to the apps tab if there are required updates
94
+ if( "1" !== nfAdmin.requiredUpdates ) {
95
+ window.location.hash = 'apps';
96
+ nfRadio.channel( 'dashboard' ).request( 'show:apps' );
97
+ }
98
 
99
  } );
100
 
101
  jQuery( 'a[href="admin.php?page=ninja-forms"]' ).on( 'click', function( event ){
102
  event.preventDefault();
103
+ // We won't allow access to the dashboard if there are required updates
104
+ if( "1" !== nfAdmin.requiredUpdates ) {
105
+ window.location.hash = 'forms';
106
+ nfRadio.channel( 'dashboard' ).request( 'show:widgets' );
107
+ nfRadio.channel( 'widget-forms' ).request( 'show:formsTable' );
108
+ }
109
  } );
110
 
111
  /**
client/dashboard/views/dashboardView.js CHANGED
@@ -6,7 +6,7 @@
6
  * @copyright (c) 2017 WP Ninjas
7
  * @since 3.2
8
  */
9
- define( [ 'views/sections/widgets.js', 'views/sections/services.js', 'views/sections/apps.js', 'views/sections/memberships.js', 'views/oauth.js', 'views/promotion.js' ], function( WidgetView, ServicesView, AppsView, MembershipsView, OAuthView, PromotionView ) {
10
  var view = Marionette.View.extend( {
11
  template: "#tmpl-nf-dashboard",
12
 
@@ -20,33 +20,49 @@ define( [ 'views/sections/widgets.js', 'views/sections/services.js', 'views/sect
20
 
21
  events: {
22
  'click .widgets a': function(e){
23
- this.showChildView( 'content', new WidgetView() );
24
- jQuery( '.' + this.currentView).find( 'a' ).removeClass( 'active' );
25
- e.target.classList.add( 'active' );
26
- this.currentView = 'widgets';
 
 
27
  },
28
  'click .services a': function(e){
29
- this.showChildView( 'content', new ServicesView() );
30
- jQuery( '.' + this.currentView).find( 'a' ).removeClass( 'active' );
31
- e.target.classList.add( 'active' );
32
- this.currentView = 'services';
 
 
33
  },
34
  'click .apps a': function(e){
35
- this.showChildView( 'content', new AppsView() );
36
- jQuery( '.' + this.currentView).find( 'a' ).removeClass( 'active' );
37
- e.target.classList.add( 'active' );
38
- this.currentView = 'apps';
 
 
39
  },
40
  'click .memberships a': function(e){
41
- this.showChildView( 'content', new MembershipsView() );
42
- jQuery( '.' + this.currentView).find( 'a' ).removeClass( 'active' );
43
- e.target.classList.add( 'active' );
44
- this.currentView = 'memberships';
 
 
45
  },
46
  },
47
 
48
  initialize: function() {
49
 
 
 
 
 
 
 
 
 
50
  switch( window.location.hash ) {
51
  case '#apps':
52
  this.currentView = 'apps';
@@ -57,6 +73,9 @@ define( [ 'views/sections/widgets.js', 'views/sections/services.js', 'views/sect
57
  case '#memberships':
58
  this.currentView = 'memberships';
59
  break;
 
 
 
60
  case '#widgets':
61
  default:
62
  this.currentView = 'widgets';
@@ -67,29 +86,43 @@ define( [ 'views/sections/widgets.js', 'views/sections/services.js', 'views/sect
67
  * TODO: Clean this up.
68
  */
69
  nfRadio.channel( 'dashboard' ).reply( 'show:widgets', function(){
70
- this.showChildView('content', new WidgetView() );
71
- jQuery( 'nav.sections a.active' ).removeClass( 'active' );
72
- jQuery( 'nav.sections .widgets a' ).addClass( 'active' );
73
- this.currentView = 'widgets';
 
 
74
  }, this );
75
  nfRadio.channel( 'dashboard' ).reply( 'show:services', function(){
76
- this.showChildView('content', new ServicesView() );
77
- jQuery( 'nav.sections a.active' ).removeClass( 'active' );
78
- jQuery( 'nav.sections .services a' ).addClass( 'active' );
79
- this.currentView = 'services';
 
 
80
  }, this );
81
  nfRadio.channel( 'dashboard' ).reply( 'show:apps', function(){
82
- this.showChildView('content', new AppsView() );
83
- jQuery( 'nav.sections a.active' ).removeClass( 'active' );
84
- jQuery( 'nav.sections .apps a' ).addClass( 'active' );
85
- this.currentView = 'apps';
 
 
86
  }, this );
87
  },
88
 
89
  onRender: function() {
90
 
91
  if( useServices ) this.showChildView( 'notices', new OAuthView() );
92
- if( useServices ) this.showChildView( 'promotions', new PromotionView() );
 
 
 
 
 
 
 
 
93
 
94
  switch( window.location.hash ) {
95
  case '#apps':
@@ -101,6 +134,9 @@ define( [ 'views/sections/widgets.js', 'views/sections/services.js', 'views/sect
101
  case '#services':
102
  var childView = new ServicesView();
103
  break;
 
 
 
104
  case '#widgets':
105
  default:
106
  var childView = new WidgetView();
@@ -242,42 +278,7 @@ define( [ 'views/sections/widgets.js', 'views/sections/services.js', 'views/sect
242
  } );
243
  } );
244
  } // If we've been told to run cleanup...
245
- else if ( '1' == nfAdmin.doingCleanup ) {
246
- // Get the context for later.
247
- var that = this;
248
- // Define our modal options.
249
- var modalData = {
250
- width: 450,
251
- closeOnClick: false,
252
- closeOnEsc: false,
253
- content: nfi18n.cleanupContent,
254
- useProgressBar: true,
255
- loadingText: nfi18n.cleanupLoading,
256
- btnSecondary: {
257
- text: nfi18n.cleanupSecondary,
258
- callback: function() {
259
- cleanupModal.toggleModal( false );
260
- }
261
- },
262
- btnPrimary: {
263
- text: nfi18n.cleanupPrimary,
264
- callback: function() {
265
- // Prevent the user from leaving without firing an alert.
266
- jQuery( window ).bind( 'beforeunload', function() {
267
- return 'Are you sure? Leaving before the process completes could cause damage to your data.';
268
- } );
269
- // Hide the buttons.
270
- cleanupModal.maybeShowActions( false );
271
- // Show the progress bar.
272
- cleanupModal.maybeShowProgress( true );
273
- // Begin our cleanup process.
274
- that.cleanupProcess( that, -1, cleanupModal );
275
- },
276
- },
277
- };
278
- // Setup our modal.
279
- var cleanupModal = new NinjaModal( modalData );
280
- }
281
  // If form telemetry is defined...
282
  // AND if we should run it...
283
  if ( 'undefined' !== typeof nfAdmin.formTelemetry && 1 == nfAdmin.formTelemetry ) {
@@ -310,56 +311,6 @@ define( [ 'views/sections/widgets.js', 'views/sections/services.js', 'views/sect
310
  },
311
  }
312
  },
313
-
314
- /**
315
- * Function to manage our data cleanup batch process response.
316
- *
317
- * @since 3.3.1
318
- *
319
- * @param context (this) The context at the time of function definition.
320
- * @param steps (int) The total number of steps in this process.
321
- * @param modal (jBox) A reference to the modal where this process is running.
322
- */
323
- cleanupProcess: function( context, steps, modal ) {
324
- var data = {
325
- action: 'nf_batch_process',
326
- batch_type: 'data_cleanup',
327
- security: nfAdmin.batchNonce
328
- };
329
- jQuery.post( ajaxurl, data, function( response ) {
330
- response = JSON.parse( response );
331
- // If we're done...
332
- if ( response.batch_complete ) {
333
- // Push our progress bar to 100%.
334
- modal.setProgress( 100 );
335
- // Allow the user to leave the page now.
336
- jQuery( window ).unbind( 'beforeunload' );
337
- modal.toggleModal( false );
338
- // Exit.
339
- return false;
340
- }
341
- // If we do not yet have a determined number of steps...
342
- if ( -1 == steps ) {
343
- // If step_toal is defined...
344
- if ( 'undefined' != typeof response.step_total ) {
345
- // Use the step_total.
346
- steps = response.step_total;
347
- } // Otherwise... (step_total is not defined)
348
- else {
349
- // Use step_remaining.
350
- steps = response.step_remaining;
351
- }
352
- }
353
- // Calculate our current step.
354
- var step = steps - response.step_remaining;
355
- // Calculate our maximum progress for this step.
356
- var maxProgress = Math.round( step / steps * 100 );
357
- // Increment the progress.
358
- modal.incrementProgress ( maxProgress );
359
- // Recall our function...
360
- context.cleanupProcess( context, steps, modal );
361
- } );
362
- }
363
  } );
364
  return view;
365
  } );
6
  * @copyright (c) 2017 WP Ninjas
7
  * @since 3.2
8
  */
9
+ define( [ 'views/sections/widgets.js', 'views/sections/services.js', 'views/sections/apps.js', 'views/sections/memberships.js', 'views/oauth.js', 'views/promotion.js', 'views/sections/requiredUpdates.js' ], function( WidgetView, ServicesView, AppsView, MembershipsView, OAuthView, PromotionView, RequiredUpdatesView ) {
10
  var view = Marionette.View.extend( {
11
  template: "#tmpl-nf-dashboard",
12
 
20
 
21
  events: {
22
  'click .widgets a': function(e){
23
+ if( "1" != nfAdmin.requiredUpdates ) {
24
+ this.showChildView( 'content', new WidgetView() );
25
+ jQuery( '.' + this.currentView).find( 'a' ).removeClass( 'active' );
26
+ e.target.classList.add( 'active' );
27
+ this.currentView = 'widgets';
28
+ }
29
  },
30
  'click .services a': function(e){
31
+ if( "1" != nfAdmin.requiredUpdates ) {
32
+ this.showChildView( 'content', new ServicesView() );
33
+ jQuery( '.' + this.currentView).find( 'a' ).removeClass( 'active' );
34
+ e.target.classList.add( 'active' );
35
+ this.currentView = 'services';
36
+ }
37
  },
38
  'click .apps a': function(e){
39
+ if( "1" != nfAdmin.requiredUpdates ) {
40
+ this.showChildView( 'content', new AppsView() );
41
+ jQuery( '.' + this.currentView).find( 'a' ).removeClass( 'active' );
42
+ e.target.classList.add( 'active' );
43
+ this.currentView = 'apps';
44
+ }
45
  },
46
  'click .memberships a': function(e){
47
+ if( "1" != nfAdmin.requiredUpdates ) {
48
+ this.showChildView( 'content', new MembershipsView() );
49
+ jQuery( '.' + this.currentView).find( 'a' ).removeClass( 'active' );
50
+ e.target.classList.add( 'active' );
51
+ this.currentView = 'memberships';
52
+ }
53
  },
54
  },
55
 
56
  initialize: function() {
57
 
58
+ if( "1" === nfAdmin.requiredUpdates ) {
59
+ // if we have required updates, redirect them
60
+ window.location.hash = '#requiredUpdates';
61
+ } else if ( '#requiredUpdates' === window.location.hash ) {
62
+ // if no updates, but someone hits update url, give the the dashboard
63
+ window.location.hash = '';
64
+ }
65
+
66
  switch( window.location.hash ) {
67
  case '#apps':
68
  this.currentView = 'apps';
73
  case '#memberships':
74
  this.currentView = 'memberships';
75
  break;
76
+ case '#requiredUpdates':
77
+ this.currentView = 'requiredUpdates';
78
+ break;
79
  case '#widgets':
80
  default:
81
  this.currentView = 'widgets';
86
  * TODO: Clean this up.
87
  */
88
  nfRadio.channel( 'dashboard' ).reply( 'show:widgets', function(){
89
+ if( "1" != nfAdmin.requiredUpdates ) {
90
+ this.showChildView('content', new WidgetView() );
91
+ jQuery( 'nav.sections a.active' ).removeClass( 'active' );
92
+ jQuery( 'nav.sections .widgets a' ).addClass( 'active' );
93
+ this.currentView = 'widgets';
94
+ }
95
  }, this );
96
  nfRadio.channel( 'dashboard' ).reply( 'show:services', function(){
97
+ if( "1" != nfAdmin.requiredUpdates ) {
98
+ this.showChildView('content', new ServicesView() );
99
+ jQuery( 'nav.sections a.active' ).removeClass( 'active' );
100
+ jQuery( 'nav.sections .services a' ).addClass( 'active' );
101
+ this.currentView = 'services';
102
+ }
103
  }, this );
104
  nfRadio.channel( 'dashboard' ).reply( 'show:apps', function(){
105
+ if( "1" != nfAdmin.requiredUpdates ) {
106
+ this.showChildView('content', new AppsView() );
107
+ jQuery( 'nav.sections a.active' ).removeClass( 'active' );
108
+ jQuery( 'nav.sections .apps a' ).addClass( 'active' );
109
+ this.currentView = 'apps';
110
+ }
111
  }, this );
112
  },
113
 
114
  onRender: function() {
115
 
116
  if( useServices ) this.showChildView( 'notices', new OAuthView() );
117
+ if( useServices && '1' !== nfAdmin.requiredUpdates ) {
118
+ this.showChildView( 'promotions', new PromotionView() );
119
+ }
120
+
121
+ // if no updates and someone hits the update url, give them the dashboard
122
+ if( '0' === nfAdmin.requiredUpdates
123
+ && '#requiredUpdates' === window.location.hash ) {
124
+ window.location.hash = '';
125
+ }
126
 
127
  switch( window.location.hash ) {
128
  case '#apps':
134
  case '#services':
135
  var childView = new ServicesView();
136
  break;
137
+ case '#requiredUpdates':
138
+ var childView = new RequiredUpdatesView();
139
+ break;
140
  case '#widgets':
141
  default:
142
  var childView = new WidgetView();
278
  } );
279
  } );
280
  } // If we've been told to run cleanup...
281
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
282
  // If form telemetry is defined...
283
  // AND if we should run it...
284
  if ( 'undefined' !== typeof nfAdmin.formTelemetry && 1 == nfAdmin.formTelemetry ) {
311
  },
312
  }
313
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
314
  } );
315
  return view;
316
  } );
client/dashboard/views/sections/requiredUpdates.js ADDED
@@ -0,0 +1,345 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @package Ninja Forms
3
+ * @subpackage Dashboard
4
+ * @copyright (c) 2017 WP Ninjas
5
+ * @since 3.2
6
+ */
7
+ define( [], function() {
8
+ var view = Marionette.View.extend( {
9
+ template: '#tmpl-nf-requiredUpdates',
10
+
11
+ updates: [], //an object containing updates to be performed
12
+
13
+ currentUpdate: 0, // current update out of totalUpdate
14
+
15
+ totalUpdates: -1, // we start with -1 and overwrite it
16
+
17
+ updatesRemaining: -1,// how many update are left
18
+
19
+ ui: {
20
+ requiredUpdates: '.nf-required-update',
21
+
22
+ },
23
+
24
+ /**
25
+ * When we render this section, check for updates
26
+ */
27
+ onRender: function() {
28
+ this.getRequiredUpdates();
29
+ },
30
+
31
+ /**
32
+ * Set up the initial on click for the button, as it changes after
33
+ * the updates are done
34
+ */
35
+ setButtonClickEvent: function() {
36
+ var that = this;
37
+
38
+ // tell the button to do updates on click
39
+ jQuery( '#nf-required-updates-btn' )
40
+ .off( 'click' )
41
+ .on( 'click', function( e ) {
42
+ e.preventDefault();
43
+ that.doRequiredUpdates();
44
+ jQuery( this ).hide();
45
+ } );
46
+ },
47
+
48
+ /**
49
+ * Get any required updates that might need to happen
50
+ */
51
+ getRequiredUpdates: function() {
52
+
53
+ var that = this;
54
+ jQuery.get( ajaxurl, { action: 'nf_required_update' } )
55
+ .then( function( response ) {
56
+ var res = JSON.parse( response );
57
+
58
+ if( 0 === res.errors.length) {
59
+ // get the number of updates
60
+ that.totalUpdates = res.data.updates.length;
61
+
62
+ // get the updates into an array
63
+ that.updates = res.data.updates;
64
+
65
+ if( 0 < that.updates.length ) {
66
+ that.requiredUpdates = that.updates.length;
67
+ // now that we have updates, let's make our table
68
+ that.constructUpdateTable();
69
+
70
+ // set up the click event for the button to do updates
71
+ that.setButtonClickEvent();
72
+ } else {
73
+ window.location = window.location.origin +
74
+ window.location.pathname + window.location.search;
75
+ }
76
+
77
+ }
78
+ } );
79
+ },
80
+
81
+ /**
82
+ * Construct a table that shows required updates
83
+ */
84
+ constructUpdateTable: function() {
85
+ var that = this;
86
+ // get the table and header
87
+ var updateTable = document.getElementById( 'nf-upgrades-table' );
88
+ var tableHeader = updateTable.getElementsByTagName( 'thead' )[0];
89
+
90
+ // create the header row
91
+ var headerRow = document.createElement( 'tr' );
92
+
93
+ // create the header cell for update name column
94
+ var updateNameHeaderCell = document.createElement( 'th' );
95
+ updateNameHeaderCell.innerHTML = "Update";
96
+ updateNameHeaderCell.classList.add( "nf-update-name-cell" );
97
+
98
+ // create header cell for progress bar column
99
+ var updateProgressHeaderCell = document.createElement( 'th' );
100
+ updateProgressHeaderCell.innerHTML = "Progress";
101
+ updateProgressHeaderCell.classList.add( "nf-update-progress-cell" );
102
+
103
+ // append header cells to header row
104
+ headerRow.appendChild( updateNameHeaderCell );
105
+ headerRow.appendChild( updateProgressHeaderCell );
106
+
107
+ // append header row to table header
108
+ tableHeader.appendChild( headerRow );
109
+
110
+ // get the table body
111
+ var tableBody = updateTable.getElementsByTagName( 'tbody' )[0];
112
+
113
+ // create a table row for each required update
114
+ jQuery.each( this.updates, function( i, update ) {
115
+ var tableRow = document.createElement( 'tr' );
116
+
117
+ var updateNameCell = document.createElement( 'td' );
118
+ updateNameCell.innerHTML = update.nicename;
119
+
120
+ var updateProgressCell = document.createElement( 'td' );
121
+ var updateProgressBar = document.createElement( 'div' );
122
+
123
+ updateProgressBar.id = "update-progress-" + i;
124
+
125
+ var newProgressBar = that.createNewProgressBar( i );
126
+
127
+ updateProgressBar.appendChild( newProgressBar );
128
+
129
+ updateProgressCell.appendChild( updateProgressBar );
130
+
131
+ tableRow.appendChild( updateNameCell );
132
+ tableRow.appendChild( updateProgressCell );
133
+
134
+ tableBody.appendChild( tableRow );
135
+ } );
136
+
137
+ var updateBtn = document.getElementById( 'nf-required-updates-btn' );
138
+ updateBtn.style.display = 'block';
139
+
140
+ },
141
+
142
+ /**
143
+ * Function that starts running required updates if we have any
144
+ */
145
+
146
+ doRequiredUpdates: function() {
147
+ // set the window.location hash just in case
148
+ window.location.hash = '#requiredUpdates';
149
+
150
+ var context = this;
151
+
152
+ // disable the button once we've clicked
153
+ jQuery( '#nf-required-updates-btn' ).addClass( 'disabled' ).attr( 'disabled', 'disabled' );
154
+
155
+ //make initial call to initiate required updates
156
+ jQuery.post( ajaxurl, { action: 'nf_required_update', security: nfAdmin.updateNonce } )
157
+ .then( function( response ) {
158
+ var res = JSON.parse( response );
159
+
160
+ // if we still have updates remaining, call the ajax again
161
+ if( res.updatesRemaining > 0 ) {
162
+
163
+ /**
164
+ * We had to add this if/else b/c the classes were returning
165
+ * results where the currentStep and stepsTotal values
166
+ * were the same, but the updatesRemaining value had changed,
167
+ * thus causing any progress bars after the first to
168
+ * automatically show 100% even though the updates continue
169
+ */
170
+ if( context.updatesRemaining !== res.updatesRemaining
171
+ && res.currentStep === res.stepsTotal ) {
172
+
173
+ // finish the current update
174
+ context.finishUpdate( context.currentUpdate );
175
+
176
+ // update the remaining updates
177
+ context.updatesRemaining = res.updatesRemaining;
178
+ } else {
179
+ // this will show progress bars that are processing
180
+ context.showProgressBars( res );
181
+
182
+ // update the remaining updates
183
+ context.updatesRemaining = res.updatesRemaining;
184
+ }
185
+
186
+ // keep moving through required updates
187
+ context.doRequiredUpdates();
188
+ } else {
189
+
190
+ // finish the current update
191
+ context.finishUpdate( context.currentUpdate );
192
+
193
+ // set the globle required updates variable to 0
194
+ nfAdmin.requiredUpdates = 0;
195
+
196
+ // remove the disabled items and set the click to the dashboard
197
+ jQuery( '#nf-required-updates-btn' )
198
+ .removeClass( 'disabled' )
199
+ .removeAttr( 'disabled' )
200
+ .val( 'Go To Dashboard' )
201
+ .off( 'click' )
202
+ .on( 'click', function( e ) {
203
+ e.preventDefault();
204
+ window.location = window.location.origin +
205
+ window.location.pathname + window.location.search;
206
+ } )
207
+ .show();
208
+
209
+ console.log( "UPDATES DONE" );
210
+ }
211
+ });
212
+ },
213
+
214
+ /**
215
+ * Function create and display progress bars.
216
+ * We create one for each update
217
+ *
218
+ * @param data
219
+ */
220
+ showProgressBars: function( data ) {
221
+ var update = this.totalUpdates - data.updatesRemaining;
222
+ var progress = data.currentStep;
223
+ var totalSteps = data.stepsTotal;
224
+
225
+ // get the progress bar we are dealing with
226
+ var currentProgressBar = document.getElementById( 'nf_progressBar_' + update );
227
+
228
+ if( null == currentProgressBar ) {
229
+ // if the element requested is null, then we know this is a new update
230
+ this.currentUpdate += 1;
231
+ if( 1 === this.currentUpdate && -1 === this.totalUpdates ) {
232
+ // the initial 'update' value with be how many remaining(total updates)
233
+ this.totalUpdates = update;
234
+ }
235
+ // create a new progress bar if it doesn't exist
236
+ currentProgressBar = this.createNewProgressBar( update );
237
+ }
238
+
239
+ // update the progress bar
240
+ this.incrementProgress( update, progress, totalSteps)
241
+ },
242
+
243
+ /**
244
+ * Create a new progress bar for the new update
245
+ *
246
+ * @param update
247
+ *
248
+ * @returns newProgressBarContainer
249
+ */
250
+ createNewProgressBar: function( update ) {
251
+ //create new container
252
+ var newProgressBarContainer = document.createElement( 'div' );
253
+ newProgressBarContainer.id = 'nf_progressBar_' + update;
254
+ newProgressBarContainer.classList.add( 'jBox-content' );
255
+ newProgressBarContainer.style.display = 'none';
256
+
257
+ // create new progress bar
258
+ var newProgressBar = document.createElement( 'div' );
259
+ newProgressBar.classList.add( 'nf-progress-bar' );
260
+
261
+ // create the slider
262
+ var newProgressSlider = document.createElement( 'div' );
263
+ newProgressSlider.id = 'nf-progress-bar-slider-' + update;
264
+ newProgressSlider.classList.add( 'nf-progress-bar-slider' );
265
+
266
+ // append the slider to the progress bar
267
+ newProgressBar.appendChild( newProgressSlider );
268
+
269
+ // append the progress bar to the container
270
+ newProgressBarContainer.appendChild( newProgressBar );
271
+
272
+ return newProgressBarContainer;
273
+ },
274
+
275
+
276
+ /**
277
+ * Increment the progress based on total steps and current progress
278
+ *
279
+ * @param update
280
+ * @param progress
281
+ * @param totalSteps
282
+ */
283
+ incrementProgress: function( update, currentStep, totalSteps ) {
284
+ var progressBarContainer = document.getElementById( 'nf_progressBar_' + update );
285
+ progressBarContainer.style.display = 'block';
286
+
287
+ // get the slider element
288
+ var progressBar = document.getElementById( 'nf-progress-bar-slider-' + update );
289
+
290
+ // get the current progress(%) based on total steps and currentStep
291
+ var newValue = ( Number( currentStep ) / Number( totalSteps ) ) * 100;
292
+
293
+ // Get our current progress.
294
+ var currentProgress = progressBar.offsetWidth / progressBar.parentElement.offsetWidth * 100;
295
+
296
+ // If the new value is greater than the currentProgress, update it
297
+ if ( newValue > currentProgress ) {
298
+ this.setProgress( update, newValue );
299
+ }
300
+ },
301
+
302
+ /**
303
+ * Sets the current progress for the current progress bar
304
+ *
305
+ * @param update
306
+ * @param percent
307
+ */
308
+ setProgress: function( update, percent ) {
309
+ // Update the width of the element as a percentage.
310
+ var progressBar = document.getElementById( 'nf-progress-bar-slider-' + update );
311
+ progressBar.style.width = percent + '%';
312
+
313
+ if( 100 <= percent ) {
314
+ this.finishUpdate( update );
315
+ }
316
+ },
317
+
318
+ /**
319
+ * If an update is done, then let's set it to done in the updates table
320
+ */
321
+ finishUpdate: function( update ) {
322
+ // get the progress bar container of the one that is done
323
+ var progressBarContainer = document.getElementById( 'nf_progressBar_' + update );
324
+
325
+ // get it's parent table cell
326
+ var progressCell = progressBarContainer.parentNode;
327
+
328
+ // remove the progress bar element
329
+ progressCell.removeChild( progressBarContainer );
330
+
331
+ // add the span with 'Done' check mark
332
+ var finishedSpan = document.createElement( 'span' );
333
+ finishedSpan.classList.add( 'dashicons' );
334
+ finishedSpan.classList.add( 'dashicons-yes' );
335
+
336
+ // append it to the progress cell
337
+ progressCell.appendChild( finishedSpan );
338
+
339
+ // update the current update
340
+ this.currentUpdate = this.currentUpdate + 1;
341
+
342
+ }
343
+ } );
344
+ return view;
345
+ } );
client/dashboard/views/widgets/forms/forms.js CHANGED
@@ -25,8 +25,8 @@ define( [
25
  },
26
 
27
  initialize: function(){
28
- nfRadio.channel( 'widget-forms' ).reply( 'show:newFormsGrid', this.showNewFormGrid, this );
29
- nfRadio.channel( 'widget-forms' ).reply( 'show:formsTable', this.showFormsTable, this );
30
  },
31
 
32
  onRender: function() {
@@ -57,7 +57,6 @@ define( [
57
  },
58
 
59
  showFormsTable: function(){
60
- console.log( this );
61
  window.location.hash = 'forms';
62
  this.showChildView( 'content', new FormsTableView() );
63
  nfRadio.channel( 'widget-forms' ).trigger( 'change:content' );
25
  },
26
 
27
  initialize: function(){
28
+ // nfRadio.channel( 'widget-forms' ).reply( 'show:newFormsGrid', this.showNewFormGrid, this );
29
+ // nfRadio.channel( 'widget-forms' ).reply( 'show:formsTable', this.showFormsTable, this );
30
  },
31
 
32
  onRender: function() {
57
  },
58
 
59
  showFormsTable: function(){
 
60
  window.location.hash = 'forms';
61
  this.showChildView( 'content', new FormsTableView() );
62
  nfRadio.channel( 'widget-forms' ).trigger( 'change:content' );
client/dashboard/views/widgets/forms/newFormTemplate.js CHANGED
@@ -21,23 +21,41 @@ define( [], function() {
21
  * @return {void}
22
  */
23
  maybeOpenModal: function( e ) {
24
- // If this isn't an ad, then early return
25
- if ( 'ad' != this.model.get( 'type' ) ) {
26
- return true;
27
- }
28
- // Prevent page navigation.
29
- e.preventDefault();
 
 
 
 
 
30
 
31
- // Open our jBox modal
32
- var modal = new jBox( 'Modal', {
33
- width: 450,
34
- title: this.model.get( 'modal-title' ),
35
- content: this.model.get( 'modal-content' ),
36
- closeButton: 'box',
37
- blockScroll: true
38
- } );
 
 
 
39
 
40
- modal.open();
 
 
 
 
 
 
 
 
 
 
41
  }
42
 
43
  } );
21
  * @return {void}
22
  */
23
  maybeOpenModal: function( e ) {
24
+ e.preventDefault();
25
+ // If this is an ad, open the ad modal.
26
+ if ( 'ad' == this.model.get( 'type' ) ) {
27
+ // Open our jBox modal
28
+ var modal = new jBox( 'Modal', {
29
+ width: 450,
30
+ title: this.model.get( 'modal-title' ),
31
+ content: this.model.get( 'modal-content' ),
32
+ closeButton: 'box',
33
+ blockScroll: true
34
+ } );
35
 
36
+ modal.open();
37
+ } else { // This is a template, so import it using the batch processor.
38
+ // Settings object for our batch processor
39
+ var settings = {
40
+ // Batch processor slug. Must match what we have set in our PHP settings array.
41
+ batch_type: 'import_form_template',
42
+ loadingText: 'Importing...',
43
+ extraData: { template: this.model.get( 'id' ) },
44
+ onCompleteCallback: function( response ) {
45
+ // Bail if we don't return a form ID.
46
+ if ( 'undefined' == typeof response.form_id ) return false;
47
 
48
+ window.location.href = nfAdmin.builderURL + response.form_id;
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Instantiate our batch processor.
54
+ *
55
+ * This will open the modal and present the user with content.
56
+ */
57
+ new NinjaBatchProcessor( settings );
58
+ }
59
  }
60
 
61
  } );
deprecated/ninja-forms.php CHANGED
@@ -265,7 +265,7 @@ class Ninja_Forms {
265
 
266
  // Plugin version
267
  if ( ! defined( 'NF_PLUGIN_VERSION' ) )
268
- define( 'NF_PLUGIN_VERSION', '3.3.21.3' );
269
 
270
  // Plugin Folder Path
271
  if ( ! defined( 'NF_PLUGIN_DIR' ) )
265
 
266
  // Plugin version
267
  if ( ! defined( 'NF_PLUGIN_VERSION' ) )
268
+ define( 'NF_PLUGIN_VERSION', '3.4.0' );
269
 
270
  // Plugin Folder Path
271
  if ( ! defined( 'NF_PLUGIN_DIR' ) )
includes/AJAX/Controllers/Form.php CHANGED
@@ -12,6 +12,7 @@ class NF_AJAX_Controllers_Form extends NF_Abstracts_Controller
12
  add_action( 'wp_ajax_nopriv_nf_ajax_get_new_nonce', array( $this, 'get_new_nonce' ) );
13
  add_action( 'wp_ajax_nf_save_form', array( $this, 'save' ) );
14
  add_action( 'wp_ajax_nf_delete_form', array( $this, 'delete' ) );
 
15
  }
16
 
17
  public function plugins_loaded()
@@ -141,6 +142,21 @@ class NF_AJAX_Controllers_Form extends NF_Abstracts_Controller
141
  $this->_respond();
142
  }
143
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  /**
145
  * Let's generate a unique nonce for each form render so that we don't get
146
  * caught with an expiring nonce accidentally and fail to allow a submission
12
  add_action( 'wp_ajax_nopriv_nf_ajax_get_new_nonce', array( $this, 'get_new_nonce' ) );
13
  add_action( 'wp_ajax_nf_save_form', array( $this, 'save' ) );
14
  add_action( 'wp_ajax_nf_delete_form', array( $this, 'delete' ) );
15
+ add_action( 'wp_ajax_nf_remove_maintenance_mode', array( $this, 'remove_maintenance_mode' ) );
16
  }
17
 
18
  public function plugins_loaded()
142
  $this->_respond();
143
  }
144
 
145
+ /**
146
+ * This function will take all form out of maintenance mode( in case some
147
+ * are still in maintenance mode after some required updates )
148
+ *
149
+ * @since 3.4.0
150
+ */
151
+ public function remove_maintenance_mode() {
152
+
153
+ check_ajax_referer( 'ninja_forms_settings_nonce', 'security' );
154
+
155
+ WPN_Helper::set_forms_maintenance_mode();
156
+
157
+ $this->_respond();
158
+ }
159
+
160
  /**
161
  * Let's generate a unique nonce for each form render so that we don't get
162
  * caught with an expiring nonce accidentally and fail to allow a submission
includes/AJAX/Controllers/Submission.php CHANGED
@@ -57,6 +57,19 @@ class NF_AJAX_Controllers_Submission extends NF_Abstracts_Controller
57
  $this->_respond();
58
  }
59
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  if( $this->is_preview() ) {
61
 
62
  $this->_form_cache = get_user_option( 'nf_form_preview_' . $this->_form_id );
57
  $this->_respond();
58
  }
59
 
60
+ // Check to see if our form is maintenance mode.
61
+ $is_maintenance = WPN_Helper::form_in_maintenance( $this->_form_id );
62
+
63
+ /*
64
+ * If our form is in maintenance mode then, stop processing and throw an error with a link
65
+ * back to the form.
66
+ */
67
+ if ( $is_maintenance ) {
68
+ $this->_errors[ 'form' ][] = apply_filters( 'nf_maintenance_message', __( 'This form is currently undergoing maintenance. Please ', 'ninja-forms' )
69
+ . '<a href="' . $_SERVER[ 'HTTP_REFERER' ] . '">' . __( 'click here ', 'ninja-forms' ) . '</a>' . __( 'to reload the form and try again.', 'ninja-forms' ) ) ;
70
+ $this->_respond();
71
+ }
72
+
73
  if( $this->is_preview() ) {
74
 
75
  $this->_form_cache = get_user_option( 'nf_form_preview_' . $this->_form_id );
includes/AJAX/REST/BatchProcess.php CHANGED
@@ -22,27 +22,19 @@ class NF_AJAX_REST_BatchProcess extends NF_AJAX_REST_Controller
22
  if ( ! isset( $request_data[ 'security' ] ) || ! wp_verify_nonce( $request_data[ 'security' ], 'ninja_forms_batch_nonce' ) ) {
23
  // Kick the request out now.
24
  $data[ 'error' ] = __( 'Request forbidden.', 'ninja-forms' );
 
25
  }
 
26
  // If we have a batch type...
27
  if ( isset( $request_data[ 'batch_type' ]) ){
28
  $batch_type = $request_data[ 'batch_type' ];
29
- // Route the request to the proper controller.
30
- switch ( $batch_type ) {
31
- case 'chunked_publish':
32
- $batch = new NF_Admin_Processes_ChunkPublish(
33
- $request_data );
34
- break;
35
- case 'data_cleanup':
36
- $batch = new NF_Admin_Processes_DataCleanup(
37
- $request_data );
38
- break;
39
- case 'expired_submission_cleanup':
40
- $batch = new NF_Admin_Processes_ExpiredSubmissionCleanup(
41
- $request_data );
42
- break;
43
- default:
44
- $data[ 'error' ] = __( 'Invalid request.', 'ninja-forms' );
45
- break;
46
  }
47
  } // Otherwise... (We don't have a batch type.)
48
  else {
22
  if ( ! isset( $request_data[ 'security' ] ) || ! wp_verify_nonce( $request_data[ 'security' ], 'ninja_forms_batch_nonce' ) ) {
23
  // Kick the request out now.
24
  $data[ 'error' ] = __( 'Request forbidden.', 'ninja-forms' );
25
+ return $data;
26
  }
27
+
28
  // If we have a batch type...
29
  if ( isset( $request_data[ 'batch_type' ]) ){
30
  $batch_type = $request_data[ 'batch_type' ];
31
+ $batch_processes = Ninja_Forms()->config( 'BatchProcesses' );
32
+
33
+ if ( isset ( $batch_processes[ $batch_type ][ 'class_name' ] ) ) {
34
+ $batch_class = $batch_processes[ $batch_type ][ 'class_name' ];
35
+ $batch = new $batch_class( $request_data );
36
+ } else {
37
+ $data[ 'error' ] = __( 'Invalid request.', 'ninja-forms' );
 
 
 
 
 
 
 
 
 
 
38
  }
39
  } // Otherwise... (We don't have a batch type.)
40
  else {
includes/AJAX/REST/NewFormTemplates.php CHANGED
@@ -13,10 +13,10 @@ class NF_AJAX_REST_NewFormTemplates extends NF_AJAX_REST_Controller
13
  $templates = Ninja_Forms()->config( 'NewFormTemplates' );
14
  usort( $templates, array( $this, 'cmp' ) );
15
  array_unshift( $templates, array(
16
- 'id' => 'new',
17
- 'title' => __( 'Blank Form', 'ninja-forms' ),
18
  'template-desc' => __( 'The blank form allows you to create any type of form using our drag & drop builder.', 'ninja-forms' ),
19
- 'type' => 'default'
20
  ) );
21
  return array_values( $templates ); // Remove keys so that the JSON is an array.
22
  }
13
  $templates = Ninja_Forms()->config( 'NewFormTemplates' );
14
  usort( $templates, array( $this, 'cmp' ) );
15
  array_unshift( $templates, array(
16
+ 'id' => 'new',
17
+ 'title' => __( 'Blank Form', 'ninja-forms' ),
18
  'template-desc' => __( 'The blank form allows you to create any type of form using our drag & drop builder.', 'ninja-forms' ),
19
+ 'type' => 'default'
20
  ) );
21
  return array_values( $templates ); // Remove keys so that the JSON is an array.
22
  }
includes/AJAX/REST/RequiredUpdate.php ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit;
2
+
3
+ class NF_AJAX_REST_RequiredUpdate extends NF_AJAX_REST_Controller
4
+ {
5
+ private $updates = array();
6
+ private $running = array();
7
+
8
+ protected $action = 'nf_required_update';
9
+ public function __construct()
10
+ {
11
+ parent::__construct();
12
+ }
13
+
14
+ /**
15
+ * POST
16
+ * @param array $request_data [ int $clone_id ]
17
+ * @return array $data [ int $new_form_id ]
18
+ */
19
+ public function post( $request_data )
20
+ {
21
+ $data = array();
22
+
23
+ // If we don't have a nonce...
24
+ // OR if the nonce is invalid...
25
+ if ( ! isset( $request_data[ 'security' ] ) || ! wp_verify_nonce( $request_data[ 'security' ], 'ninja_forms_required_update_nonce' ) ) {
26
+ // Kick the request out now.
27
+ $data[ 'error' ] = __( 'Request forbidden.', 'ninja-forms' );
28
+ return $data;
29
+ }
30
+ $doing_updates = get_option( 'ninja_forms_doing_required_updates' );
31
+ // If we're not already doing updates...
32
+ if ( ! $doing_updates ) {
33
+ // Get our list of already run updates.
34
+ $processed = get_option( 'ninja_forms_required_updates', array() );
35
+ // Get our list of updates to run.
36
+ $this->updates = Ninja_Forms()->config( 'RequiredUpdates' );
37
+ // Sort our updates.
38
+ $this->running = $this->sort_updates( $this->updates, $processed );
39
+ } // Otherwise... (We are already processing updates.)
40
+ else {
41
+ $this->running = $doing_updates;
42
+ }
43
+ // Call the class of our current update.
44
+ $class = $this->running[ 0 ][ 'class_name' ];
45
+ $update_class = new $class( $request_data, $this->running );
46
+ }
47
+
48
+ /**
49
+ * GET
50
+ * @param $request_data (Array)
51
+ * @return $data (Array)
52
+ *
53
+ * @since 3.4.0
54
+ */
55
+ public function get( $request_data ) {
56
+
57
+ $data = array();
58
+ $data[ 'updates' ] = array();
59
+
60
+ // Get our list of already run updates.
61
+ $processed = get_option( 'ninja_forms_required_updates', array() );
62
+ // Get our list of updates yet to be run.
63
+ $this->updates = Ninja_Forms()->config( 'RequiredUpdates' );
64
+ // Sort our updates.
65
+ $sorted = $this->sort_updates( $this->updates, $processed );
66
+ $data[ 'updates' ] = $sorted;
67
+ return $data;
68
+ }
69
+
70
+ protected function get_request_data()
71
+ {
72
+ $request_data = array();
73
+
74
+ if( isset( $_REQUEST[ 'data' ] ) && $_REQUEST[ 'data' ] ){
75
+ $request_data[ 'data' ] = $_REQUEST[ 'data' ];
76
+ }
77
+
78
+ if( isset( $_REQUEST[ 'security' ] ) && $_REQUEST[ 'security' ] ){
79
+ $request_data[ 'security' ] = $_REQUEST[ 'security' ];
80
+ }
81
+
82
+ if( isset( $_REQUEST[ 'action' ] ) && $_REQUEST[ 'action' ] ){
83
+ $request_data[ 'action' ] = $_REQUEST[ 'action' ];
84
+ }
85
+
86
+ return $request_data;
87
+ }
88
+
89
+ /**
90
+ * Function to get the list of updates that need to run.
91
+ *
92
+ * @param $processed (Array) The list of updates that have already run on this install.
93
+ * @return Array
94
+ */
95
+ private function get_current_updates( $processed ) {
96
+ $updates = array();
97
+ // For each update in the list...
98
+ foreach ( $this->updates as $slug => $update ) {
99
+ // If we've not already processed it...
100
+ if ( ! isset( $processed[ $slug ] ) ) {
101
+ // Add it to our list.
102
+ $updates[ $slug ] = $update;
103
+ }
104
+ }
105
+ return $updates;
106
+ }
107
+
108
+ /**
109
+ * Function to sort the updates to be run.
110
+ *
111
+ * @param $current (Array) The list of updates to be run.
112
+ * @param $previous (Array) The list of updates that have already been run.
113
+ * @return Array
114
+ */
115
+ private function sort_updates( $current, $previous ) {
116
+ $sorted = array();
117
+ $queue = array();
118
+ // While we have not finished sorting updates...
119
+ while ( count( $sorted ) < count( $current ) ) {
120
+ // For each update we wish to run...
121
+ foreach ( $current as $slug => $update ) {
122
+ // Migrate the slug to a property.
123
+ $update[ 'slug' ] = $slug;
124
+ // If we've not already added this to the sorted list...
125
+ if ( ! in_array( $update, $sorted ) ) {
126
+ // If it has requirements...
127
+ if ( ! empty( $update[ 'requires' ] ) ) {
128
+ $enqueued = 0;
129
+ // For each requirement...
130
+ foreach ( $update[ 'requires' ] as $requirement ) {
131
+ // If the requirement doesn't exist...
132
+ if ( ! isset( $this->updates[ $requirement ] ) ) {
133
+ // unset the update b/c we are missing requirements
134
+ unset( $current[ $slug ] );
135
+ unset( $this->updates[ $slug ] );
136
+ }
137
+ // If the requirement has already been added to the stack...
138
+ if ( in_array( $requirement, $queue ) ) {
139
+ $enqueued++;
140
+ } // OR If the requirement has already been processed...
141
+ elseif ( isset( $previous[ $requirement ] ) ) {
142
+ $enqueued++;
143
+ }
144
+ }
145
+ // If all requirement are met...
146
+ if ( $enqueued == count( $update[ 'requires' ] ) ) {
147
+ // Add it to the list.
148
+ array_push( $sorted, $update );
149
+ // Record that we enqueued it.
150
+ array_push( $queue, $slug );
151
+ }
152
+ } // Otherwise... (It has no requirements.)
153
+ else {
154
+ // Add it to the list.
155
+ array_push( $sorted, $update );
156
+ // Record that we enqueued it.
157
+ array_push( $queue, $slug );
158
+ }
159
+ }
160
+ }
161
+ }
162
+ return $sorted;
163
+ }
164
+
165
+ }
includes/Abstracts/BatchProcess.php CHANGED
@@ -5,6 +5,15 @@
5
  */
6
  abstract class NF_Abstracts_BatchProcess
7
  {
 
 
 
 
 
 
 
 
 
8
 
9
  /**
10
  * Constructor
@@ -14,11 +23,49 @@ abstract class NF_Abstracts_BatchProcess
14
  //Bail if we aren't in the admin.
15
  if ( ! is_admin() )
16
  return false;
 
 
 
 
 
 
 
 
 
 
 
17
  }
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
  /**
21
  * Function to loop over the batch.
 
 
 
22
  */
23
  public function process()
24
  {
@@ -27,9 +74,11 @@ abstract class NF_Abstracts_BatchProcess
27
  */
28
  }
29
 
30
-
31
  /**
32
  * Function to run any setup steps necessary to begin processing.
 
 
 
33
  */
34
  public function startup()
35
  {
@@ -38,9 +87,37 @@ abstract class NF_Abstracts_BatchProcess
38
  */
39
  }
40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
  /**
43
  * Function to cleanup any lingering temporary elements of a batch process after completion.
 
 
 
44
  */
45
  public function cleanup()
46
  {
@@ -49,4 +126,53 @@ abstract class NF_Abstracts_BatchProcess
49
  */
50
  }
51
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  }
5
  */
6
  abstract class NF_Abstracts_BatchProcess
7
  {
8
+ protected $_db;
9
+
10
+ /**
11
+ * Array that holds data we're sending back to the JS front-end.
12
+ * @var array
13
+ */
14
+ protected $response = array(
15
+ 'batch_complete' => false
16
+ );
17
 
18
  /**
19
  * Constructor
23
  //Bail if we aren't in the admin.
24
  if ( ! is_admin() )
25
  return false;
26
+
27
+ global $wpdb;
28
+
29
+ /**
30
+ * Set $_db to $wpdb.
31
+ * This helps us by not requiring us to declare global $wpdb in every class method.
32
+ */
33
+ $this->_db = $wpdb;
34
+
35
+ // Run init.
36
+ $this->init();
37
  }
38
 
39
+ /**
40
+ * Decides whether we need to run startup or restart and then calls processing.
41
+ *
42
+ * @since 3.4.0
43
+ * @return void
44
+ */
45
+ public function init()
46
+ {
47
+ if ( ! get_option( 'nf_doing_' . $this->_slug ) ) {
48
+ // Run the startup process.
49
+ $this->startup();
50
+ } else {
51
+ // Otherwise... (We've already run startup.)
52
+ $this->restart();
53
+ }
54
+
55
+ // Determine how many steps this will take.
56
+ $this->response[ 'step_total' ] = $this->get_steps();
57
+
58
+ add_option( 'nf_doing_' . $this->_slug, true );
59
+
60
+ // Run processing
61
+ $this->process();
62
+ }
63
 
64
  /**
65
  * Function to loop over the batch.
66
+ *
67
+ * @since 3.4.0
68
+ * @return void
69
  */
70
  public function process()
71
  {
74
  */
75
  }
76
 
 
77
  /**
78
  * Function to run any setup steps necessary to begin processing.
79
+ *
80
+ * @since 3.4.0
81
+ * @return void
82
  */
83
  public function startup()
84
  {
87
  */
88
  }
89
 
90
+ /**
91
+ * Function to run any setup steps necessary to begin processing for steps after the first.
92
+ *
93
+ * @since 3.4.0
94
+ * @return void
95
+ */
96
+ public function restart()
97
+ {
98
+ /**
99
+ * This function intentionally left empty.
100
+ */
101
+ }
102
+
103
+ /**
104
+ * Returns how many steps we have in this process.
105
+ *
106
+ * If this method isn't overwritten by a child, it defaults to 1.
107
+ *
108
+ * @since 3.4.0
109
+ * @return int
110
+ */
111
+ public function get_steps()
112
+ {
113
+ return 1;
114
+ }
115
 
116
  /**
117
  * Function to cleanup any lingering temporary elements of a batch process after completion.
118
+ *
119
+ * @since 3.4.0
120
+ * @return void
121
  */
122
  public function cleanup()
123
  {
126
  */
127
  }
128
 
129
+ /**
130
+ * Method called when we are finished with this process.
131
+ *
132
+ * Deletes our "doing" option.
133
+ * Set's our response 'batch_complete' to true.
134
+ * Runs cleanup().
135
+ * Responds to the JS front-end.
136
+ *
137
+ * @since 3.4.0
138
+ * @return void
139
+ */
140
+ public function batch_complete()
141
+ {
142
+ // Delete our options.
143
+ delete_option( 'nf_doing_' . $this->_slug );
144
+ // Tell our JS that we're done.
145
+ $this->response[ 'batch_complete' ] = true;
146
+
147
+ $this->cleanup();
148
+ $this->respond();
149
+ }
150
+
151
+ /**
152
+ * Method that immediately moves on to the next step.
153
+ *
154
+ * Used in child methods to stop processing the current step an dmove to the next.
155
+ *
156
+ * @since 3.4.0
157
+ * @return void
158
+ */
159
+ public function next_step()
160
+ {
161
+ // ..see how many steps we have left, update our option, and send the remaining step to the JS.
162
+ $this->response[ 'step_remaining' ] = $this->get_steps();
163
+ $this->respond();
164
+ }
165
+
166
+ /**
167
+ * Method that encodes $this->response and sends the data to the front-end.
168
+ *
169
+ * @since 3.4.0
170
+ * @return void
171
+ */
172
+ public function respond()
173
+ {
174
+ echo wp_json_encode( $this->response );
175
+ wp_die();
176
+ }
177
+
178
  }
includes/Abstracts/Migration.php CHANGED
@@ -10,63 +10,121 @@ abstract class NF_Abstracts_Migration
10
 
11
  public $flag = '';
12
 
 
 
 
 
 
 
 
 
 
13
  public function __construct( $table_name, $flag )
14
  {
15
  $this->table_name = $table_name;
16
  }
17
 
 
 
 
 
 
 
 
 
18
  public function table_name()
19
  {
20
  global $wpdb;
21
  return $wpdb->prefix . $this->table_name;
22
  }
23
 
24
- public function charset_collate()
 
 
 
 
 
 
 
 
 
 
25
  {
26
  global $wpdb;
27
- // If our mysql version is 5.5.3 or higher...
28
- if ( version_compare( $wpdb->db_version(), '5.5.3', '>=' ) ) {
29
- // We can use mb4.
30
- return 'DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci';
31
- } // Otherwise...
32
- else {
33
- // We use standard utf8.
34
- return 'DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci';
35
- }
36
  }
37
 
 
38
  /**
39
- * Function to get the column collate for ALTER TABLE statements.
40
  *
41
- * @since 3.3.7
42
  *
43
- * @return string
 
 
 
44
  */
45
- public function collate()
46
  {
 
47
  global $wpdb;
48
  // If our mysql version is 5.5.3 or higher...
49
  if ( version_compare( $wpdb->db_version(), '5.5.3', '>=' ) ) {
50
  // We can use mb4.
51
- return 'COLLATE utf8mb4_general_ci';
52
  } // Otherwise...
53
  else {
54
  // We use standard utf8.
55
- return 'COLLATE utf8_general_ci';
56
- }
57
-
 
 
 
 
 
58
  }
59
-
60
  /**
61
- * Function to run our stage one db updates.
 
 
 
 
62
  */
63
- public function _stage_one()
64
  {
65
- if ( method_exists( $this, 'do_stage_one' ) ) {
66
- $this->do_stage_one();
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  }
68
  }
69
 
 
 
 
 
 
 
70
  public function _run()
71
  {
72
  // Check the flag
@@ -79,17 +137,38 @@ abstract class NF_Abstracts_Migration
79
  update_option( $this->flag, TRUE );
80
  }
81
 
 
 
 
 
 
 
82
  protected abstract function run();
83
 
 
 
 
 
 
 
84
  public function _drop()
85
  {
86
  global $wpdb;
 
87
  if( ! $this->table_name ) return;
 
88
  if( 0 == $wpdb->query( $wpdb->prepare( "SHOW TABLES LIKE '%s'", $this->table_name() ) ) ) return;
 
89
  $wpdb->query( "DROP TABLE " . $this->table_name() );
90
  return $this->drop();
91
  }
92
 
 
 
 
 
 
 
93
  protected function drop()
94
  {
95
  // This section intentionally left blank.
10
 
11
  public $flag = '';
12
 
13
+
14
+ /**
15
+ * Constructor method for the NF_Abstracts_Migration class.
16
+ *
17
+ * @param $table_name (String) The database table name managed by the extension.
18
+ * @param $flag (String) The wp option set to determine if this migration has already been run.
19
+ *
20
+ * @since 3.0.0
21
+ */
22
  public function __construct( $table_name, $flag )
23
  {
24
  $this->table_name = $table_name;
25
  }
26
 
27
+
28
+ /**
29
+ * Function to retrieve the full table name of the extension.
30
+ *
31
+ * @return (String) The full table name, including database prefix.
32
+ *
33
+ * @since 3.0.28
34
+ */
35
  public function table_name()
36
  {
37
  global $wpdb;
38
  return $wpdb->prefix . $this->table_name;
39
  }
40
 
41
+
42
+ /**
43
+ * Function to check for the existence of a column in the extension's table.
44
+ *
45
+ * @param $column (String) The name of the column to search for.
46
+ *
47
+ * @return (Boolean) Whether or not the column exists.
48
+ *
49
+ * @since 3.4.0
50
+ */
51
+ public function column_exists( $column )
52
  {
53
  global $wpdb;
54
+ $response = false;
55
+ // Fetch any records of the target column.
56
+ $sql = $wpdb->prepare( "SHOW COLUMNS FROM `{$this->table_name()}` WHERE `Field` = '%s';", $column );
57
+ $result = $wpdb->query( $sql );
58
+ // If we got anything back, say so.
59
+ if ( ! empty( $result ) ) $response = true;
60
+ return $result;
 
 
61
  }
62
 
63
+
64
  /**
65
+ * Funciton to get the charset and collate for migrations.
66
  *
67
+ * @param $use_default (Boolean) Whether or not to include the DEFAULT keyword in the return pattern.
68
  *
69
+ * @return (String) A SQL formatted charset and collate for use by table definition.
70
+ *
71
+ * @since 3.0.28
72
+ * @updated 3.1.14
73
  */
74
+ public function charset_collate( $use_default = false )
75
  {
76
+ $response = '';
77
  global $wpdb;
78
  // If our mysql version is 5.5.3 or higher...
79
  if ( version_compare( $wpdb->db_version(), '5.5.3', '>=' ) ) {
80
  // We can use mb4.
81
+ $response = 'CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci';
82
  } // Otherwise...
83
  else {
84
  // We use standard utf8.
85
+ $response = 'CHARACTER SET utf8 COLLATE utf8_general_ci';
86
+ }
87
+ // If we need to use default...
88
+ if ( $use_default ) {
89
+ // Append that to the response.
90
+ $response = 'DEFAULT ' . $response;
91
+ }
92
+ return $response;
93
  }
94
+
95
  /**
96
+ * Function to run our required update functions.
97
+ *
98
+ * @param $callback (String) The function to be run by this call.
99
+ *
100
+ * @since 3.4.0
101
  */
102
+ public function _do_upgrade( $callback )
103
  {
104
+ // If the method exists...
105
+ if ( method_exists( $this, $callback ) ) {
106
+ $blacklist = array(
107
+ '__construct',
108
+ '_do_upgrade',
109
+ '_run',
110
+ 'run',
111
+ '_drop',
112
+ 'drop'
113
+ );
114
+ // If this callback method isn't blacklisted...
115
+ if ( ! in_array( $callback, $blacklist ) ) {
116
+ // Run it.
117
+ $this->{$callback}();
118
+ }
119
  }
120
  }
121
 
122
+
123
+ /**
124
+ * Function to run our initial migration.
125
+ *
126
+ * @since 3.0.0
127
+ */
128
  public function _run()
129
  {
130
  // Check the flag
137
  update_option( $this->flag, TRUE );
138
  }
139
 
140
+
141
+ /**
142
+ * Abstract protection of inherited funciton run.
143
+ *
144
+ * @since 3.0.0
145
+ */
146
  protected abstract function run();
147
 
148
+
149
+ /**
150
+ * Function to drop the table managed by this migration.
151
+ *
152
+ * @since 3.0.28
153
+ */
154
  public function _drop()
155
  {
156
  global $wpdb;
157
+ // If we don't have a table name, exit early.
158
  if( ! $this->table_name ) return;
159
+ // If the table doesn't exist, exit early.
160
  if( 0 == $wpdb->query( $wpdb->prepare( "SHOW TABLES LIKE '%s'", $this->table_name() ) ) ) return;
161
+ // Drop the table.
162
  $wpdb->query( "DROP TABLE " . $this->table_name() );
163
  return $this->drop();
164
  }
165
 
166
+
167
+ /**
168
+ * Protection of inherited function drop.
169
+ *
170
+ * @since 3.0.28
171
+ */
172
  protected function drop()
173
  {
174
  // This section intentionally left blank.
includes/Abstracts/Model.php CHANGED
@@ -120,6 +120,13 @@ class NF_Abstracts_Model
120
  */
121
  protected $_cache = TRUE;
122
 
 
 
 
 
 
 
 
123
  //-----------------------------------------------------
124
  // Public Methods
125
  //-----------------------------------------------------
@@ -251,6 +258,8 @@ class NF_Abstracts_Model
251
  $this->_settings[ 'label' ] = $field[ 'label' ];
252
  $this->_settings[ 'key' ] = $field[ 'key' ];
253
  $this->_settings[ 'type' ] = $field[ 'type' ];
 
 
254
  }
255
  }
256
 
@@ -482,6 +491,22 @@ class NF_Abstracts_Model
482
  */
483
  public function save()
484
  {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
485
  $data = array ( 'updated_at' => current_time( 'mysql' ));
486
 
487
  // If the ID is not set, assign an ID
@@ -644,11 +669,11 @@ class NF_Abstracts_Model
644
  if( $meta_row ){
645
 
646
  $update_values = array(
647
- 'value' => $value
648
  );
649
 
650
  // for forms we need to update the meta_key and meta_value columns
651
- if( 'form' == $this->_type ) {
652
  $update_values[ 'meta_key' ] = $key;
653
  $update_values[ 'meta_value' ] = $value;
654
  }
@@ -671,7 +696,7 @@ class NF_Abstracts_Model
671
  );
672
 
673
  // for forms we need to update the meta_key and meta_value columns
674
- if( 'form' == $this->_type ) {
675
  $insert_values[ 'meta_key' ] = $key;
676
  $insert_values[ 'meta_value' ] = $value;
677
  }
120
  */
121
  protected $_cache = TRUE;
122
 
123
+ /**
124
+ * A Flag for testing whether or not we've completed stage 1 of our db.
125
+ *
126
+ * @var boolean
127
+ */
128
+ private $db_stage_1_complete = TRUE;
129
+
130
  //-----------------------------------------------------
131
  // Public Methods
132
  //-----------------------------------------------------
258
  $this->_settings[ 'label' ] = $field[ 'label' ];
259
  $this->_settings[ 'key' ] = $field[ 'key' ];
260
  $this->_settings[ 'type' ] = $field[ 'type' ];
261
+ $this->_settings[ 'field_label' ] = $field[ 'label' ];
262
+ $this->_settings[ 'field_key' ] = $field[ 'key' ];
263
  }
264
  }
265
 
491
  */
492
  public function save()
493
  {
494
+
495
+ /**
496
+ * Check to see if we've completed stage 1 of our db update.
497
+ */
498
+
499
+ $sql = "SHOW COLUMNS FROM {$this->_db->prefix}nf3_fields LIKE 'field_key'";
500
+ $results = $this->_db->get_results( $sql );
501
+ /**
502
+ * If we don't have the field_key column, we need to remove our new columns.
503
+ *
504
+ * Also, set our db stage 1 tracker to false.
505
+ */
506
+ if ( empty ( $results ) ) {
507
+ $this->db_stage_1_complete = false;
508
+ }
509
+
510
  $data = array ( 'updated_at' => current_time( 'mysql' ));
511
 
512
  // If the ID is not set, assign an ID
669
  if( $meta_row ){
670
 
671
  $update_values = array(
672
+ 'value' => $value,
673
  );
674
 
675
  // for forms we need to update the meta_key and meta_value columns
676
+ if( 'form' == $this->_type || $this->db_stage_1_complete ) {
677
  $update_values[ 'meta_key' ] = $key;
678
  $update_values[ 'meta_value' ] = $value;
679
  }
696
  );
697
 
698
  // for forms we need to update the meta_key and meta_value columns
699
+ if( 'form' == $this->_type || $this->db_stage_1_complete ) {
700
  $insert_values[ 'meta_key' ] = $key;
701
  $insert_values[ 'meta_value' ] = $value;
702
  }
includes/Abstracts/RequiredUpdate.php ADDED
@@ -0,0 +1,224 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit;
2
+
3
+ /**
4
+ * Class NF_Abstracts_RequiredUpdate
5
+ */
6
+ abstract class NF_Abstracts_RequiredUpdate
7
+ {
8
+
9
+ protected $_slug = '';
10
+
11
+ protected $_requires = array();
12
+
13
+ protected $_class_name = '';
14
+
15
+ protected $db;
16
+
17
+ public $response = array();
18
+
19
+ public $debug = false;
20
+
21
+ public $lock_process = false;
22
+
23
+ /**
24
+ * Constructor
25
+ *
26
+ * @since 3.4.0
27
+ */
28
+ public function __construct( $data = array() )
29
+ {
30
+ // Save a reference to wpdb.
31
+ global $wpdb;
32
+ $this->db = $wpdb;
33
+ //Bail if we aren't in the admin.
34
+ if ( ! is_admin() ) return false;
35
+ // If we weren't provided with a slug or a class name...
36
+ if ( ! isset( $data[ 'slug' ] ) || ! isset( $data[ 'class_name' ] ) ) {
37
+ // Bail.
38
+ return false;
39
+ }
40
+ $this->_slug = $data[ 'slug' ];
41
+ $this->_class_name = $data[ 'class_name' ];
42
+ // Record debug settings if provided.
43
+ if ( isset( $data[ 'debug' ] ) ) $this->debug = $data[ 'debug' ];
44
+ }
45
+
46
+
47
+ /**
48
+ * Function to loop over the batch.
49
+ *
50
+ * @since 3.4.0
51
+ */
52
+ public function process()
53
+ {
54
+ /**
55
+ * This function intentionlly left empty.
56
+ */
57
+ }
58
+
59
+
60
+ /**
61
+ * Function to run any setup steps necessary to begin processing.
62
+ *
63
+ * @since 3.4.0
64
+ */
65
+ public function startup()
66
+ {
67
+ /**
68
+ * This function intentionally left empty.
69
+ */
70
+ }
71
+
72
+
73
+ /**
74
+ * Function to cleanup any lingering temporary elements of required updates after completion.
75
+ *
76
+ * @since 3.4.0
77
+ */
78
+ public function cleanup()
79
+ {
80
+ // Delete our required updates data.
81
+ delete_option( 'ninja_forms_doing_required_updates' );
82
+ // Flag that updates are done.
83
+ update_option( 'ninja_forms_needs_updates', 0 );
84
+ // Set our new db version.
85
+ update_option( 'ninja_forms_db_version', Ninja_Forms::DB_VERSION );
86
+ // Fetch our list of completed updates.
87
+ $updates = get_option( 'ninja_forms_required_updates', array() );
88
+ // If we got something back...
89
+ if ( ! empty( $updates ) ) {
90
+ // Send out a call to telemetry.
91
+ Ninja_Forms()->dispatcher()->send( 'required_updates_complete', $updates );
92
+ }
93
+ // Output that we're done.
94
+ $this->response[ 'updatesRemaining' ] = 0;
95
+ $this->respond();
96
+ }
97
+
98
+
99
+ /**
100
+ * Function to dump our JSON response and kill processing.
101
+ *
102
+ * @since 3.4.0
103
+ */
104
+ public function respond()
105
+ {
106
+ // Dump the response.
107
+ echo( json_encode( $this->response ) );
108
+ // Terminate processing.
109
+ die();
110
+ }
111
+
112
+
113
+ /**
114
+ * Function to run our table migrations.
115
+ *
116
+ * @param $callback (String) The callback function in the migration file.
117
+ *
118
+ * @since 3.4.0
119
+ */
120
+ protected function migrate( $callback )
121
+ {
122
+ $migrations = new NF_Database_Migrations();
123
+ $migrations->do_upgrade( $callback );
124
+ }
125
+
126
+ /**
127
+ * Function to prepare our query values for insert.
128
+ *
129
+ * @param $value (Mixed) The value to be escaped for SQL.
130
+ * @return (String) The escaped (and possibly serialized) value of the string.
131
+ *
132
+ * @since 3.4.0
133
+ */
134
+ public function prepare( $value )
135
+ {
136
+ // If our value is a number...
137
+ if ( is_float( $value ) ) {
138
+ // Exit early and return the value.
139
+ return $value;
140
+ }
141
+ // Serialize the value if necessary.
142
+ $escaped = maybe_serialize( $value );
143
+ // Escape it.
144
+ $escaped = $this->db->_real_escape( $escaped );
145
+
146
+ return $escaped;
147
+ }
148
+
149
+ /**
150
+ * Function used to call queries that are gated by debug.
151
+ *
152
+ * @param $sql (String) The query to be run.
153
+ * @return (Object) The response to the wpdb query call.
154
+ *
155
+ * @since 3.4.0
156
+ */
157
+ protected function query( $sql )
158
+ {
159
+ // If we're not debugging...
160
+ if ( ! $this->debug ) {
161
+ // Run the query.
162
+ return $this->db->query( $sql );
163
+ } // Otherwise...
164
+ // Append the query to the response object.
165
+ $this->response[ 'queries' ][] = $sql;
166
+ // Return false.
167
+ return false;
168
+ }
169
+
170
+ /**
171
+ * Function to record the completion of our update in the DB.
172
+ *
173
+ * @since 3.4.0
174
+ */
175
+ protected function confirm_complete()
176
+ {
177
+ // If we're not debugging...
178
+ if ( ! $this->debug ) {
179
+ // Fetch our required updates array.
180
+ $updates = get_option( 'ninja_forms_required_updates', array() );
181
+ // Get a timestamp.
182
+ date_default_timezone_set( 'UTC' );
183
+ $now = date( "Y-m-d H:i:s" );
184
+ // Append the current update to the array.
185
+ $updates[ $this->_slug ] = $now;
186
+ // Save it.
187
+ update_option( 'ninja_forms_required_updates', $updates );
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Enable Maintenance mode
193
+ * Enables maintenance mode so the form will not render on the front end while updates are running.
194
+ *
195
+ * @since 3.4.0
196
+ *
197
+ * @param $prefix - the db prefix.
198
+ * @param $form_id - The id of the form.
199
+ */
200
+ public function enable_maintenance_mode( $prefix, $form_id )
201
+ {
202
+ // Change maintenance column value to 1, run the query and then return.
203
+ $sql = $this->db->prepare( 'UPDATE `' . $prefix . 'nf3_upgrades` SET `maintenance` = 1 WHERE `id` = %d', $form_id );
204
+ $this->db->query( $sql );
205
+ return;
206
+ }
207
+
208
+ /**
209
+ * Disable Maintenance Mode
210
+ * Disables maintenance mode, so the form will be displayed on the front end..
211
+ *
212
+ * @since 3.4.0
213
+ *
214
+ * @param $prefix - the db prefix.
215
+ * @param $form_id - The id of the form.
216
+ */
217
+ public function disable_maintenance_mode( $prefix, $form_id )
218
+ {
219
+ // Change maintenance column value to 0, run the query and then return.
220
+ $sql = $this->db->prepare( 'UPDATE `' . $prefix . 'nf3_upgrades` SET `maintenance` = 0 WHERE `id` = %d', $form_id );
221
+ $this->db->query( $sql );
222
+ return;
223
+ }
224
+ }
includes/Admin/Menus/AddNew.php CHANGED
@@ -13,6 +13,19 @@ final class NF_Admin_Menus_AddNew extends NF_Abstracts_Submenu
13
  public function __construct()
14
  {
15
  parent::__construct();
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  }
17
 
18
  public function get_page_title()
13
  public function __construct()
14
  {
15
  parent::__construct();
16
+
17
+ add_action( 'admin_init', array( $this, 'nf_upgrade_redirect' ) );
18
+ }
19
+
20
+ /**
21
+ * If we have required updates, unregister the menu item
22
+ */
23
+ public function nf_upgrade_redirect() {
24
+ global $pagenow;
25
+
26
+ if( "1" == get_option( 'ninja_forms_needs_updates' ) ) {
27
+ remove_submenu_page( $this->parent_slug, $this->menu_slug );
28
+ }
29
  }
30
 
31
  public function get_page_title()
includes/Admin/Menus/Addons.php CHANGED
@@ -14,6 +14,19 @@ final class NF_Admin_Menus_Addons extends NF_Abstracts_Submenu
14
  if ( ! apply_filters( 'ninja_forms_disable_marketing', $disable_marketing ) ) {
15
  parent::__construct();
16
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  }
18
 
19
  public function get_page_title()
14
  if ( ! apply_filters( 'ninja_forms_disable_marketing', $disable_marketing ) ) {
15
  parent::__construct();
16
  }
17
+
18
+ add_action( 'admin_init', array( $this, 'nf_upgrade_redirect' ) );
19
+ }
20
+
21
+ /**
22
+ * If we have required updates, unregister the menu item
23
+ */
24
+ public function nf_upgrade_redirect() {
25
+ global $pagenow;
26
+
27
+ if( "1" == get_option( 'ninja_forms_needs_updates' ) ) {
28
+ remove_submenu_page( $this->parent_slug, $this->menu_slug );
29
+ }
30
  }
31
 
32
  public function get_page_title()
includes/Admin/Menus/Forms.php CHANGED
@@ -25,6 +25,23 @@ final class NF_Admin_Menus_Forms extends NF_Abstracts_Menu
25
  }
26
 
27
  add_action( 'admin_body_class', array( $this, 'body_class' ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  }
29
 
30
  public function body_class( $classes )
@@ -134,10 +151,13 @@ final class NF_Admin_Menus_Forms extends NF_Abstracts_Menu
134
  </script>
135
  <?php
136
 
 
 
137
  wp_enqueue_script( 'backbone-radio', Ninja_Forms::$url . 'assets/js/lib/backbone.radio.min.js', array( 'jquery', 'backbone' ) );
138
  wp_enqueue_script( 'backbone-marionette-3', Ninja_Forms::$url . 'assets/js/lib/backbone.marionette3.min.js', array( 'jquery', 'backbone' ) );
139
  wp_enqueue_script( 'nf-jbox', Ninja_Forms::$url . 'assets/js/lib/jBox.min.js', array( 'jquery' ) );
140
  wp_enqueue_script( 'nf-ninjamodal', Ninja_Forms::$url . 'assets/js/lib/ninjaModal.js', array( 'jquery' ), $this->ver );
 
141
  wp_enqueue_script( 'nf-moment', Ninja_Forms::$url . 'assets/js/lib/moment-with-locales.min.js', array( 'jquery', 'nf-dashboard' ) );
142
  wp_enqueue_script( 'nf-dashboard', Ninja_Forms::$url . 'assets/js/min/dashboard.min.js', array( 'backbone-radio', 'backbone-marionette-3' ), $this->ver );
143
 
@@ -147,13 +167,13 @@ final class NF_Admin_Menus_Forms extends NF_Abstracts_Menu
147
  wp_localize_script( 'nf-dashboard', 'nfAdmin', array(
148
  'ajaxNonce' => wp_create_nonce( 'ninja_forms_dashboard_nonce' ),
149
  'batchNonce' => wp_create_nonce( 'ninja_forms_batch_nonce' ),
 
150
  'formTelemetry' => ( get_option( 'nf_form_tel_sent' ) ) ? 0 : 1,
151
  'showOptin' => ( get_option( 'ninja_forms_do_not_allow_tracking' ) ||
152
  get_option( 'ninja_forms_allow_tracking' ) ) ? 0 : 1,
 
153
  'currentUserEmail' => $current_user->user_email,
154
- 'doingCleanup' => ( ! get_option( 'ninja_forms_data_is_clean' ) &&
155
- isset( $_REQUEST[ 'action' ] ) &&
156
- 'cleanup' == $_REQUEST[ 'action' ] ) ? 1 : 0,
157
  ) );
158
 
159
  wp_enqueue_style( 'nf-builder', Ninja_Forms::$url . 'assets/css/builder.css', array(), $this->ver );
@@ -161,6 +181,10 @@ final class NF_Admin_Menus_Forms extends NF_Abstracts_Menu
161
  wp_enqueue_style( 'nf-jbox', Ninja_Forms::$url . 'assets/css/jBox.css' );
162
  wp_enqueue_style( 'nf-font-awesome', Ninja_Forms::$url . 'assets/css/font-awesome.min.css' );
163
 
 
 
 
 
164
  Ninja_Forms::template( 'admin-menu-dashboard.html.php' );
165
  }
166
  }
@@ -170,6 +194,11 @@ final class NF_Admin_Menus_Forms extends NF_Abstracts_Menu
170
  add_submenu_page( 'ninja-forms', '', '', 'read', '', '' );
171
  }
172
 
 
 
 
 
 
173
  private function import_from_template()
174
  {
175
  $template = sanitize_title( $_GET['form_id'] );
25
  }
26
 
27
  add_action( 'admin_body_class', array( $this, 'body_class' ) );
28
+ add_action( 'admin_init', array( $this, 'nf_upgrade_redirect' ) );
29
+ }
30
+
31
+ /**
32
+ * If we have required updates, redirect to the main Ninja Forms page
33
+ */
34
+ public function nf_upgrade_redirect() {
35
+ global $pagenow;
36
+
37
+ if( "1" == get_option( 'ninja_forms_needs_updates' ) &&
38
+ 'admin.php' == $pagenow &&
39
+ 'ninja-forms' == $_GET[ 'page' ] &&
40
+ isset( $_GET[ 'form_id' ] ) ) {
41
+ wp_safe_redirect( admin_url( 'admin.php?page=ninja-forms' ), 301 );
42
+ exit;
43
+
44
+ }
45
  }
46
 
47
  public function body_class( $classes )
151
  </script>
152
  <?php
153
 
154
+ $required_updates = get_option( 'ninja_forms_needs_updates', 0 );
155
+
156
  wp_enqueue_script( 'backbone-radio', Ninja_Forms::$url . 'assets/js/lib/backbone.radio.min.js', array( 'jquery', 'backbone' ) );
157
  wp_enqueue_script( 'backbone-marionette-3', Ninja_Forms::$url . 'assets/js/lib/backbone.marionette3.min.js', array( 'jquery', 'backbone' ) );
158
  wp_enqueue_script( 'nf-jbox', Ninja_Forms::$url . 'assets/js/lib/jBox.min.js', array( 'jquery' ) );
159
  wp_enqueue_script( 'nf-ninjamodal', Ninja_Forms::$url . 'assets/js/lib/ninjaModal.js', array( 'jquery' ), $this->ver );
160
+ wp_enqueue_script( 'nf-batch-processor', Ninja_Forms::$url . 'assets/js/lib/batch-processor.js', array( 'nf-ninjamodal' ), $this->ver );
161
  wp_enqueue_script( 'nf-moment', Ninja_Forms::$url . 'assets/js/lib/moment-with-locales.min.js', array( 'jquery', 'nf-dashboard' ) );
162
  wp_enqueue_script( 'nf-dashboard', Ninja_Forms::$url . 'assets/js/min/dashboard.min.js', array( 'backbone-radio', 'backbone-marionette-3' ), $this->ver );
163
 
167
  wp_localize_script( 'nf-dashboard', 'nfAdmin', array(
168
  'ajaxNonce' => wp_create_nonce( 'ninja_forms_dashboard_nonce' ),
169
  'batchNonce' => wp_create_nonce( 'ninja_forms_batch_nonce' ),
170
+ 'updateNonce' => wp_create_nonce( 'ninja_forms_required_update_nonce' ),
171
  'formTelemetry' => ( get_option( 'nf_form_tel_sent' ) ) ? 0 : 1,
172
  'showOptin' => ( get_option( 'ninja_forms_do_not_allow_tracking' ) ||
173
  get_option( 'ninja_forms_allow_tracking' ) ) ? 0 : 1,
174
+ 'requiredUpdates' => $required_updates,
175
  'currentUserEmail' => $current_user->user_email,
176
+ 'builderURL' => admin_url( 'admin.php?page=ninja-forms&form_id=' ),
 
 
177
  ) );
178
 
179
  wp_enqueue_style( 'nf-builder', Ninja_Forms::$url . 'assets/css/builder.css', array(), $this->ver );
181
  wp_enqueue_style( 'nf-jbox', Ninja_Forms::$url . 'assets/css/jBox.css' );
182
  wp_enqueue_style( 'nf-font-awesome', Ninja_Forms::$url . 'assets/css/font-awesome.min.css' );
183
 
184
+ if( $required_updates ) {
185
+ wp_enqueue_style( 'nf-updates-styles', Ninja_Forms::$url . '/assets/css/required-updates.css' );
186
+ }
187
+
188
  Ninja_Forms::template( 'admin-menu-dashboard.html.php' );
189
  }
190
  }
194
  add_submenu_page( 'ninja-forms', '', '', 'read', '', '' );
195
  }
196
 
197
+ /**
198
+ * TODO: Remove this function and its hook because we are handling template imports via the batch processor.
199
+ * @since 3.0
200
+ * @return void
201
+ */
202
  private function import_from_template()
203
  {
204
  $template = sanitize_title( $_GET['form_id'] );
includes/Admin/Menus/ImportExport.php CHANGED
@@ -7,7 +7,7 @@ final class NF_Admin_Menus_ImportExport extends NF_Abstracts_Submenu
7
  public $menu_slug = 'nf-import-export';
8
 
9
  public function __construct()
10
- {
11
  add_action( 'init', array( $this, 'import_form_listener' ), 0 );
12
  add_action( 'init', array( $this, 'export_form_listener' ), 0 );
13
 
@@ -17,6 +17,24 @@ final class NF_Admin_Menus_ImportExport extends NF_Abstracts_Submenu
17
  add_filter( 'ninja_forms_before_import_fields', array( $this, 'import_fields_backwards_compatibility' ) );
18
 
19
  parent::__construct();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  }
21
 
22
  public function get_page_title()
@@ -143,6 +161,25 @@ final class NF_Admin_Menus_ImportExport extends NF_Abstracts_Submenu
143
 
144
  wp_enqueue_script( 'ninja_forms_admin_import_export' );
145
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  Ninja_Forms::template( 'admin-menu-import-export.html.php', compact( 'tabs', 'active_tab' ) );
147
  }
148
 
7
  public $menu_slug = 'nf-import-export';
8
 
9
  public function __construct()
10
+ {
11
  add_action( 'init', array( $this, 'import_form_listener' ), 0 );
12
  add_action( 'init', array( $this, 'export_form_listener' ), 0 );
13
 
17
  add_filter( 'ninja_forms_before_import_fields', array( $this, 'import_fields_backwards_compatibility' ) );
18
 
19
  parent::__construct();
20
+
21
+ add_action( 'admin_init', array( $this, 'nf_upgrade_redirect' ) );
22
+ }
23
+
24
+ /**
25
+ * If we have required updates, redirect to the main Ninja Forms page
26
+ */
27
+ public function nf_upgrade_redirect() {
28
+ global $pagenow;
29
+
30
+ if( "1" == get_option( 'ninja_forms_needs_updates' ) ) {
31
+ remove_submenu_page( $this->parent_slug, $this->menu_slug );
32
+ if( 'admin.php' == $pagenow && 'nf-import-export' == $_GET[ 'page' ] ) {
33
+
34
+ wp_safe_redirect( admin_url( 'admin.php?page=ninja-forms' ), 301 );
35
+ exit;
36
+ }
37
+ }
38
  }
39
 
40
  public function get_page_title()
161
 
162
  wp_enqueue_script( 'ninja_forms_admin_import_export' );
163
 
164
+ wp_localize_script( 'ninja_forms_admin_import_export', 'nfAdmin', array(
165
+ 'ajax_url' => admin_url( 'admin-ajax.php' ),
166
+ 'batchNonce' => wp_create_nonce( 'ninja_forms_batch_nonce' ),
167
+ 'i18n' => array(
168
+ 'trashExpiredSubsMessage' => __( 'Are you sure you want to trash all expired submissions?', 'ninja-forms' ),
169
+ 'trashExpiredSubsButtonPrimary' => __( 'Trash', 'ninja-forms' ),
170
+ 'trashExpiredSubsButtonSecondary' => __( 'Cancel', 'ninja-forms' ),
171
+ ),
172
+ 'builderURL' => admin_url( 'admin.php?page=ninja-forms&form_id=' ),
173
+ ));
174
+
175
+ wp_enqueue_script( 'jBox', Ninja_Forms::$url . 'assets/js/lib/jBox.min.js', array( 'jquery' ) );
176
+ wp_enqueue_style( 'jBox', Ninja_Forms::$url . 'assets/css/jBox.css' );
177
+ wp_enqueue_script( 'nf-ninja-modal', Ninja_Forms::$url . 'assets/js/lib/ninjaModal.js', array( 'jquery' ) );
178
+ wp_enqueue_script( 'nf-batch-processor', Ninja_Forms::$url . 'assets/js/lib/batch-processor.js', array( 'jquery' ) );
179
+ wp_enqueue_style( 'nf-font-awesome', Ninja_Forms::$url . 'assets/css/font-awesome.min.css' );
180
+
181
+
182
+
183
  Ninja_Forms::template( 'admin-menu-import-export.html.php', compact( 'tabs', 'active_tab' ) );
184
  }
185
 
includes/Admin/Menus/Settings.php CHANGED
@@ -154,12 +154,44 @@ final class NF_Admin_Menus_Settings extends NF_Abstracts_Submenu
154
  wp_enqueue_style( 'nf-combobox', Ninja_Forms::$url . 'assets/css/combobox.css' );
155
  wp_enqueue_style( 'jBox', Ninja_Forms::$url . 'assets/css/jBox.css' );
156
  wp_register_script( 'ninja_forms_admin_menu_settings', Ninja_Forms::$url . 'assets/js/admin-settings.js', array( 'jquery' ), FALSE, TRUE );
 
 
 
 
 
 
157
  wp_localize_script( 'ninja_forms_admin_menu_settings', 'nf_settings', array(
158
  'ajax_url' => admin_url( 'admin-ajax.php' ),
159
  'forms' => $form_options,
160
  'nf_nuke_title' => __( 'Remove ALL Ninja Forms data and uninstall?', 'ninja-forms' ),
161
  'nonce' => wp_create_nonce( "ninja_forms_settings_nonce" ),
162
- 'batch_nonce' => wp_create_nonce( 'ninja_forms_batch_nonce' ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  'i18n' => array(
164
  'downgradeMessage' => __( 'Are you sure you want to downgrade?', 'ninja-forms' ),
165
  'downgradeWarningMessage' => __( 'You WILL lose any forms or submissions created on this version of Ninja Forms.', 'ninja-forms' ),
@@ -173,6 +205,7 @@ final class NF_Admin_Menus_Settings extends NF_Abstracts_Submenu
173
  'allow_telemetry' => $allow_tel,
174
  ));
175
  wp_enqueue_script( 'nf-ninja-modal', Ninja_Forms::$url . 'assets/js/lib/ninjaModal.js' );
 
176
  wp_enqueue_style( 'nf-font-awesome', Ninja_Forms::$url . 'assets/css/font-awesome.min.css' );
177
 
178
 
154
  wp_enqueue_style( 'nf-combobox', Ninja_Forms::$url . 'assets/css/combobox.css' );
155
  wp_enqueue_style( 'jBox', Ninja_Forms::$url . 'assets/css/jBox.css' );
156
  wp_register_script( 'ninja_forms_admin_menu_settings', Ninja_Forms::$url . 'assets/js/admin-settings.js', array( 'jquery' ), FALSE, TRUE );
157
+
158
+ /**
159
+ * This wp_localize_script call should eventually be removed.
160
+ *
161
+ * TODO: Remove this function call when we've replaced references to nf_settings in our JS with nfAdmin.
162
+ */
163
  wp_localize_script( 'ninja_forms_admin_menu_settings', 'nf_settings', array(
164
  'ajax_url' => admin_url( 'admin-ajax.php' ),
165
  'forms' => $form_options,
166
  'nf_nuke_title' => __( 'Remove ALL Ninja Forms data and uninstall?', 'ninja-forms' ),
167
  'nonce' => wp_create_nonce( "ninja_forms_settings_nonce" ),
168
+ 'batchNonce' => wp_create_nonce( 'ninja_forms_batch_nonce' ),
169
+ 'i18n' => array(
170
+ 'downgradeMessage' => __( 'Are you sure you want to downgrade?', 'ninja-forms' ),
171
+ 'downgradeWarningMessage' => __( 'You WILL lose any forms or submissions created on this version of Ninja Forms.', 'ninja-forms' ),
172
+ 'downgradeConfirmMessage' => __( 'Type ', 'ninja-forms' ) . '<span style="color: red";>' . 'DOWNGRADE' . "</span>" . __( ' to confirm.', 'ninja-forms' ),
173
+ 'downgradeButtonPrimary' => __( 'Downgrade', 'ninja-forms'),
174
+ 'downgradeButtonSecondary' => __( 'Cancel', 'ninja-forms' ),
175
+ 'trashExpiredSubsMessage' => __( 'Are you sure you want to trash all expired submissions?', 'ninja-forms' ),
176
+ 'trashExpiredSubsButtonPrimary' => __( 'Trash', 'ninja-forms' ),
177
+ 'trashExpiredSubsButtonSecondary' => __( 'Cancel', 'ninja-forms' ),
178
+ ),
179
+ 'allow_telemetry' => $allow_tel,
180
+ ));
181
+
182
+ /**
183
+ * Duplicating the localization above with an nfAdmin variable for consistency.
184
+ *
185
+ * Eventually, we should remove all references to nf_settings, which isn't very descriptive or specific with nfAdmin instead.
186
+ *
187
+ * TODO: Replace references to nf_settings object in admin JS files with nfAdmin.
188
+ */
189
+ wp_localize_script( 'ninja_forms_admin_menu_settings', 'nfAdmin', array(
190
+ 'ajax_url' => admin_url( 'admin-ajax.php' ),
191
+ 'forms' => $form_options,
192
+ 'nf_nuke_title' => __( 'Remove ALL Ninja Forms data and uninstall?', 'ninja-forms' ),
193
+ 'nonce' => wp_create_nonce( "ninja_forms_settings_nonce" ),
194
+ 'batchNonce' => wp_create_nonce( 'ninja_forms_batch_nonce' ),
195
  'i18n' => array(
196
  'downgradeMessage' => __( 'Are you sure you want to downgrade?', 'ninja-forms' ),
197
  'downgradeWarningMessage' => __( 'You WILL lose any forms or submissions created on this version of Ninja Forms.', 'ninja-forms' ),
205
  'allow_telemetry' => $allow_tel,
206
  ));
207
  wp_enqueue_script( 'nf-ninja-modal', Ninja_Forms::$url . 'assets/js/lib/ninjaModal.js' );
208
+ wp_enqueue_script( 'nf-ninja-batch-processor', Ninja_Forms::$url . 'assets/js/lib/batch-processor.js' );
209
  wp_enqueue_style( 'nf-font-awesome', Ninja_Forms::$url . 'assets/css/font-awesome.min.css' );
210
 
211
 
includes/Admin/Menus/Submissions.php CHANGED
@@ -29,7 +29,7 @@ final class NF_Admin_Menus_Submissions extends NF_Abstracts_Submenu
29
  * Constructor
30
  */
31
  public function __construct()
32
- {
33
  parent::__construct();
34
 
35
  add_filter( 'manage_nf_sub_posts_columns', array( $this, 'change_columns' ) );
@@ -56,6 +56,23 @@ final class NF_Admin_Menus_Submissions extends NF_Abstracts_Submenu
56
 
57
  // This will only run on our post type.
58
  add_action( 'views_edit-nf_sub', array( $this, 'change_views' ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  }
60
 
61
  /**
29
  * Constructor
30
  */
31
  public function __construct()
32
+ {
33
  parent::__construct();
34
 
35
  add_filter( 'manage_nf_sub_posts_columns', array( $this, 'change_columns' ) );
56
 
57
  // This will only run on our post type.
58
  add_action( 'views_edit-nf_sub', array( $this, 'change_views' ) );
59
+
60
+ // add_action( 'admin_init', array( $this, 'nf_upgrade_redirect' ) );
61
+ }
62
+
63
+ /**
64
+ * If we have required updates, redirect to the main Ninja Forms page
65
+ */
66
+ public function nf_upgrade_redirect() {
67
+ global $pagenow;
68
+
69
+ if( "1" == get_option( 'ninja_forms_needs_updates' ) ) {
70
+ // remove_submenu_page( $this->parent_slug, $this->menu_slug );
71
+ // if( 'edit.php' == $pagenow && 'nf_sub' == $_GET[ 'post_type' ] ) {
72
+ // wp_safe_redirect( admin_url( 'admin.php?page=ninja-forms' ), 301 );
73
+ // exit;
74
+ // }
75
+ }
76
  }
77
 
78
  /**
includes/Admin/Menus/SystemStatus.php CHANGED
@@ -165,6 +165,8 @@ final class NF_Admin_Menus_SystemStatus extends NF_Abstracts_Submenu
165
  __( 'Home URL','ninja-forms' ) => home_url(),
166
  __( 'Site URL','ninja-forms' ) => site_url(),
167
  __( 'Ninja Forms Version','ninja-forms' ) => esc_html( Ninja_Forms::VERSION ),
 
 
168
  __( 'WP Version','ninja-forms' ) => $wp_version . ' - ' . $wp_compatible,
169
  __( 'WP Multisite Enabled','ninja-forms' ) => $multisite,
170
  __( 'Web Server Info','ninja-forms' ) => esc_html( $_SERVER['SERVER_SOFTWARE'] ),
165
  __( 'Home URL','ninja-forms' ) => home_url(),
166
  __( 'Site URL','ninja-forms' ) => site_url(),
167
  __( 'Ninja Forms Version','ninja-forms' ) => esc_html( Ninja_Forms::VERSION ),
168
+ __( 'Ninja Forms DB Version', 'ninja-forms' ) => get_option( 'ninja_forms_db_version' ),
169
+ __( 'Ninja Forms Gatekeeper', 'ninja-forms' ) => WPN_Helper::get_zuul(),
170
  __( 'WP Version','ninja-forms' ) => $wp_version . ' - ' . $wp_compatible,
171
  __( 'WP Multisite Enabled','ninja-forms' ) => $multisite,
172
  __( 'Web Server Info','ninja-forms' ) => esc_html( $_SERVER['SERVER_SOFTWARE'] ),
includes/Admin/Processes/ChunkPublish.php CHANGED
@@ -8,7 +8,7 @@ class NF_Admin_Processes_ChunkPublish extends NF_Abstracts_BatchProcess
8
  // header( 'Content-Type: application/json' );
9
  private $data;
10
  private $form_id;
11
- private $response = array(
12
  'last_request' => 'failure',
13
  'batch_complete' => false,
14
  );
8
  // header( 'Content-Type: application/json' );
9
  private $data;
10
  private $form_id;
11
+ protected $response = array(
12
  'last_request' => 'failure',
13
  'batch_complete' => false,
14
  );
includes/Admin/Processes/DataCleanup.php DELETED
@@ -1,131 +0,0 @@
1
- <?php if ( ! defined( 'ABSPATH' ) ) exit;
2
-
3
- /**
4
- * Class NF_Abstracts_Batch_Process
5
- */
6
- class NF_Admin_Processes_DataCleanup extends NF_Abstracts_BatchProcess
7
- {
8
- private $response = array(
9
- 'batch_complete' => false
10
- );
11
- protected $delete = array();
12
-
13
- /**
14
- * Constructor
15
- */
16
- public function __construct( $data = array() )
17
- {
18
- //Bail if we aren't in the admin.
19
- if ( ! is_admin() )
20
- return false;
21
- // Run process.
22
- $this->process();
23
- }
24
-
25
-
26
- /**
27
- * Function to loop over the batch.
28
- */
29
- public function process()
30
- {
31
- global $wpdb;
32
- // If we've not already started the cleanup process...
33
- if ( ! get_option( 'nf_doing_data_cleanup' ) ) {
34
- // Run the startup process.
35
- $this->startup();
36
- } // Otherwise... (We've already run startup.)
37
- else {
38
- // Get our data.
39
- $data = get_option( 'nf_data_cleanup_ids' );
40
- $this->delete = explode( ',', $data );
41
- }
42
- // If our array isn't emtpy...
43
- if ( ! empty( $this->delete ) ) {
44
- // Fetch the last item on it.
45
- $id = array_pop( $this->delete );
46
- // Get a list of post IDs to delete.
47
- $sql = "SELECT DISTINCT(`id`) FROM `" . $wpdb->prefix . "posts` WHERE `id` IN( SELECT DISTINCT(`post_id`) FROM `" . $wpdb->prefix . "postmeta` WHERE `meta_key` = '_form_id' AND `meta_value` = '" . $id . "' ) AND `post_type` = 'nf_sub' LIMIT 500";
48
- $result = $wpdb->get_results( $sql, 'ARRAY_A' );
49
- // If we got 500 or more results...
50
- if ( 500 == count( $result ) ) {
51
- // Put this id back in our array to run again.
52
- array_push( $this->delete, $id );
53
- }
54
- // Convert our results to something we can use in a query.
55
- array_walk( $result, array( $this, 'smush_results' ) );
56
- $sub_sql = implode( ', ', $result );
57
- // If we have something to query...
58
- if ( '' != $sub_sql ) {
59
- // Delete postmeta data.
60
- $sql = "DELETE FROM `" . $wpdb->prefix . "postmeta` WHERE `post_id` IN(" . $sub_sql . ")";
61
- $wpdb->query( $sql );
62
- // Delete post data.
63
- $sql = "DELETE FROM `" . $wpdb->prefix . "posts` WHERE `id` IN(" . $sub_sql . ")";
64
- $wpdb->query( $sql );
65
- }
66
- }
67
- // If our array isn't empty...
68
- if ( ! empty( $this->delete ) ) {
69
- // Determine how many steps we have left.
70
- $this->response[ 'step_remaining' ] = count( $this->delete );
71
- update_option( 'nf_data_cleanup_ids', implode( ',', $this->delete ) );
72
- echo wp_json_encode( $this->response );
73
- wp_die();
74
- }
75
- // Run our cleanup process.
76
- $this->cleanup();
77
- echo wp_json_encode( $this->response );
78
- wp_die();
79
- }
80
-
81
-
82
- /**
83
- * Function to run any setup steps necessary to begin processing.
84
- */
85
- public function startup()
86
- {
87
- global $wpdb;
88
- // Get a list of IDs from the forms table.
89
- $sql = "SELECT DISTINCT(`id`) FROM `" . $wpdb->prefix . "nf3_forms`";
90
- $forms = $wpdb->get_results( $sql, 'ARRAY_A' );
91
- // Get a list of IDs from the Submissions data.
92
- $sql = "SELECT DISTINCT(m.meta_value) AS id FROM `" . $wpdb->prefix . "postmeta` AS m LEFT JOIN `" . $wpdb->prefix . "posts` AS p on p.id = m.post_id WHERE m.meta_key = '_form_id' AND p.post_type = 'nf_sub'";
93
- $sub_forms = $wpdb->get_results( $sql, 'ARRAY_A' );
94
- // For each form ID in the submission records...
95
- foreach( $sub_forms AS $form ) {
96
- // If the form is not currently defined in our forms table...
97
- if ( ! in_array( $form, $forms ) ) {
98
- // Add it to our list of things to delete.
99
- $this->delete[] = $form[ 'id' ];
100
- }
101
- }
102
- // Get our number of steps for the progress bar.
103
- $this->response[ 'step_total' ] = count( $this->delete );
104
- // Flag startup done.
105
- add_option( 'nf_doing_data_cleanup', 'true' );
106
- }
107
-
108
-
109
- /**
110
- * Function to cleanup any lingering temporary elements of a batch process after completion.
111
- */
112
- public function cleanup()
113
- {
114
- global $wpdb;
115
- // Delete our options.
116
- delete_option( 'nf_data_cleanup_ids' );
117
- delete_option( 'nf_doing_data_cleanup' );
118
- // Add our "finished" option.
119
- add_option( 'ninja_forms_data_is_clean', 'true' );
120
- // Tell our JS that we're done.
121
- $this->response[ 'step_remaining' ] = 0;
122
- $this->response[ 'batch_complete' ] = true;
123
- }
124
-
125
- /**
126
- * Function to compress data array and eliminate unnecessary keys.
127
- */
128
- public function smush_results( &$value, $key ) {
129
- $value = $value[ 'id' ];
130
- }
131
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/Admin/Processes/ExpiredSubmissionCleanup.php CHANGED
@@ -5,41 +5,53 @@
5
  */
6
  class NF_Admin_Processes_ExpiredSubmissionCleanup extends NF_Abstracts_BatchProcess
7
  {
8
-
9
  protected $expired_subs = array();
10
 
11
- private $response = array(
12
- 'batch_complete' => false
13
- );
14
-
15
  /**
16
- * Constructor
17
  */
18
- public function __construct( $data = array() )
19
  {
20
- //Bail if we aren't in the admin.
21
- if ( ! is_admin() )
22
- return false;
 
 
 
 
 
 
 
 
23
 
24
- // Run process.
25
- $this->process();
 
 
 
26
  }
27
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
  /**
30
  * Function to loop over the batch.
 
 
 
31
  */
32
  public function process()
33
  {
34
- if ( ! get_option( 'nf_doing_expired_submission_cleanup' ) ) {
35
- // Run the startup process.
36
- $this->startup();
37
- } // Otherwise... (We've already run startup.)
38
- else {
39
- // Get our remaining submissions from record.
40
- $data = get_option( 'nf_expired_submissions' );
41
- $this->expired_subs = $data;
42
- }
43
 
44
  // For the first 250 in the array.
45
  for( $i = 0; $i < 250; $i++ ){
@@ -53,68 +65,32 @@ class NF_Admin_Processes_ExpiredSubmissionCleanup extends NF_Abstracts_BatchProc
53
 
54
  // If our subs array isn't empty...
55
  if( ! empty( $this->expired_subs ) ) {
56
- // ..see how many steps we have left, update our option, and send the remaining step to the JS.
57
- $this->response[ 'step_remaining' ] = $this->get_steps();
58
  update_option( 'nf_expired_submissions', $this->expired_subs );
59
- echo wp_json_encode( $this->response );
60
- wp_die();
61
  }
62
 
63
- // Run our cleanup process.
64
- $this->cleanup();
65
- echo wp_json_encode( $this->response );
66
- wp_die();
67
  }
68
 
69
-
70
- /**
71
- * Function to run any setup steps necessary to begin processing.
72
- */
73
- public function startup()
74
- {
75
- // Retrieves the option that contains all of our expiration data.
76
- $expired_sub_option = get_option( 'nf_sub_expiration', array() );
77
-
78
- // Loop over our options and ...
79
- foreach( $expired_sub_option as $sub ) {
80
- /*
81
- * Separate our $option values into two positions
82
- * $option[ 0 ] = ( int ) form_id
83
- * $option[ 1 ] = ( int ) expiration time in days.
84
- */
85
- $sub = explode( ',', $sub );
86
-
87
- $expired_subs = $this->get_expired_subs( $sub[ 0 ], $sub[ 1 ] );
88
-
89
- // Use the helper method to build an array of expired subs.
90
- $this->expired_subs = array_merge( $this->expired_subs, $expired_subs );
91
- }
92
-
93
- // Determine how many steps this will take.
94
- $this->response[ 'step_total' ] = $this->get_steps();
95
-
96
-
97
- add_option( 'nf_doing_expired_submission_cleanup', 'true' );
98
- }
99
-
100
-
101
  /**
102
  * Function to cleanup any lingering temporary elements of a batch process after completion.
 
 
 
103
  */
104
  public function cleanup()
105
  {
106
- // Delete our options.
107
- delete_option('nf_doing_expired_submission_cleanup' );
108
  delete_option( 'nf_expired_submissions' );
109
-
110
- // Tell our JS that we're done.
111
- $this->response[ 'batch_complete' ] = true;
112
  }
113
 
114
- /*
115
  * Get Steps
116
  * Determines the amount of steps needed for the step processors.
117
  *
 
118
  * @return int of the number of steps.
119
  */
120
  public function get_steps()
5
  */
6
  class NF_Admin_Processes_ExpiredSubmissionCleanup extends NF_Abstracts_BatchProcess
7
  {
8
+ protected $_slug = 'expired_submission_cleanup';
9
  protected $expired_subs = array();
10
 
 
 
 
 
11
  /**
12
+ * Function to run any setup steps necessary to begin processing.
13
  */
14
+ public function startup()
15
  {
16
+ // Retrieves the option that contains all of our expiration data.
17
+ $expired_sub_option = get_option( 'nf_sub_expiration', array() );
18
+
19
+ // Loop over our options and ...
20
+ foreach( $expired_sub_option as $sub ) {
21
+ /*
22
+ * Separate our $option values into two positions
23
+ * $option[ 0 ] = ( int ) form_id
24
+ * $option[ 1 ] = ( int ) expiration time in days.
25
+ */
26
+ $sub = explode( ',', $sub );
27
 
28
+ $expired_subs = $this->get_expired_subs( $sub[ 0 ], $sub[ 1 ] );
29
+
30
+ // Use the helper method to build an array of expired subs.
31
+ $this->expired_subs = array_merge( $this->expired_subs, $expired_subs );
32
+ }
33
  }
34
 
35
+ /**
36
+ * Function to run any setup steps necessary to begin processing for steps after the first.
37
+ *
38
+ * @since 3.4.0
39
+ * @return void
40
+ */
41
+ public function restart()
42
+ {
43
+ // Get our remaining submissions from record.
44
+ $this->expired_subs = get_option( 'nf_expired_submissions', array() );
45
+ }
46
 
47
  /**
48
  * Function to loop over the batch.
49
+ *
50
+ * @since 3.4.0
51
+ * @return void
52
  */
53
  public function process()
54
  {
 
 
 
 
 
 
 
 
 
55
 
56
  // For the first 250 in the array.
57
  for( $i = 0; $i < 250; $i++ ){
65
 
66
  // If our subs array isn't empty...
67
  if( ! empty( $this->expired_subs ) ) {
68
+ // Update nf_expired_submissions so that we can use it in our next step.
 
69
  update_option( 'nf_expired_submissions', $this->expired_subs );
70
+ // End processing and move to the next step.
71
+ $this->next_step();
72
  }
73
 
74
+ // If we get here, then we're ready to end batch processing.
75
+ $this->batch_complete();
 
 
76
  }
77
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  /**
79
  * Function to cleanup any lingering temporary elements of a batch process after completion.
80
+ *
81
+ * @since 3.4.0
82
+ * @return void
83
  */
84
  public function cleanup()
85
  {
 
 
86
  delete_option( 'nf_expired_submissions' );
 
 
 
87
  }
88
 
89
+ /**
90
  * Get Steps
91
  * Determines the amount of steps needed for the step processors.
92
  *
93
+ * @since 3.4.0
94
  * @return int of the number of steps.
95
  */
96
  public function get_steps()
includes/Admin/Processes/ImportForm.php ADDED
@@ -0,0 +1,856 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit;
2
+
3
+ /**
4
+ * Class NF_Abstracts_Batch_Process
5
+ */
6
+ class NF_Admin_Processes_ImportForm extends NF_Abstracts_BatchProcess
7
+ {
8
+ protected $_slug = 'import_form';
9
+
10
+ private $fields_per_step = 20;
11
+
12
+ protected $form;
13
+
14
+ /**
15
+ * Store an array of columns that we want to store in our table rather than meta.
16
+ *
17
+ * This array stores the column name and the name of the setting that it maps to.
18
+ *
19
+ * The format is:
20
+ *
21
+ * array( 'COLUMN_NAME' => 'SETTING_NAME' )
22
+ */
23
+ protected $forms_db_columns = array(
24
+ 'title' => 'title',
25
+ 'created_at' => 'created_at',
26
+ 'form_title' => 'title',
27
+ 'default_label_pos' => 'default_label_pos',
28
+ 'show_title' => 'show_title',
29
+ 'clear_complete' => 'clear_complete',
30
+ 'hide_complete' => 'hide_complete',
31
+ 'logged_in' => 'logged_in',
32
+ 'seq_num' => 'seq_num',
33
+ );
34
+
35
+ protected $fields_db_columns = array(
36
+ 'parent_id' => 'parent_id',
37
+ 'id' => 'id',
38
+ 'key' => 'key',
39
+ 'type' => 'type',
40
+ 'label' => 'label',
41
+ 'field_key' => 'key',
42
+ 'field_label' => 'label',
43
+ 'order' => 'order',
44
+ 'required' => 'required',
45
+ 'default_value' => 'default',
46
+ 'label_pos' => 'label_pos',
47
+ 'personally_identifiable' => 'personally_identifiable',
48
+ );
49
+
50
+ protected $actions_db_columns = array(
51
+ 'title' => 'title',
52
+ 'key' =>'key',
53
+ 'type' =>'type',
54
+ 'active' =>'active',
55
+ 'parent_id' =>'parent_id',
56
+ 'created_at' =>'created_at',
57
+ 'updated_at' =>'updated_at',
58
+ 'label' =>'label',
59
+ );
60
+
61
+ /**
62
+ * Function to run any setup steps necessary to begin processing.
63
+ *
64
+ * @since 3.4.0
65
+ * @return void
66
+ */
67
+ public function startup()
68
+ {
69
+ // If we aren't passed any form content, bail.
70
+ if ( empty ( $_POST[ 'extraData' ][ 'content' ] ) ) {
71
+ // TODO: When we add error handling to the batch processor, this should be revisited.
72
+ $this->batch_complete();
73
+ }
74
+
75
+ $data = explode( ';base64,', $_POST[ 'extraData' ][ 'content' ] );
76
+ $data = base64_decode( $data[ 1 ] );
77
+
78
+ /**
79
+ * $data could now hold two things, depending on whether this was a 2.9 or 3.0 export.
80
+ *
81
+ * If it's a 3.0 export, the data will be json encoded.
82
+ * If it's a 2.9 export, the data will be serialized.
83
+ *
84
+ * We're first going to try to json_decode. If we don't get an array, we'll unserialize.
85
+ */
86
+
87
+ $decoded_data = json_decode( WPN_Helper::json_cleanup( html_entity_decode( $data ) ), true );
88
+
89
+ // If we don't have an array, try unserializing
90
+ if ( ! is_array( $decoded_data ) ) {
91
+ $decoded_data = WPN_Helper::maybe_unserialize( $data );
92
+ }
93
+
94
+ // Try to utf8 decode our results.
95
+ $data = WPN_Helper::utf8_decode( $decoded_data );
96
+
97
+ // If json_encode returns false, then this is an invalid utf8 decode.
98
+ if ( ! json_encode( $data ) ) {
99
+ $data = $decoded_data;
100
+ }
101
+
102
+ $data = $this->import_form_backwards_compatibility( $data );
103
+
104
+ // $data is now a form array.
105
+ $this->form = $data;
106
+
107
+ /**
108
+ * Check to see if we've got new field columns.
109
+ *
110
+ * We do this here instead of the get_sql_queries() method so that we don't hit the db multiple times.
111
+ */
112
+ $sql = "SHOW COLUMNS FROM {$this->_db->prefix}nf3_fields LIKE 'field_key'";
113
+ $results = $this->_db->get_results( $sql );
114
+
115
+ /**
116
+ * If we don't have the field_key column, we need to remove our new columns.
117
+ *
118
+ * Also, set our db stage 1 tracker to false.
119
+ */
120
+ if ( empty ( $results ) ) {
121
+ unset( $this->actions_db_columns[ 'label' ] );
122
+ $db_stage_one_complete = false;
123
+ } else {
124
+ // Add a form value that stores whether or not we have our new DB columns.
125
+ $db_stage_one_complete = true;
126
+ }
127
+
128
+ $this->form[ 'db_stage_one_complete' ] = $db_stage_one_complete;
129
+ }
130
+
131
+ /**
132
+ * On processing steps after the first, we need to grab our data from our saved option.
133
+ *
134
+ * @since 3.4.0
135
+ * @return void
136
+ */
137
+ public function restart()
138
+ {
139
+ // Get our remaining fields from the database.
140
+ $this->form = get_option( 'nf_import_form', $this->form, array() );
141
+ }
142
+
143
+ /**
144
+ * Function to loop over the batch.
145
+ *
146
+ * @since 3.4.0
147
+ * @return void
148
+ */
149
+ public function process()
150
+ {
151
+ /**
152
+ * Check to see if our $this->form var contains an 'ID' index.
153
+ *
154
+ * If it doesn't, then we need to:
155
+ * Insert our Form.
156
+ * Insert our Form Meta.
157
+ * Insert our Actions.
158
+ * Insert our Action Meta.
159
+ * Unset [ 'settings' ] and [ 'actions' ] from $this->form.
160
+ * Update $this->form[ 'ID' ].
161
+ * Save our processing option.
162
+ * Move on to the next step.
163
+ */
164
+ if ( ! isset ( $this->form[ 'ID' ] ) ) {
165
+ $this->insert_form();
166
+ } else { // We have a form ID set.
167
+ $this->insert_fields();
168
+ }
169
+
170
+ // If we don't have any more fields to insert, we're done.
171
+ if ( empty( $this->form[ 'fields' ] ) ) {
172
+ // Update our form cache for the new form.
173
+ WPN_Helper::build_nf_cache( $this->form[ 'ID' ] );
174
+ // We're done with this batch process.
175
+ $this->batch_complete();
176
+ } else { // We have fields left to process.
177
+ /**
178
+ * If we have fields left, we need to reset the index.
179
+ * Since fields is a non-associative array, we are looping over it by sequential numeric index.
180
+ * Resetting the index ensures we always have a 0 -> COUNT() keys.
181
+ */
182
+ $this->form[ 'fields' ] = array_values( $this->form[ 'fields' ] );
183
+ // Save our progress.
184
+ update_option( 'nf_import_form', $this->form, 'no' );
185
+ // Move on to the next step in processing.
186
+ $this->next_step();
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Function to cleanup any lingering temporary elements of a batch process after completion.
192
+ */
193
+ public function cleanup()
194
+ {
195
+ // Remove the option we used to track between
196
+ delete_option( 'nf_import_form' );
197
+ // Return our new Form ID
198
+ $this->response[ 'form_id' ] = $this->form[ 'ID' ];
199
+ }
200
+
201
+ /*
202
+ * Get Steps
203
+ * Determines the amount of steps needed for the step processors.
204
+ *
205
+ * @return int of the number of steps.
206
+ */
207
+ public function get_steps()
208
+ {
209
+ /**
210
+ * We want to run a step for every $this->fields_per_step fields on this form.
211
+ *
212
+ * If we have no fields, then we want to return 0.
213
+ */
214
+ if ( ! isset ( $this->form[ 'fields' ] ) || empty ( $this->form[ 'fields' ] ) ) {
215
+ return 0;
216
+ }
217
+
218
+ $steps = count( $this->form[ 'fields' ] ) / $this->fields_per_step;
219
+ $steps = ceil( $steps );
220
+ return $steps;
221
+ }
222
+
223
+ /**
224
+ * Insert our form using $this->_db->insert by building an array of column => value pairs and %s, %d types.
225
+ *
226
+ * @since 3.4.0
227
+ * @return void
228
+ */
229
+ public function insert_form()
230
+ {
231
+ $insert_columns = array();
232
+ $insert_columns_types = array();
233
+ foreach ( $this->forms_db_columns as $column_name => $setting_name ) {
234
+ $insert_columns[ $column_name ] = $this->form[ 'settings' ][ $setting_name ];
235
+ if ( is_numeric( $this->form[ 'settings' ][ $setting_name ] ) ) {
236
+ array_push( $insert_columns_types, '%d' );
237
+ } else {
238
+ array_push( $insert_columns_types, '%s' );
239
+ }
240
+ }
241
+
242
+ $this->_db->insert( "{$this->_db->prefix}nf3_forms", $insert_columns, $insert_columns_types );
243
+
244
+ // Update our form ID with the newly inserted row ID.
245
+ $this->form[ 'ID' ] = $this->_db->insert_id;
246
+
247
+ $this->insert_form_meta();
248
+ $this->insert_actions();
249
+
250
+ // Remove our settings and actions array items.
251
+ unset( $this->form[ 'settings' ], $this->form[ 'actions' ] );
252
+ }
253
+
254
+ /**
255
+ * Insert Form Meta.
256
+ *
257
+ * Loop over our remaining form settings that we need to insert into meta.
258
+ * Add them to our "Values" string for insertion later.
259
+ *
260
+ * @since 3.4.0
261
+ * @return void
262
+ */
263
+ public function insert_form_meta()
264
+ {
265
+ $insert_values = '';
266
+
267
+ foreach( $this->form[ 'settings' ] as $meta_key => $meta_value ) {
268
+ $meta_value = maybe_serialize( $meta_value );
269
+ $this->_db->escape_by_ref( $meta_value );
270
+ $insert_values .= "( {$this->form[ 'ID' ]}, '{$meta_key}', '{$meta_value}'";
271
+ if ( $this->form[ 'db_stage_one_complete'] ) {
272
+ $insert_values .= ", '{$meta_key}', '{$meta_value}'";
273
+ }
274
+ $insert_values .= "),";
275
+ }
276
+
277
+ // Remove the trailing comma.
278
+ $insert_values = rtrim( $insert_values, ',' );
279
+ $insert_columns = '`parent_id`, `key`, `value`';
280
+ if ( $this->form[ 'db_stage_one_complete'] ) {
281
+ $insert_columns .= ', `meta_key`, `meta_value`';
282
+ }
283
+
284
+ // Create SQL string.
285
+ $sql = "INSERT INTO {$this->_db->prefix}nf3_form_meta ( {$insert_columns} ) VALUES {$insert_values}";
286
+ // Run our SQL query.
287
+ $this->_db->query( $sql );
288
+ }
289
+
290
+ /**
291
+ * Insert Actions and Action Meta.
292
+ *
293
+ * Loop over actions for this form and insert actions and action meta.
294
+ *
295
+ * @since 3.4.0
296
+ * @return void
297
+ */
298
+ public function insert_actions()
299
+ {
300
+ foreach( $this->form[ 'actions' ] as $action_settings ) {
301
+ $action_settings[ 'parent_id' ] = $this->form[ 'ID' ];
302
+ // Array that tracks which settings need to be meta and which are columns.
303
+ $action_meta = $action_settings;
304
+ $insert_columns = array();
305
+ $insert_columns_types = array();
306
+ // Loop over all our action columns to get their values.
307
+ foreach ( $this->actions_db_columns as $column_name => $setting_name ) {
308
+ $insert_columns[ $column_name ] = $action_settings[ $setting_name ];
309
+ if ( is_numeric( $action_settings[ $setting_name ] ) ) {
310
+ array_push( $insert_columns_types, '%d' );
311
+ } else {
312
+ array_push( $insert_columns_types, '%s' );
313
+ }
314
+ }
315
+
316
+ // Insert Action
317
+ $this->_db->insert( "{$this->_db->prefix}nf3_actions", $insert_columns, $insert_columns_types );
318
+
319
+ // Get our new action ID.
320
+ $action_id = $this->_db->insert_id;
321
+
322
+ // Insert Action Meta.
323
+ $insert_values = '';
324
+ /**
325
+ * Anything left in the $action_meta array should be inserted as meta.
326
+ *
327
+ * Loop over each of our settings and add it to our insert sql string.
328
+ */
329
+ $insert_values = '';
330
+ foreach ( $action_meta as $meta_key => $meta_value ) {
331
+ $meta_value = maybe_serialize( $meta_value );
332
+ $this->_db->escape_by_ref( $meta_value );
333
+ $insert_values .= "( {$action_id}, '{$meta_key}', '{$meta_value}'";
334
+ if ( $this->form[ 'db_stage_one_complete'] ) {
335
+ $insert_values .= ", '{$meta_key}', '{$meta_value}'";
336
+ }
337
+ $insert_values .= "),";
338
+ }
339
+
340
+ // Remove the trailing comma.
341
+ $insert_values = rtrim( $insert_values, ',' );
342
+ $insert_columns = '`parent_id`, `key`, `value`';
343
+ if ( $this->form[ 'db_stage_one_complete'] ) {
344
+ $insert_columns .= ', `meta_key`, `meta_value`';
345
+ }
346
+ // Create SQL string.
347
+ $sql = "INSERT INTO {$this->_db->prefix}nf3_action_meta ( {$insert_columns} ) VALUES {$insert_values}";
348
+
349
+ // Run our SQL query.
350
+ $this->_db->query( $sql );
351
+ }
352
+ }
353
+
354
+ /**
355
+ * If we have a Form ID set, then we've already inserted our Form, Form Meta, Actions, and Action Meta.
356
+ * All we have left to insert are fields.
357
+ *
358
+ * Loop over our fields array and insert up to $this->fields_per_step.
359
+ * After we've inserted the field, unset it from our form array.
360
+ * Update our processing option with $this->form.
361
+ * Respond with the remaining steps.
362
+ *
363
+ * @since 3.4.0
364
+ * @return void
365
+ */
366
+ public function insert_fields()
367
+ {
368
+ // Remove new field table columns if we haven't completed stage one of our DB conversion.
369
+ if ( ! $this->form[ 'db_stage_one_complete' ] ) {
370
+ // Remove field columns added after stage one.
371
+ unset( $this->fields_db_columns[ 'field_key' ] );
372
+ unset( $this->fields_db_columns[ 'field_label' ] );
373
+ unset( $this->fields_db_columns[ 'order' ] );
374
+ unset( $this->fields_db_columns[ 'required' ] );
375
+ unset( $this->fields_db_columns[ 'default_value' ] );
376
+ unset( $this->fields_db_columns[ 'label_pos' ] );
377
+ unset( $this->fields_db_columns[ 'personally_identifiable' ] );
378
+ }
379
+
380
+ /**
381
+ * Loop over our field array up to $this->fields_per_step.
382
+ */
383
+ for ( $i = 0; $i < $this->fields_per_step; $i++ ) {
384
+ // If we don't have a field, skip this $i.
385
+ if ( ! isset ( $this->form[ 'fields' ][ $i ] ) ) continue;
386
+
387
+ $field_settings = $this->form[ 'fields' ][ $i ];
388
+ // Remove a field ID if we have one set.
389
+ unset( $field_settings[ 'id' ] );
390
+ $field_settings[ 'parent_id' ] = $this->form[ 'ID' ];
391
+ // Array that tracks which settings need to be meta and which are columns.
392
+ $field_meta = $field_settings;
393
+ $insert_columns = array();
394
+ $insert_columns_types = array();
395
+ // Loop over all our action columns to get their values.
396
+ foreach ( $this->fields_db_columns as $column_name => $setting_name ) {
397
+ $insert_columns[ $column_name ] = $field_settings[ $setting_name ];
398
+ if ( is_numeric( $field_settings[ $setting_name ] ) ) {
399
+ array_push( $insert_columns_types, '%d' );
400
+ } else {
401
+ array_push( $insert_columns_types, '%s' );
402
+ }
403
+ }
404
+
405
+ // Add our field to the database.
406
+ $this->_db->insert( "{$this->_db->prefix}nf3_fields", $insert_columns, $insert_columns_types );
407
+
408
+ /**
409
+ * Get our new field ID.
410
+ */
411
+ $field_id = $this->_db->insert_id;
412
+
413
+ $insert_values = '';
414
+ /**
415
+ * Anything left in the $field_meta array should be inserted as meta.
416
+ *
417
+ * Loop over each of our settings and add it to our insert sql string.
418
+ */
419
+ $insert_values = '';
420
+ foreach ( $field_meta as $meta_key => $meta_value ) {
421
+ $meta_value = maybe_serialize( $meta_value );
422
+ $this->_db->escape_by_ref( $meta_value );
423
+ $insert_values .= "( {$field_id}, '{$meta_key}', '{$meta_value}'";
424
+ if ( $this->form[ 'db_stage_one_complete'] ) {
425
+ $insert_values .= ", '{$meta_key}', '{$meta_value}'";
426
+ }
427
+ $insert_values .= "),";
428
+ }
429
+
430
+ // Remove the trailing comma.
431
+ $insert_values = rtrim( $insert_values, ',' );
432
+ $insert_columns = '`parent_id`, `key`, `value`';
433
+ if ( $this->form[ 'db_stage_one_complete'] ) {
434
+ $insert_columns .= ', `meta_key`, `meta_value`';
435
+ }
436
+ // Create SQL string.
437
+ $sql = "INSERT INTO {$this->_db->prefix}nf3_field_meta ( {$insert_columns} ) VALUES {$insert_values}";
438
+
439
+ // Run our SQL query.
440
+ $this->_db->query( $sql );
441
+
442
+ // Remove this field from our fields array.
443
+ unset( $this->form[ 'fields' ][ $i ] );
444
+ }
445
+ }
446
+
447
+ /*
448
+ |--------------------------------------------------------------------------
449
+ | Backwards Compatibility
450
+ |--------------------------------------------------------------------------
451
+ */
452
+
453
+ public function import_form_backwards_compatibility( $import )
454
+ {
455
+ // Rename `data` to `settings`
456
+ if( isset( $import[ 'data' ] ) ){
457
+ $import[ 'settings' ] = $import[ 'data' ];
458
+ unset( $import[ 'data' ] );
459
+ }
460
+
461
+ // Rename `notifications` to `actions`
462
+ if( isset( $import[ 'notifications' ] ) ){
463
+ $import[ 'actions' ] = $import[ 'notifications' ];
464
+ unset( $import[ 'notifications' ] );
465
+ }
466
+
467
+ // Rename `form_title` to `title`
468
+ if( isset( $import[ 'settings' ][ 'form_title' ] ) ){
469
+ $import[ 'settings' ][ 'title' ] = $import[ 'settings' ][ 'form_title' ];
470
+ unset( $import[ 'settings' ][ 'form_title' ] );
471
+ }
472
+
473
+ // Convert `last_sub` to `_seq_num`
474
+ if( isset( $import[ 'settings' ][ 'last_sub' ] ) ) {
475
+ $import[ 'settings' ][ '_seq_num' ] = $import[ 'settings' ][ 'last_sub' ] + 1;
476
+ }
477
+
478
+ // Make sure
479
+ if( ! isset( $import[ 'fields' ] ) ){
480
+ $import[ 'fields' ] = array();
481
+ }
482
+
483
+ // `Field` to `Fields`
484
+ if( isset( $import[ 'field' ] ) ){
485
+ $import[ 'fields' ] = $import[ 'field' ];
486
+ unset( $import[ 'field' ] );
487
+ }
488
+
489
+ $import = apply_filters( 'ninja_forms_upgrade_settings', $import );
490
+
491
+ // Combine Field and Field Data
492
+ foreach( $import[ 'fields' ] as $key => $field ){
493
+
494
+ if( '_honeypot' == $field[ 'type' ] ) {
495
+ unset( $import[ 'fields' ][ $key ] );
496
+ continue;
497
+ }
498
+
499
+ if( ! $field[ 'type' ] ) {
500
+ unset( $import[ 'fields'][ $key ] );
501
+ continue;
502
+ }
503
+
504
+ // TODO: Split Credit Card field into multiple fields.
505
+ $field = $this->import_field_backwards_compatibility( $field );
506
+
507
+ if( isset( $field[ 'new_fields' ] ) ){
508
+ foreach( $field[ 'new_fields' ] as $new_field ){
509
+ $import[ 'fields' ][] = $new_field;
510
+ }
511
+ unset( $field[ 'new_fields' ] );
512
+ }
513
+
514
+ $import[ 'fields' ][ $key ] = $field;
515
+ }
516
+
517
+ $has_save_action = FALSE;
518
+ foreach( $import[ 'actions' ] as $key => $action ){
519
+ $action = $this->import_action_backwards_compatibility( $action );
520
+ $import[ 'actions' ][ $key ] = $action;
521
+
522
+ if( 'save' == $action[ 'type' ] ) $has_save_action = TRUE;
523
+ }
524
+
525
+ if( ! $has_save_action ) {
526
+ $import[ 'actions' ][] = array(
527
+ 'type' => 'save',
528
+ 'label' => __( 'Save Form', 'ninja-forms' ),
529
+ 'active' => TRUE
530
+ );
531
+ }
532
+
533
+ $import = $this->import_merge_tags_backwards_compatibility( $import );
534
+
535
+ return apply_filters( 'ninja_forms_after_upgrade_settings', $import );
536
+ }
537
+
538
+ public function import_merge_tags_backwards_compatibility( $import )
539
+ {
540
+ $field_lookup = array();
541
+
542
+ foreach( $import[ 'fields' ] as $key => $field ){
543
+
544
+ if( ! isset( $field[ 'id' ] ) ) continue;
545
+
546
+ $field_id = $field[ 'id' ];
547
+ $field_key = $field[ 'type' ] . '_' . $field_id;
548
+ $field_lookup[ $field_id ] = $import[ 'fields' ][ $key ][ 'key' ] = $field_key;
549
+ }
550
+
551
+ foreach( $import[ 'actions' ] as $key => $action_settings ){
552
+ foreach( $action_settings as $setting => $value ){
553
+ foreach( $field_lookup as $field_id => $field_key ){
554
+
555
+ // Convert Tokenizer
556
+ $token = 'field_' . $field_id;
557
+ if( ! is_array( $value ) ) {
558
+ if (FALSE !== strpos($value, $token)) {
559
+ $value = str_replace($token, '{field:' . $field_key . '}', $value);
560
+ }
561
+ }
562
+
563
+ // Convert Shortcodes
564
+ $shortcode = "[ninja_forms_field id=$field_id]";
565
+ if( ! is_array( $value ) ) {
566
+ if ( FALSE !== strpos( $value, $shortcode ) ) {
567
+ $value = str_replace( $shortcode, '{field:' . $field_key . '}', $value );
568
+ }
569
+ }
570
+ }
571
+
572
+ //Checks for the nf_sub_seq_num short code and replaces it with the submission sequence merge tag
573
+ $sub_seq = '[nf_sub_seq_num]';
574
+ if( ! is_array( $value ) ) {
575
+ if( FALSE !== strpos( $value, $sub_seq ) ){
576
+ $value = str_replace( $sub_seq, '{submission:sequence}', $value );
577
+ }
578
+ }
579
+
580
+ if( ! is_array( $value ) ) {
581
+ if (FALSE !== strpos($value, '[ninja_forms_all_fields]')) {
582
+ $value = str_replace('[ninja_forms_all_fields]', '{field:all_fields}', $value);
583
+ }
584
+ }
585
+ $action_settings[ $setting ] = $value;
586
+ $import[ 'actions' ][ $key ] = $action_settings;
587
+ }
588
+ }
589
+
590
+ return $import;
591
+ }
592
+
593
+ public function import_action_backwards_compatibility( $action )
594
+ {
595
+ // Remove `_` from type
596
+ if( isset( $action[ 'type' ] ) ) {
597
+ $action['type'] = str_replace('_', '', $action['type']);
598
+ }
599
+
600
+ if( 'email' == $action[ 'type' ] ){
601
+ $action[ 'to' ] = str_replace( '`', ',', $action[ 'to' ] );
602
+ $action[ 'email_subject' ] = str_replace( '`', ',', $action[ 'email_subject' ] );
603
+ $action[ 'cc' ] = str_replace( '`', ',', $action[ 'cc' ] );
604
+ $action[ 'bcc' ] = str_replace( '`', ',', $action[ 'bcc' ] );
605
+ // If our email is in plain text...
606
+ if ( $action[ 'email_format' ] == 'plain' ) {
607
+ // Record it as such.
608
+ $action[ 'email_message_plain' ] = $action[ 'email_message' ];
609
+ } // Otherwise... (It's not plain text.)
610
+ else {
611
+ // Record it as HTML.
612
+ $action[ 'email_message' ] = nl2br( $action[ 'email_message' ] );
613
+ }
614
+ }
615
+
616
+ // Convert `name` to `label`
617
+ if( isset( $action[ 'name' ] ) ) {
618
+ $action['label'] = $action['name'];
619
+ unset($action['name']);
620
+ }
621
+
622
+ return apply_filters( 'ninja_forms_upgrade_action_' . $action[ 'type' ], $action );
623
+ }
624
+
625
+ public function import_field_backwards_compatibility( $field )
626
+ {
627
+ // Flatten field settings array
628
+ if( isset( $field[ 'data' ] ) && is_array( $field[ 'data' ] ) ){
629
+ $field = array_merge( $field, $field[ 'data' ] );
630
+ }
631
+ unset( $field[ 'data' ] );
632
+
633
+ // Drop form_id in favor of parent_id, which is set by the form.
634
+ if( isset( $field[ 'form_id' ] ) ){
635
+ unset( $field[ 'form_id' ] );
636
+ }
637
+
638
+ // Remove `_` prefix from type setting
639
+ $field[ 'type' ] = ltrim( $field[ 'type' ], '_' );
640
+
641
+ // Type: `text` -> `textbox`
642
+ if( 'text' == $field[ 'type' ] ){
643
+ $field[ 'type' ] = 'textbox';
644
+ }
645
+
646
+ if( 'submit' == $field[ 'type' ] ){
647
+ $field[ 'processing_label' ] = 'Processing';
648
+ }
649
+
650
+ if( isset( $field[ 'email' ] ) ){
651
+
652
+ if( 'textbox' == $field[ 'type' ] && $field[ 'email' ] ) {
653
+ $field['type'] = 'email';
654
+ }
655
+ unset( $field[ 'email' ] );
656
+ }
657
+
658
+ if( isset( $field[ 'class' ] ) ){
659
+ $field[ 'element_class' ] = $field[ 'class' ];
660
+ unset( $field[ 'class' ] );
661
+ }
662
+
663
+ if( isset( $field[ 'req' ] ) ){
664
+ $field[ 'required' ] = $field[ 'req' ];
665
+ unset( $field[ 'req' ] );
666
+ }
667
+
668
+ if( isset( $field[ 'default_value_type' ] ) ){
669
+
670
+ /* User Data */
671
+ if( '_user_id' == $field[ 'default_value_type' ] ) $field[ 'default' ] = '{wp:user_id}';
672
+ if( '_user_email' == $field[ 'default_value_type' ] ) $field[ 'default' ] = '{wp:user_email}';
673
+ if( '_user_lastname' == $field[ 'default_value_type' ] ) $field[ 'default' ] = '{wp:user_last_name}';
674
+ if( '_user_firstname' == $field[ 'default_value_type' ] ) $field[ 'default' ] = '{wp:user_first_name}';
675
+ if( '_user_display_name' == $field[ 'default_value_type' ] ) $field[ 'default' ] = '{wp:user_display_name}';
676
+
677
+ /* Post Data */
678
+ if( 'post_id' == $field[ 'default_value_type' ] ) $field[ 'default' ] = '{wp:post_id}';
679
+ if( 'post_url' == $field[ 'default_value_type' ] ) $field[ 'default' ] = '{wp:post_url}';
680
+ if( 'post_title' == $field[ 'default_value_type' ] ) $field[ 'default' ] = '{wp:post_title}';
681
+
682
+ /* System Data */
683
+ if( 'today' == $field[ 'default_value_type' ] ) $field[ 'default' ] = '{other:date}';
684
+
685
+ /* Miscellaneous */
686
+ if( '_custom' == $field[ 'default_value_type' ] && isset( $field[ 'default_value' ] ) ){
687
+ $field[ 'default' ] = $field[ 'default_value' ];
688
+ }
689
+ if( 'querystring' == $field[ 'default_value_type' ] && isset( $field[ 'default_value' ] ) ){
690
+ $field[ 'default' ] = '{querystring:' . $field[ 'default_value' ] . '}';
691
+ }
692
+
693
+ unset( $field[ 'default_value' ] );
694
+ unset( $field[ 'default_value_type' ] );
695
+ } else if ( isset ( $field[ 'default_value' ] ) ) {
696
+ $field[ 'default' ] = $field[ 'default_value' ];
697
+ }
698
+
699
+ if( 'list' == $field[ 'type' ] ) {
700
+
701
+ if ( isset( $field[ 'list_type' ] ) ) {
702
+
703
+ if ('dropdown' == $field['list_type']) {
704
+ $field['type'] = 'listselect';
705
+ }
706
+ if ('radio' == $field['list_type']) {
707
+ $field['type'] = 'listradio';
708
+ }
709
+ if ('checkbox' == $field['list_type']) {
710
+ $field['type'] = 'listcheckbox';
711
+ }
712
+ if ('multi' == $field['list_type']) {
713
+ $field['type'] = 'listmultiselect';
714
+ }
715
+ }
716
+
717
+ if( isset( $field[ 'list' ][ 'options' ] ) ) {
718
+ $field[ 'options' ] = array_values( $field[ 'list' ][ 'options' ] );
719
+ unset( $field[ 'list' ][ 'options' ] );
720
+ }
721
+
722
+ foreach( $field[ 'options' ] as &$option ){
723
+ if( isset( $option[ 'value' ] ) && $option[ 'value' ] ) continue;
724
+ $option[ 'value' ] = $option[ 'label' ];
725
+ }
726
+ }
727
+
728
+ if( 'country' == $field[ 'type' ] ){
729
+ $field[ 'type' ] = 'listcountry';
730
+ $field[ 'options' ] = array();
731
+ }
732
+
733
+ // Convert `textbox` to other field types
734
+ foreach( array( 'fist_name', 'last_name', 'user_zip', 'user_city', 'user_phone', 'user_email', 'user_address_1', 'user_address_2', 'datepicker' ) as $item ) {
735
+ if ( isset( $field[ $item ] ) && $field[ $item ] ) {
736
+ $field[ 'type' ] = str_replace( array( '_', 'user', '1', '2', 'picker' ), '', $item );
737
+
738
+ unset( $field[ $item ] );
739
+ }
740
+ }
741
+
742
+ if( 'timed_submit' == $field[ 'type' ] ) {
743
+ $field[ 'type' ] = 'submit';
744
+ }
745
+
746
+ if( 'checkbox' == $field[ 'type' ] ){
747
+
748
+ if( isset( $field[ 'calc_value' ] ) ){
749
+
750
+ if( isset( $field[ 'calc_value' ][ 'checked' ] ) ){
751
+ $field[ 'checked_calc_value' ] = $field[ 'calc_value' ][ 'checked' ];
752
+ unset( $field[ 'calc_value' ][ 'checked' ] );
753
+ }
754
+ if( isset( $field[ 'calc_value' ][ 'unchecked' ] ) ){
755
+ $field[ 'unchecked_calc_value' ] = $field[ 'calc_value' ][ 'unchecked' ];
756
+ unset( $field[ 'calc_value' ][ 'unchecked' ] );
757
+ }
758
+ }
759
+ }
760
+
761
+ if( 'rating' == $field[ 'type' ] ){
762
+ $field[ 'type' ] = 'starrating';
763
+
764
+ if( isset( $field[ 'rating_stars' ] ) ){
765
+ $field[ 'default' ] = $field[ 'rating_stars' ];
766
+ unset( $field[ 'rating_stars' ] );
767
+ }
768
+ }
769
+
770
+ if( 'number' == $field[ 'type' ] ){
771
+
772
+ if( ! isset( $field[ 'number_min' ] ) || ! $field[ 'number_min' ] ){
773
+ $field[ 'num_min' ] = '';
774
+ } else {
775
+ $field[ 'num_min' ] = $field[ 'number_min' ];
776
+ }
777
+
778
+ if( ! isset( $field[ 'number_max' ] ) || ! $field[ 'number_max' ] ){
779
+ $field[ 'num_max' ] = '';
780
+ } else {
781
+ $field[ 'num_max' ] = $field[ 'number_max' ];
782
+ }
783
+
784
+ if( ! isset( $field[ 'number_step' ] ) || ! $field[ 'number_step' ] ){
785
+ $field[ 'num_step' ] = 1;
786
+ } else {
787
+ $field[ 'num_step' ] = $field[ 'number_step' ];
788
+ }
789
+ }
790
+
791
+ if( 'profile_pass' == $field[ 'type' ] ){
792
+ $field[ 'type' ] = 'password';
793
+
794
+ $passwordconfirm = array_merge( $field, array(
795
+ 'id' => '',
796
+ 'type' => 'passwordconfirm',
797
+ 'label' => $field[ 'label' ] . ' ' . __( 'Confirm', 'ninja-forms' ),
798
+ 'confirm_field' => 'password_' . $field[ 'id' ]
799
+ ));
800
+ $field[ 'new_fields' ][] = $passwordconfirm;
801
+ }
802
+
803
+ if( 'desc' == $field[ 'type' ] ){
804
+ $field[ 'type' ] = 'html';
805
+ }
806
+
807
+ if( 'credit_card' == $field[ 'type' ] ){
808
+
809
+ $field[ 'type' ] = 'creditcardnumber';
810
+ $field[ 'label' ] = $field[ 'cc_number_label' ];
811
+ $field[ 'label_pos' ] = 'above';
812
+
813
+ if( $field[ 'help_text' ] ){
814
+ $field[ 'help_text' ] = '<p>' . $field[ 'help_text' ] . '</p>';
815
+ }
816
+
817
+ $credit_card_fields = array(
818
+ 'creditcardcvc' => $field[ 'cc_cvc_label' ],
819
+ 'creditcardfullname' => $field[ 'cc_name_label' ],
820
+ 'creditcardexpiration' => $field[ 'cc_exp_month_label' ] . ' ' . $field[ 'cc_exp_year_label' ],
821
+ 'creditcardzip' => __( 'Credit Card Zip', 'ninja-forms' ),
822
+ );
823
+
824
+
825
+ foreach( $credit_card_fields as $new_type => $new_label ){
826
+ $field[ 'new_fields' ][] = array_merge( $field, array(
827
+ 'id' => '',
828
+ 'type' => $new_type,
829
+ 'label' => $new_label,
830
+ 'help_text' => '',
831
+ 'desc_text' => ''
832
+ ));
833
+ }
834
+ }
835
+
836
+ /*
837
+ * Convert inside label position over to placeholder
838
+ */
839
+ if ( isset ( $field[ 'label_pos' ] ) && 'inside' == $field[ 'label_pos' ] ) {
840
+ if ( ! isset ( $field[ 'placeholder' ] ) || empty ( $field[ 'placeholder' ] ) ) {
841
+ $field[ 'placeholder' ] = $field[ 'label' ];
842
+ }
843
+ $field[ 'label_pos' ] = 'hidden';
844
+ }
845
+
846
+ if( isset( $field[ 'desc_text' ] ) ){
847
+ $field[ 'desc_text' ] = nl2br( $field[ 'desc_text' ] );
848
+ }
849
+ if( isset( $field[ 'help_text' ] ) ){
850
+ $field[ 'help_text' ] = nl2br( $field[ 'help_text' ] );
851
+ }
852
+
853
+
854
+ return apply_filters( 'ninja_forms_upgrade_field', $field );
855
+ }
856
+ }
includes/Admin/Processes/ImportFormTemplate.php ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit;
2
+
3
+ /**
4
+ * Class NF_Abstracts_Batch_Process
5
+ */
6
+ class NF_Admin_Processes_ImportFormTemplate extends NF_Admin_Processes_ImportForm
7
+ {
8
+ protected $_slug = 'import_form_template';
9
+
10
+ /**
11
+ * Function to run any setup steps necessary to begin processing.
12
+ */
13
+ public function startup()
14
+ {
15
+ global $wpdb;
16
+
17
+ // If we haven't been passed a template as extraData, then bail.
18
+ if ( ! isset ( $_POST[ 'extraData' ][ 'template' ] ) || empty ( $_POST[ 'extraData' ][ 'template' ] ) ) {
19
+ $this->batch_complete();
20
+ }
21
+
22
+ $template_file_name = $_POST[ 'extraData' ][ 'template' ];
23
+
24
+ /**
25
+ * If our template_file_name is set to 'new', then respond with 'new' as our form id.
26
+ *
27
+ * This will redirect to the builder with a new form.
28
+ */
29
+ if ( 'new' == $template_file_name ) {
30
+ $this->form[ 'ID' ] = 'new';
31
+ $this->batch_complete();
32
+ }
33
+
34
+ // Grab the data from the appropriate file location.
35
+ $registered_templates = Ninja_Forms::config( 'NewFormTemplates' );
36
+
37
+ if( isset( $registered_templates[ $template_file_name ] ) && ! empty( $registered_templates[ $template_file_name ][ 'form' ] ) ) {
38
+ $form_data = $registered_templates[ $template_file_name ][ 'form' ];
39
+ } else {
40
+ $form_data = Ninja_Forms::template( $template_file_name . '.nff', array(), TRUE );
41
+ }
42
+
43
+ /**
44
+ * If we don't have any form data, run cleanup.
45
+ *
46
+ * TODO: We probably need to show an error to the user here.
47
+ */
48
+ if( ! $form_data ) {
49
+ $this->cleanup();
50
+ }
51
+
52
+ $this->form = json_decode( html_entity_decode( $form_data ), true );
53
+
54
+ // Determine how many steps this will take.
55
+ $this->response[ 'step_total' ] = $this->get_steps();
56
+
57
+ /**
58
+ * Check to see if we've got new field columns.
59
+ *
60
+ * We do this here instead of the get_sql_queries() method so that we don't hit the db multiple times.
61
+ */
62
+ $sql = "SHOW COLUMNS FROM {$wpdb->prefix}nf3_fields LIKE 'field_key'";
63
+ $results = $wpdb->get_results( $sql );
64
+
65
+ /**
66
+ * If we don't have the field_key column, we need to remove our new columns.
67
+ *
68
+ * Also, set our db stage 1 tracker to false.
69
+ */
70
+ if ( empty ( $results ) ) {
71
+ unset( $this->actions_db_columns[ 'label' ] );
72
+ $db_stage_one_complete = false;
73
+ } else {
74
+ // Add a form value that stores whether or not we have our new DB columns.
75
+ $db_stage_one_complete = true;
76
+ }
77
+
78
+ $this->form[ 'db_stage_one_complete' ] = $db_stage_one_complete;
79
+
80
+ add_option( 'nf_doing_' . $this->_slug, 'true', false );
81
+ }
82
+ }
includes/Config/BatchProcesses.php ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit;
2
+
3
+ return apply_filters( 'ninja_forms_batch_processes', array(
4
+ 'chunked_publish' => array(
5
+ 'class_name' => 'NF_Admin_Processes_ChunkPublish',
6
+ ),
7
+ 'data_cleanup' => array(
8
+ 'class_name' => 'NF_Admin_Processes_DataCleanup',
9
+ ),
10
+ 'expired_submission_cleanup' => array(
11
+ 'class_name' => 'NF_Admin_Processes_ExpiredSubmissionCleanup',
12
+ ),
13
+ 'import_form' => array(
14
+ 'class_name' => 'NF_Admin_Processes_ImportForm',
15
+ ),
16
+ 'import_form_template' => array(
17
+ 'class_name' => 'NF_Admin_Processes_ImportFormTemplate',
18
+ ),
19
+ ));
includes/Config/NewFormTemplates.php CHANGED
@@ -63,9 +63,9 @@ $templates = array(
63
  ),
64
 
65
  'formtemplate-exportdata' => array(
66
- 'id' => 'formtemplate-exportdata',
67
- 'title' => __( 'Export Data Request', 'ninja-forms' ),
68
- 'template-desc' => __( 'Includes action to add users to WordPress\' personal data export tool, allowing admins to comply with the GDPR and other privacy regulations from the site\'s front end.', 'ninja-forms' ),
69
  ),
70
  );
71
 
63
  ),
64
 
65
  'formtemplate-exportdata' => array(
66
+ 'id' => 'formtemplate-exportdata',
67
+ 'title' => __( 'Export Data Request', 'ninja-forms' ),
68
+ 'template-desc' => __( 'Includes action to add users to WordPress\' personal data export tool, allowing admins to comply with the GDPR and other privacy regulations from the site\'s front end.', 'ninja-forms' ),
69
  ),
70
  );
71
 
includes/Config/PluginSettingsAdvanced.php CHANGED
@@ -113,4 +113,13 @@ return apply_filters( 'ninja_forms_plugin_settings_advanced', array(
113
  'desc' => __( 'This setting maybe helpful if your WordPress installation is not moving expired submissions to the trash properly.', 'ninja-forms' ),
114
  ),
115
 
 
 
 
 
 
 
 
 
 
116
  ));
113
  'desc' => __( 'This setting maybe helpful if your WordPress installation is not moving expired submissions to the trash properly.', 'ninja-forms' ),
114
  ),
115
 
116
+ // Add a button for removing all forms from maintenance
117
+ 'remove_maintenance_mode' => array(
118
+ 'id' => 'remove_maintenance_mode',
119
+ 'type' => 'html',
120
+ 'html' => '<div id="nfRemoveMaintenanceMode" class="button">' . __( 'Remove Maintenance Mode', 'ninja-forms' ) . '</div><span id="nf_maintenanceModeProgress" style="display:none;margin-left:15px;"></span>',
121
+ 'label' => __( 'Remove Maintenance Mode', 'ninja-forms' ),
122
+ 'desc' => __( 'Click this button if any of your forms are still in \'Maintenance Mode\' after performing any required updates.' , 'ninja-forms' ),
123
+ ),
124
+
125
  ));
includes/Config/RequiredUpdates.php ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit;
2
+
3
+ return apply_filters( 'ninja_forms_required_updates', array(
4
+
5
+ 'CacheCollateActions' => array(
6
+ 'class_name' => 'NF_Updates_CacheCollateActions',
7
+ 'requires' => array( 'CacheCollateForms' ),
8
+ 'nicename' => __( 'Update Actions Tables', 'ninja-forms' ),
9
+ ),
10
+ 'CacheCollateForms' => array(
11
+ 'class_name' => 'NF_Updates_CacheCollateForms',
12
+ 'requires' => array(),
13
+ 'nicename' => __( 'Update Forms Tables', 'ninja-forms' ),
14
+ ),
15
+ 'CacheCollateFields' => array(
16
+ 'class_name' => 'NF_Updates_CacheCollateFields',
17
+ 'requires' => array( 'CacheCollateActions' ),
18
+ 'nicename' => __( 'Update Fields Tables', 'ninja-forms' ),
19
+ ),
20
+ 'CacheCollateObjects' => array(
21
+ 'class_name' => 'NF_Updates_CacheCollateObjects',
22
+ 'requires' => array( 'CacheCollateFields' ),
23
+ 'nicename' => __( 'Update Objects Tables', 'ninja-forms' ),
24
+ ),
25
+ 'CacheCollateCleanup' => array(
26
+ 'class_name' => 'NF_Updates_CacheCollateCleanup',
27
+ 'requires' => array( 'CacheCollateObjects' ),
28
+ 'nicename' => __( 'Cleanup Orphan Records', 'ninja-forms' ),
29
+ ),
30
+
31
+ ));
includes/Database/FieldsController.php CHANGED
@@ -8,15 +8,88 @@ final class NF_Database_FieldsController
8
  private $insert_fields;
9
  private $insert_field_meta = array();
10
  private $insert_field_meta_chunk = 0;
11
- private $update_fields = array( 'key' => '', 'label' => '', 'type' => '' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  private $update_field_meta = array();
13
  private $update_field_meta_chunk = 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  public function __construct( $form_id, $fields_data )
15
  {
16
  global $wpdb;
17
  $this->db = $wpdb;
18
  $this->form_id = $form_id;
19
  $this->fields_data = $fields_data;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  }
21
  public function run()
22
  {
@@ -49,14 +122,67 @@ final class NF_Database_FieldsController
49
  {
50
  foreach( $this->fields_data as $field_data ){
51
  $field_id = $field_data[ 'id' ];
52
- $settings = array(
53
- 'key' => $field_data[ 'settings' ][ 'key' ],
54
- 'label' => $field_data[ 'settings' ][ 'label' ],
55
- 'type' => $field_data[ 'settings' ][ 'type' ]
56
- );
57
- if( ! is_numeric( $field_id ) ) {
58
- $this->insert_field( $settings ); // New Field.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  } else {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  $this->update_field( $field_id, $settings );
61
  }
62
  }
@@ -70,7 +196,7 @@ final class NF_Database_FieldsController
70
  // we don't need object type or domain stored in the db
71
  if( ! in_array( $key, array( 'objectType', 'objectDomain' ) ) ) {
72
  if( isset( $existing_meta[ $field_id ][ $key ] ) ){
73
- if( $value == $existing_meta[ $field_id ][ $key ] ) continue;
74
  $this->update_field_meta( $field_id, $key, $value );
75
  } else {
76
  $this->insert_field_meta( $field_id, $key, $value );
@@ -81,8 +207,18 @@ final class NF_Database_FieldsController
81
  }
82
  private function get_existing_meta()
83
  {
 
 
 
 
 
 
 
 
 
 
84
  $results = $this->db->get_results("
85
- SELECT m.parent_id, m.key, m.value
86
  FROM `{$this->db->prefix}nf3_field_meta` AS m
87
  LEFT JOIN `{$this->db->prefix}nf3_fields` AS f
88
  ON m.parent_id = f.id
@@ -92,6 +228,12 @@ final class NF_Database_FieldsController
92
  foreach( $results as $meta ){
93
  if( ! isset( $field_meta[ $meta->parent_id ] ) ) $field_meta[ $meta->parent_id ] = array();
94
  $field_meta[ $meta->parent_id ][ $meta->key ] = $meta->value;
 
 
 
 
 
 
95
  }
96
  return $field_meta;
97
  }
@@ -121,20 +263,45 @@ final class NF_Database_FieldsController
121
  */
122
  private function insert_field( $settings )
123
  {
 
124
  $this->insert_fields .= "(";
125
- foreach ( $settings as $setting => $value ) {
 
 
 
 
 
 
 
126
  $this->db->escape_by_ref( $value );
127
- $this->insert_fields .= "'{$value}',";
 
 
 
 
 
128
  }
129
- $this->insert_fields .= "'{$this->form_id}'";
 
130
  $this->insert_fields .= '),';
131
  }
132
  public function get_insert_fields_query()
133
  {
134
  if( ! $this->insert_fields ) return "";
135
  $insert_fields = rtrim( $this->insert_fields, ',' ); // Strip trailing comma from SQl.
 
 
 
 
 
 
 
 
 
 
 
136
  return "
137
- INSERT INTO {$this->db->prefix}nf3_fields ( `key`, `label`, `type`, `parent_id` )
138
  VALUES {$insert_fields}
139
  ";
140
  }
@@ -148,29 +315,50 @@ final class NF_Database_FieldsController
148
  foreach ( $settings as $setting => $value ) {
149
  $line = "WHEN `id` = '{$field_id}' ";
150
  $this->db->escape_by_ref( $value );
151
- $line .= "THEN '{$value}'";
 
 
 
 
 
 
152
  $this->update_fields[ $setting ] .= $line;
153
  }
154
  }
155
  public function get_update_fields_query()
156
  {
157
- if(
158
- empty( $this->update_fields[ 'key' ] ) ||
159
- empty( $this->update_fields[ 'label' ] ) ||
160
- empty( $this->update_fields[ 'type' ] )
161
- ) return "";
162
- return "
163
- UPDATE {$this->db->prefix}nf3_fields
164
- SET `key` = CASE {$this->update_fields[ 'key' ]}
165
- ELSE `key`
166
- END
167
- , `label` = CASE {$this->update_fields[ 'label' ]}
168
- ELSE `label`
169
- END
170
- , `type` = CASE {$this->update_fields[ 'type' ]}
171
- ELSE `type`
 
 
 
 
 
 
 
 
 
 
 
172
  END
173
- ";
 
 
 
 
174
  }
175
  /*
176
  |--------------------------------------------------------------------------
@@ -190,17 +378,38 @@ final class NF_Database_FieldsController
190
  if( ! isset( $this->insert_field_meta[ $this->insert_field_meta_chunk ] ) || ! $this->insert_field_meta[ $this->insert_field_meta_chunk ] ) {
191
  $this->insert_field_meta[ $this->insert_field_meta_chunk ] = '';
192
  }
193
- $this->insert_field_meta[ $this->insert_field_meta_chunk ] .= "('{$field_id}','{$key}','{$value}' ),";
 
 
 
 
 
 
 
 
 
 
 
194
  $counter++;
195
  if( 0 == $counter % 5000 ) $this->insert_field_meta_chunk++;
196
  }
 
197
  public function run_insert_field_meta_query()
198
  {
199
  if( ! $this->insert_field_meta ) return "";
200
  foreach( $this->insert_field_meta as $insert_field_meta ){
201
  $insert_field_meta = rtrim( $insert_field_meta, ',' ); // Strip trailing comma from SQl.
 
 
 
 
 
 
 
 
 
202
  $this->db->query( "
203
- INSERT INTO {$this->db->prefix}nf3_field_meta ( `parent_id`, `key`, `value` )
204
  VALUES {$insert_field_meta}
205
  ");
206
  }
@@ -229,10 +438,17 @@ final class NF_Database_FieldsController
229
  {
230
  if( empty( $this->update_field_meta ) ) return '';
231
  foreach( $this->update_field_meta as $update_field_meta ){
232
- $this->db->query("
233
- UPDATE {$this->db->prefix}nf3_field_meta as field_meta
234
- SET `value` = CASE {$update_field_meta} ELSE `value` END
235
- ");
 
 
 
 
 
 
 
236
  return;
237
  }
238
  }
8
  private $insert_fields;
9
  private $insert_field_meta = array();
10
  private $insert_field_meta_chunk = 0;
11
+ /**
12
+ * An array of UPDATE SQL strings.
13
+ *
14
+ * i.e. array( 'key' => 'WHERE `id` = X THEN...' )
15
+ *
16
+ * @var array
17
+ */
18
+ private $update_fields = array(
19
+ 'id' => '',
20
+ 'key' => '',
21
+ 'label' => '',
22
+ 'type' => '',
23
+ 'field_key' => '',
24
+ 'field_label' => '',
25
+ 'order' => '',
26
+ 'default_value' => '',
27
+ 'label_pos' => '',
28
+ 'required' => '',
29
+ 'personally_identifiable' => '',
30
+ );
31
  private $update_field_meta = array();
32
  private $update_field_meta_chunk = 0;
33
+
34
+ private $db_stage_1_complete = true;
35
+
36
+ /**
37
+ * Store an array of columns that we want to store in our table rather than meta.
38
+ *
39
+ * This array stores the column name and the name of the setting that it maps to.
40
+ *
41
+ * The format is:
42
+ *
43
+ * array( 'COLUMN_NAME' => 'SETTING_NAME' )
44
+ */
45
+ private $db_columns = array(
46
+ 'parent_id' => 'parent_id',
47
+ 'id' => 'id',
48
+ 'key' => 'key',
49
+ 'type' => 'type',
50
+ 'label' => 'label',
51
+ 'field_key' => 'key',
52
+ 'field_label' => 'label',
53
+ 'order' => 'order',
54
+ 'required' => 'required',
55
+ 'default_value' => 'default',
56
+ 'label_pos' => 'label_pos',
57
+ 'personally_identifiable' => 'personally_identifiable',
58
+ );
59
+
60
+ private $db_bit_columns = array(
61
+ 'required',
62
+ 'personally_identifiable',
63
+ );
64
+
65
  public function __construct( $form_id, $fields_data )
66
  {
67
  global $wpdb;
68
  $this->db = $wpdb;
69
  $this->form_id = $form_id;
70
  $this->fields_data = $fields_data;
71
+
72
+ /**
73
+ * Remove new DB columns from our $db_columns list if the user hasn't completed required upgrades stage 1.
74
+ */
75
+ $sql = "SHOW COLUMNS FROM {$this->db->prefix}nf3_fields LIKE 'field_key'";
76
+ $results = $this->db->get_results( $sql );
77
+ /**
78
+ * If we don't have the field_key column, we need to remove our new columns.
79
+ *
80
+ * Also, set our db stage 1 tracker to false.
81
+ */
82
+ if ( empty ( $results ) ) {
83
+ unset( $this->db_columns[ 'field_key' ] );
84
+ unset( $this->db_columns[ 'field_label' ] );
85
+ unset( $this->db_columns[ 'order' ] );
86
+ unset( $this->db_columns[ 'required' ] );
87
+ unset( $this->db_columns[ 'default_value' ] );
88
+ unset( $this->db_columns[ 'label_pos' ] );
89
+ unset( $this->db_columns[ 'personally_identifiable' ] );
90
+
91
+ $this->db_stage_1_complete = false;
92
+ }
93
  }
94
  public function run()
95
  {
122
  {
123
  foreach( $this->fields_data as $field_data ){
124
  $field_id = $field_data[ 'id' ];
125
+
126
+ /**
127
+ * We've defined which items go into our DB, as well as which settings they map to.
128
+ *
129
+ * Loop over our $db_columns array and setup an array for $settings.
130
+ */
131
+ $settings = array();
132
+
133
+ foreach( $this->db_columns as $column_name => $setting_name ) {
134
+ // If the setting value is numeric, make sure it's intval'd.
135
+ if ( is_numeric( $field_data[ 'settings' ][ $setting_name ] ) ) {
136
+ $value = intval( $field_data[ 'settings' ][ $setting_name ] );
137
+ } else {
138
+ $value = $field_data[ 'settings' ][ $setting_name ];
139
+ }
140
+
141
+ if ( in_array( $column_name, $this->db_bit_columns ) ) {
142
+ $value = absint( $value );
143
+ }
144
+
145
+ $settings[ $column_name ] = $value;
146
+ }
147
+
148
+ /**
149
+ * We need to decide if we need to insert this field or update it in our fields table.
150
+ *
151
+ * If we don't have a numeric field id, we're dealing with a tmp field, which is a new field
152
+ *
153
+ * If this field exists in our cache, but doesn't exist in our table, we need to insert it.
154
+ *
155
+ * Otherwise, we're updating.
156
+ *
157
+ * Check our DB for a field with this id.
158
+ */
159
+ if ( is_numeric( $field_id ) ) {
160
+ $field_in_db = $this->db->get_row( "SELECT `id` FROM `wp_nf3_fields` WHERE `id` = {$field_id}" );
161
  } else {
162
+ $field_in_db = array();
163
+ }
164
+
165
+ /**
166
+ * If $field_id isn't a number, then it's a tmp-id.
167
+ *
168
+ * If we have a tmp-id OR the field hasn't been found in our DB, we need to insert it.
169
+ */
170
+ if( ! is_numeric( $field_id ) || empty( $field_in_db ) ) {
171
+
172
+ /**
173
+ * If our $field_id is numeric, we want to insert it into the db with the row.
174
+ *
175
+ * If it's not, we want to pass NULL so that we get an autoincrement.
176
+ */
177
+ if ( is_numeric( $field_id ) ) {
178
+ $settings[ 'id' ] = $field_id;
179
+ } else {
180
+ $settings[ 'id' ] = NULL;
181
+ }
182
+ // New Field.
183
+ $this->insert_field( $settings );
184
+ } else {
185
+ // We're updating field settings.
186
  $this->update_field( $field_id, $settings );
187
  }
188
  }
196
  // we don't need object type or domain stored in the db
197
  if( ! in_array( $key, array( 'objectType', 'objectDomain' ) ) ) {
198
  if( isset( $existing_meta[ $field_id ][ $key ] ) ){
199
+ if( $value == $existing_meta[ $field_id ][ $key ] && $value == $existing_meta[ $field_id ][ 'meta_key' ][ $key ] ) continue;
200
  $this->update_field_meta( $field_id, $key, $value );
201
  } else {
202
  $this->insert_field_meta( $field_id, $key, $value );
207
  }
208
  private function get_existing_meta()
209
  {
210
+
211
+ $sql_select = "m.parent_id, m.key, m.value";
212
+
213
+ /**
214
+ * If we have completed stage 1 of our db migration, pull meta_key and meta_value as well as key and value.
215
+ */
216
+ if ( $this->db_stage_1_complete ) {
217
+ $sql_select .= " , m.meta_key, m.meta_value";
218
+ }
219
+
220
  $results = $this->db->get_results("
221
+ SELECT {$sql_select}
222
  FROM `{$this->db->prefix}nf3_field_meta` AS m
223
  LEFT JOIN `{$this->db->prefix}nf3_fields` AS f
224
  ON m.parent_id = f.id
228
  foreach( $results as $meta ){
229
  if( ! isset( $field_meta[ $meta->parent_id ] ) ) $field_meta[ $meta->parent_id ] = array();
230
  $field_meta[ $meta->parent_id ][ $meta->key ] = $meta->value;
231
+ if ( is_null( $meta->meta_value ) ) {
232
+ $meta_value = '';
233
+ } else {
234
+ $meta_value = $meta->meta_value;
235
+ }
236
+ $field_meta[ $meta->parent_id ]['meta_key'][ $meta->key ] = $meta_value;
237
  }
238
  return $field_meta;
239
  }
263
  */
264
  private function insert_field( $settings )
265
  {
266
+ // Add our initial opening parenthesis.
267
  $this->insert_fields .= "(";
268
+ // Add our form id to our settings as 'parent_id'.
269
+ $settings[ 'parent_id' ] = $this->form_id;
270
+
271
+ /**
272
+ * Loop over each of our $this->db_columns to create a value list for our SQL statement.
273
+ */
274
+ foreach ( $this->db_columns as $column_name => $setting_name ) {
275
+ $value = $settings[ $column_name ];
276
  $this->db->escape_by_ref( $value );
277
+ if ( is_numeric( $value ) ) {
278
+ $this->insert_fields .= "{$value},";
279
+ } else {
280
+ $this->insert_fields .= "'{$value}',";
281
+ }
282
+
283
  }
284
+ // Remove any trailing commas from our SQL string.
285
+ $this->insert_fields = rtrim( $this->insert_fields, ',' );
286
  $this->insert_fields .= '),';
287
  }
288
  public function get_insert_fields_query()
289
  {
290
  if( ! $this->insert_fields ) return "";
291
  $insert_fields = rtrim( $this->insert_fields, ',' ); // Strip trailing comma from SQl.
292
+
293
+ /**
294
+ * Loop over each of our $this->db_columns to create a column list for our SQL statement below.
295
+ */
296
+ $columns = '';
297
+ foreach( $this->db_columns as $column_name => $setting_name ) {
298
+ $columns .= "`{$column_name}` ,";
299
+ }
300
+
301
+ $columns = rtrim( $columns, ',' );
302
+
303
  return "
304
+ INSERT INTO {$this->db->prefix}nf3_fields ( {$columns} )
305
  VALUES {$insert_fields}
306
  ";
307
  }
315
  foreach ( $settings as $setting => $value ) {
316
  $line = "WHEN `id` = '{$field_id}' ";
317
  $this->db->escape_by_ref( $value );
318
+ $line .= "THEN ";
319
+ if ( is_numeric( $value ) ) {
320
+ $line .= "{$value} ";
321
+ } else {
322
+ $line .= "'{$value}' ";
323
+ }
324
+
325
  $this->update_fields[ $setting ] .= $line;
326
  }
327
  }
328
  public function get_update_fields_query()
329
  {
330
+ /**
331
+ * Loop over our $db_columns class var and make sure that none of them are empty.
332
+ *
333
+ * If they are empty, return an empty string to prevent errors.
334
+ */
335
+
336
+ foreach ( $this->db_columns as $column_name => $setting_name ) {
337
+ if ( empty( $this->update_fields[ $column_name ] ) ) {
338
+ return "";
339
+ }
340
+ }
341
+
342
+ /**
343
+ * Build our return statement based upon our $db_columns class var.
344
+ */
345
+
346
+ $return = "UPDATE {$this->db->prefix}nf3_fields
347
+ SET ";
348
+
349
+ foreach( $this->db_columns as $column_name => $setting_name ) {
350
+ // We don't need to update our parent_id or our id.
351
+ if ( 'id' == $column_name || 'parent_id' == $column_name ) {
352
+ continue;
353
+ }
354
+ $return .= "`{$column_name}` = CASE {$this->update_fields[ $column_name ]}
355
+ ELSE `{$column_name}`
356
  END
357
+ ,";
358
+ }
359
+
360
+ $return = rtrim( $return, ',' );
361
+ return $return;
362
  }
363
  /*
364
  |--------------------------------------------------------------------------
378
  if( ! isset( $this->insert_field_meta[ $this->insert_field_meta_chunk ] ) || ! $this->insert_field_meta[ $this->insert_field_meta_chunk ] ) {
379
  $this->insert_field_meta[ $this->insert_field_meta_chunk ] = '';
380
  }
381
+
382
+ $insert_values = "'{$field_id}','{$key}','{$value}'";
383
+
384
+ /**
385
+ * If we have completed stage 1 of our db update process, then we want to add meta_key and meta_value as well as key and value.
386
+ */
387
+ if ( $this->db_stage_1_complete ) {
388
+ $insert_values .= ", '{$key}','{$value}'";
389
+ }
390
+
391
+ $this->insert_field_meta[ $this->insert_field_meta_chunk ] .= "( {$insert_values} ),";
392
+
393
  $counter++;
394
  if( 0 == $counter % 5000 ) $this->insert_field_meta_chunk++;
395
  }
396
+
397
  public function run_insert_field_meta_query()
398
  {
399
  if( ! $this->insert_field_meta ) return "";
400
  foreach( $this->insert_field_meta as $insert_field_meta ){
401
  $insert_field_meta = rtrim( $insert_field_meta, ',' ); // Strip trailing comma from SQl.
402
+
403
+ /**
404
+ * If we have completed stage 1 of our db update process, then we want to insert meta_key and meta_value as well.
405
+ */
406
+ $insert_columns = '`parent_id`, `key`, `value`';
407
+ if ( $this->db_stage_1_complete ) {
408
+ $insert_columns .= ', `meta_key`, `meta_value`';
409
+ }
410
+
411
  $this->db->query( "
412
+ INSERT INTO {$this->db->prefix}nf3_field_meta ( {$insert_columns} )
413
  VALUES {$insert_field_meta}
414
  ");
415
  }
438
  {
439
  if( empty( $this->update_field_meta ) ) return '';
440
  foreach( $this->update_field_meta as $update_field_meta ){
441
+
442
+ $sql = "UPDATE {$this->db->prefix}nf3_field_meta as field_meta
443
+ SET `value` = CASE {$update_field_meta} ELSE `value` END";
444
+ /**
445
+ * If we have completed stage 1 of our db update process, then we want to update meta_value as well as value.
446
+ */
447
+ if ( $this->db_stage_1_complete ) {
448
+ $sql .= ", `meta_value` = CASE {$update_field_meta} ELSE `meta_value` END, `meta_key` = CASE WHEN `parent_id` = '-999' THEN NULL ELSE `key` END";
449
+ }
450
+
451
+ $this->db->query( $sql );
452
  return;
453
  }
454
  }
includes/Database/Migrations.php CHANGED
@@ -4,6 +4,14 @@ class NF_Database_Migrations
4
  {
5
  protected $migrations = array();
6
 
 
 
 
 
 
 
 
 
7
  public function __construct()
8
  {
9
  $this->migrations[ 'forms' ] = new NF_Database_Migrations_Forms();
@@ -20,29 +28,40 @@ class NF_Database_Migrations
20
  $this->migrations[ 'chunks' ] = new NF_Database_Migrations_Chunks();
21
  }
22
 
 
 
 
 
 
 
23
  public function migrate()
24
  {
25
  foreach( $this->migrations as $migration ){
26
  $migration->_run();
27
  }
28
  }
29
-
 
30
  /**
31
- * Function to run all our stage one changes.
 
 
 
 
32
  */
33
- public function do_stage_one()
34
  {
35
  foreach( $this->migrations as $migration ) {
36
- $migration->_stage_one();
37
  }
38
  }
39
 
40
  /**
41
  * This function drops ninja forms tables and options
42
  *
43
- * @param $areYouSure
44
- * @param $areYouReallySure
45
- * @param $nuke_multisite
46
  *
47
  * @since 2.9.34
48
  * @updated 3.3.16
@@ -72,6 +91,12 @@ class NF_Database_Migrations
72
  }
73
  }
74
 
 
 
 
 
 
 
75
  private function _nuke()
76
  {
77
  global $wpdb;
@@ -87,6 +112,15 @@ class NF_Database_Migrations
87
  $wpdb->query( "DELETE FROM `{$wpdb->options}` WHERE `option_name` LIKE '_transient_timeout_nf_form_%'" );
88
  }
89
 
 
 
 
 
 
 
 
 
 
90
  public function nuke_settings( $areYouSure = FALSE, $areYouReallySure = FALSE )
91
  {
92
  if( ! $areYouSure || ! $areYouReallySure ) return;
@@ -107,6 +141,12 @@ class NF_Database_Migrations
107
  }
108
  }
109
 
 
 
 
 
 
 
110
  private function _nuke_settings()
111
  {
112
  global $wpdb;
@@ -134,7 +174,16 @@ class NF_Database_Migrations
134
  $wpdb->query( "DELETE FROM `{$wpdb->options}` WHERE `option_name` LIKE 'wp_nf_update_fields_%'" );
135
  }
136
 
137
- public function nuke_deprecated( $areYouSure = FALSE, $areYouReallySure = FALSE )
 
 
 
 
 
 
 
 
 
138
  {
139
  if( ! $areYouSure || ! $areYouReallySure ) return;
140
 
@@ -154,6 +203,12 @@ class NF_Database_Migrations
154
  }
155
  }
156
 
 
 
 
 
 
 
157
  private function _nuke_deprecated()
158
  {
159
  global $wpdb;
4
  {
5
  protected $migrations = array();
6
 
7
+
8
+ /**
9
+ * Constructor method for the NF_Database_Migrations class.
10
+ *
11
+ * @since 3.0.0
12
+ *
13
+ * @updated 3.3.8
14
+ */
15
  public function __construct()
16
  {
17
  $this->migrations[ 'forms' ] = new NF_Database_Migrations_Forms();
28
  $this->migrations[ 'chunks' ] = new NF_Database_Migrations_Chunks();
29
  }
30
 
31
+
32
+ /**
33
+ * Function to run each migration on the stack.
34
+ *
35
+ * @since 3.0.0
36
+ */
37
  public function migrate()
38
  {
39
  foreach( $this->migrations as $migration ){
40
  $migration->_run();
41
  }
42
  }
43
+
44
+
45
  /**
46
+ * Function to run any required database upgrades.
47
+ *
48
+ * @param $callback (String) The method this upgrade will call from individual migration files.
49
+ *
50
+ * @since 3.4.0
51
  */
52
+ public function do_upgrade( $callback )
53
  {
54
  foreach( $this->migrations as $migration ) {
55
+ $migration->_do_upgrade( $callback );
56
  }
57
  }
58
 
59
  /**
60
  * This function drops ninja forms tables and options
61
  *
62
+ * @param $areYouSure (Boolean)
63
+ * @param $areYouReallySure (Boolean)
64
+ * @param $nuke_multisite (Boolean)
65
  *
66
  * @since 2.9.34
67
  * @updated 3.3.16
91
  }
92
  }
93
 
94
+
95
+ /**
96
+ * Function to handle the actual deletion of tables and caches.
97
+ *
98
+ * @since 3.1.0
99
+ */
100
  private function _nuke()
101
  {
102
  global $wpdb;
112
  $wpdb->query( "DELETE FROM `{$wpdb->options}` WHERE `option_name` LIKE '_transient_timeout_nf_form_%'" );
113
  }
114
 
115
+
116
+ /**
117
+ * Function to nuke our 3.0 settings.
118
+ *
119
+ * @param $areYouSure (Boolean)
120
+ * @param $areYouReallySure (Boolean)
121
+ *
122
+ * @since 3.1.0
123
+ */
124
  public function nuke_settings( $areYouSure = FALSE, $areYouReallySure = FALSE )
125
  {
126
  if( ! $areYouSure || ! $areYouReallySure ) return;
141
  }
142
  }
143
 
144
+
145
+ /**
146
+ * Function to handle the actual deletion of our 3.0 settings.
147
+ *
148
+ * @since 3.1.0
149
+ */
150
  private function _nuke_settings()
151
  {
152
  global $wpdb;
174
  $wpdb->query( "DELETE FROM `{$wpdb->options}` WHERE `option_name` LIKE 'wp_nf_update_fields_%'" );
175
  }
176
 
177
+
178
+ /**
179
+ * Function to nuke our 2.9 database tables.
180
+ *
181
+ * @param $areYouSure (Boolean)
182
+ * @param $areYouReallySure (Boolean)
183
+ *
184
+ * @since 3.1.0
185
+ */
186
+ public function nuke_deprecated( $areYouSure = FALSE, $areYouReallySure = FALSE )
187
  {
188
  if( ! $areYouSure || ! $areYouReallySure ) return;
189
 
203
  }
204
  }
205
 
206
+
207
+ /**
208
+ * Function to handle the actual deletion of deprecated tables and options.
209
+ *
210
+ * @since 3.1.0
211
+ */
212
  private function _nuke_deprecated()
213
  {
214
  global $wpdb;
includes/Database/Migrations/ActionMeta.php CHANGED
@@ -2,6 +2,12 @@
2
 
3
  class NF_Database_Migrations_ActionMeta extends NF_Abstracts_Migration
4
  {
 
 
 
 
 
 
5
  public function __construct()
6
  {
7
  parent::__construct(
@@ -10,6 +16,14 @@ class NF_Database_Migrations_ActionMeta extends NF_Abstracts_Migration
10
  );
11
  }
12
 
 
 
 
 
 
 
 
 
13
  public function run()
14
  {
15
  $query = "CREATE TABLE IF NOT EXISTS {$this->table_name()} (
@@ -17,10 +31,34 @@ class NF_Database_Migrations_ActionMeta extends NF_Abstracts_Migration
17
  `parent_id` int NOT NULL,
18
  `key` longtext NOT NULL,
19
  `value` longtext,
 
 
20
  UNIQUE KEY (`id`)
21
- ) {$this->charset_collate()};";
22
 
23
  dbDelta( $query );
24
  }
25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  }
2
 
3
  class NF_Database_Migrations_ActionMeta extends NF_Abstracts_Migration
4
  {
5
+
6
+ /**
7
+ * Constructor method for the NF_Database_Migrations_ActionMeta class.
8
+ *
9
+ * @since 3.0.0
10
+ */
11
  public function __construct()
12
  {
13
  parent::__construct(
16
  );
17
  }
18
 
19
+
20
+ /**
21
+ * Function to run our initial migration.
22
+ *
23
+ * @since 3.0.0
24
+ *
25
+ * @updated 3.4.0
26
+ */
27
  public function run()
28
  {
29
  $query = "CREATE TABLE IF NOT EXISTS {$this->table_name()} (
31
  `parent_id` int NOT NULL,
32
  `key` longtext NOT NULL,
33
  `value` longtext,
34
+ `meta_key` longtext,
35
+ `meta_value` longtext,
36
  UNIQUE KEY (`id`)
37
+ ) {$this->charset_collate( true )};";
38
 
39
  dbDelta( $query );
40
  }
41
 
42
+
43
+ /**
44
+ * Function to be run as part of our CacheCollateActions required update.
45
+ *
46
+ * @since 3.3.12
47
+ *
48
+ * @updated 3.4.0
49
+ */
50
+ public function cache_collate_actions()
51
+ {
52
+ // If the meta_key column has not already been defined...
53
+ if ( ! $this->column_exists( 'meta_key' ) ) {
54
+ global $wpdb;
55
+ // Modify our table.
56
+ $query = "ALTER TABLE {$this->table_name()}
57
+ ADD `meta_key` longtext {$this->charset_collate()},
58
+ ADD `meta_value` longtext {$this->charset_collate()};";
59
+
60
+ $wpdb->query( $query );
61
+ }
62
+ }
63
+
64
  }
includes/Database/Migrations/Actions.php CHANGED
@@ -2,6 +2,12 @@
2
 
3
  class NF_Database_Migrations_Actions extends NF_Abstracts_Migration
4
  {
 
 
 
 
 
 
5
  public function __construct()
6
  {
7
  parent::__construct(
@@ -10,6 +16,14 @@ class NF_Database_Migrations_Actions extends NF_Abstracts_Migration
10
  );
11
  }
12
 
 
 
 
 
 
 
 
 
13
  public function run()
14
  {
15
  $query = "CREATE TABLE IF NOT EXISTS {$this->table_name()} (
@@ -21,10 +35,33 @@ class NF_Database_Migrations_Actions extends NF_Abstracts_Migration
21
  `parent_id` int NOT NULL,
22
  `created_at` TIMESTAMP,
23
  `updated_at` DATETIME,
 
24
  UNIQUE KEY (`id`)
25
- ) {$this->charset_collate()};";
26
 
27
  dbDelta( $query );
28
  }
29
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  }
2
 
3
  class NF_Database_Migrations_Actions extends NF_Abstracts_Migration
4
  {
5
+
6
+ /**
7
+ * Constructor method for the NF_Database_Migrations_Actions class.
8
+ *
9
+ * @since 3.0.0
10
+ */
11
  public function __construct()
12
  {
13
  parent::__construct(
16
  );
17
  }
18
 
19
+
20
+ /**
21
+ * Function to run our initial migration.
22
+ *
23
+ * @since 3.0.0
24
+ *
25
+ * @updated 3.4.0
26
+ */
27
  public function run()
28
  {
29
  $query = "CREATE TABLE IF NOT EXISTS {$this->table_name()} (
35
  `parent_id` int NOT NULL,
36
  `created_at` TIMESTAMP,
37
  `updated_at` DATETIME,
38
+ `label` longtext,
39
  UNIQUE KEY (`id`)
40
+ ) {$this->charset_collate( true )};";
41
 
42
  dbDelta( $query );
43
  }
44
 
45
+
46
+ /**
47
+ * Function to be run as part of our CacheCollateActions required update.
48
+ *
49
+ * @since 3.3.12
50
+ *
51
+ * @updated 3.4.0
52
+ */
53
+ public function cache_collate_actions()
54
+ {
55
+ // If the label column has not already been defined...
56
+ if ( ! $this->column_exists( 'label' ) ) {
57
+ global $wpdb;
58
+ // Modify our table.
59
+ $query = "ALTER TABLE {$this->table_name()}
60
+ ADD `label` longtext {$this->charset_collate()},
61
+ MODIFY `type` longtext {$this->charset_collate()};";
62
+
63
+ $wpdb->query( $query );
64
+ }
65
+ }
66
+
67
  }
includes/Database/Migrations/Chunks.php CHANGED
@@ -25,7 +25,7 @@ class NF_Database_Migrations_Chunks extends NF_Abstracts_Migration
25
  `name` varchar(200),
26
  `value` longtext,
27
  UNIQUE KEY (`id`)
28
- ) {$this->charset_collate()};";
29
 
30
  dbDelta( $query );
31
  }
25
  `name` varchar(200),
26
  `value` longtext,
27
  UNIQUE KEY (`id`)
28
+ ) {$this->charset_collate( true )};";
29
 
30
  dbDelta( $query );
31
  }
includes/Database/Migrations/FieldMeta.php CHANGED
@@ -2,6 +2,12 @@
2
 
3
  class NF_Database_Migrations_FieldMeta extends NF_Abstracts_Migration
4
  {
 
 
 
 
 
 
5
  public function __construct()
6
  {
7
  parent::__construct(
@@ -10,6 +16,13 @@ class NF_Database_Migrations_FieldMeta extends NF_Abstracts_Migration
10
  );
11
  }
12
 
 
 
 
 
 
 
 
13
  public function run()
14
  {
15
  $query = "CREATE TABLE IF NOT EXISTS {$this->table_name()} (
@@ -17,10 +30,32 @@ class NF_Database_Migrations_FieldMeta extends NF_Abstracts_Migration
17
  `parent_id` int NOT NULL,
18
  `key` longtext NOT NULL,
19
  `value` longtext,
20
- UNIQUE KEY (`id`)
21
- ) {$this->charset_collate()};";
 
 
22
 
23
  dbDelta( $query );
24
  }
25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  }
2
 
3
  class NF_Database_Migrations_FieldMeta extends NF_Abstracts_Migration
4
  {
5
+
6
+ /**
7
+ * Constructor method for the NF_Database_Migrations_FieldMeta class.
8
+ *
9
+ * @since 3.0.0
10
+ */
11
  public function __construct()
12
  {
13
  parent::__construct(
16
  );
17
  }
18
 
19
+ /**
20
+ * Function to run our initial migration.
21
+ *
22
+ * @since 3.0.0
23
+ *
24
+ * @updated 3.4.0
25
+ */
26
  public function run()
27
  {
28
  $query = "CREATE TABLE IF NOT EXISTS {$this->table_name()} (
30
  `parent_id` int NOT NULL,
31
  `key` longtext NOT NULL,
32
  `value` longtext,
33
+ `meta_key` longtext,
34
+ `meta_value` longtext,
35
+ UNIQUE KEY (`id`)
36
+ ) {$this->charset_collate( true )};";
37
 
38
  dbDelta( $query );
39
  }
40
 
41
+ /**
42
+ * Function to run our stage three upgrades.
43
+ *
44
+ * @since 3.3.12
45
+ *
46
+ * @updated 3.4.0
47
+ */
48
+ public function cache_collate_fields()
49
+ {
50
+ // If the meta_key column has not already been defined...
51
+ if ( ! $this->column_exists( 'meta_key' ) ) {
52
+ global $wpdb;
53
+ // Modify our table.
54
+ $query = "ALTER TABLE {$this->table_name()}
55
+ ADD `meta_key` longtext {$this->charset_collate()},
56
+ ADD `meta_value` longtext {$this->charset_collate()}";
57
+ $wpdb->query( $query );
58
+ }
59
+ }
60
+
61
  }
includes/Database/Migrations/Fields.php CHANGED
@@ -2,6 +2,12 @@
2
 
3
  class NF_Database_Migrations_Fields extends NF_Abstracts_Migration
4
  {
 
 
 
 
 
 
5
  public function __construct()
6
  {
7
  parent::__construct(
@@ -10,6 +16,13 @@ class NF_Database_Migrations_Fields extends NF_Abstracts_Migration
10
  );
11
  }
12
 
 
 
 
 
 
 
 
13
  public function run()
14
  {
15
  $query = "CREATE TABLE IF NOT EXISTS {$this->table_name()} (
@@ -20,10 +33,43 @@ class NF_Database_Migrations_Fields extends NF_Abstracts_Migration
20
  `parent_id` int NOT NULL,
21
  `created_at` TIMESTAMP,
22
  `updated_at` DATETIME,
 
 
 
 
 
 
 
23
  UNIQUE KEY (`id`)
24
- ) {$this->charset_collate()};";
25
 
26
  dbDelta( $query );
27
  }
28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  }
2
 
3
  class NF_Database_Migrations_Fields extends NF_Abstracts_Migration
4
  {
5
+
6
+ /**
7
+ * Constructor method for the NF_Database_Migrations_Fields class.
8
+ *
9
+ * @since 3.0.0
10
+ */
11
  public function __construct()
12
  {
13
  parent::__construct(
16
  );
17
  }
18
 
19
+ /**
20
+ * Function to run our initial migration.
21
+ *
22
+ * @since 3.0.0
23
+ *
24
+ * @updated 3.4.0
25
+ */
26
  public function run()
27
  {
28
  $query = "CREATE TABLE IF NOT EXISTS {$this->table_name()} (
33
  `parent_id` int NOT NULL,
34
  `created_at` TIMESTAMP,
35
  `updated_at` DATETIME,
36
+ `field_label` longtext,
37
+ `field_key` longtext,
38
+ `order` int(11),
39
+ `required` bit,
40
+ `default_value` longtext,
41
+ `label_pos` varchar(15),
42
+ `personally_identifiable` bit,
43
  UNIQUE KEY (`id`)
44
+ ) {$this->charset_collate( true )};";
45
 
46
  dbDelta( $query );
47
  }
48
 
49
+ /**
50
+ * Function to run our stage two upgrades.
51
+ *
52
+ * @since 3.3.12
53
+ *
54
+ * @updated 3.4.0
55
+ */
56
+ public function cache_collate_fields()
57
+ {
58
+ // If the field_label column has not already been defined...
59
+ if ( ! $this->column_exists( 'field_label' ) ) {
60
+ global $wpdb;
61
+ // Modify our table.
62
+ $query = "ALTER TABLE {$this->table_name()}
63
+ ADD `field_label` longtext {$this->charset_collate()},
64
+ ADD `field_key` longtext {$this->charset_collate()},
65
+ ADD `order` int(11),
66
+ ADD `required` bit,
67
+ ADD `default_value` longtext {$this->charset_collate()},
68
+ ADD `label_pos` varchar(15) {$this->charset_collate()},
69
+ ADD `personally_identifiable` bit,
70
+ MODIFY `type` longtext {$this->charset_collate()};";
71
+ $wpdb->query( $query );
72
+ }
73
+ }
74
+
75
  }
includes/Database/Migrations/FormMeta.php CHANGED
@@ -2,6 +2,12 @@
2
 
3
  class NF_Database_Migrations_FormMeta extends NF_Abstracts_Migration
4
  {
 
 
 
 
 
 
5
  public function __construct()
6
  {
7
  parent::__construct(
@@ -10,6 +16,14 @@ class NF_Database_Migrations_FormMeta extends NF_Abstracts_Migration
10
  );
11
  }
12
 
 
 
 
 
 
 
 
 
13
  public function run()
14
  {
15
  $query = "CREATE TABLE IF NOT EXISTS {$this->table_name()} (
@@ -20,20 +34,33 @@ class NF_Database_Migrations_FormMeta extends NF_Abstracts_Migration
20
  `meta_key` longtext,
21
  `meta_value` longtext,
22
  UNIQUE KEY (`id`)
23
- ) {$this->charset_collate()};";
24
 
25
  dbDelta( $query );
26
  }
27
-
28
  /**
29
- * Function to run our stage one upgrades.
 
 
30
  */
31
- public function do_stage_one()
32
  {
33
- $query = "ALTER TABLE {$this->table_name()}
34
- ADD `meta_key` longtext {$this->collate()},
35
- ADD `meta_value` longtext {$this->collate()}";
36
  global $wpdb;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  $wpdb->query( $query );
38
  }
39
 
2
 
3
  class NF_Database_Migrations_FormMeta extends NF_Abstracts_Migration
4
  {
5
+
6
+ /**
7
+ * Constructor method for the NF_Database_Migrations_Actions class.
8
+ *
9
+ * @since 3.0.0
10
+ */
11
  public function __construct()
12
  {
13
  parent::__construct(
16
  );
17
  }
18
 
19
+
20
+ /**
21
+ * Function to run our initial migration.
22
+ *
23
+ * @since 3.0.0
24
+ *
25
+ * @updated 3.4.0
26
+ */
27
  public function run()
28
  {
29
  $query = "CREATE TABLE IF NOT EXISTS {$this->table_name()} (
34
  `meta_key` longtext,
35
  `meta_value` longtext,
36
  UNIQUE KEY (`id`)
37
+ ) {$this->charset_collate( true )};";
38
 
39
  dbDelta( $query );
40
  }
41
+
42
  /**
43
+ * Function to be run as part of our CacheCollateForms required update.
44
+ *
45
+ * @since 3.4.0
46
  */
47
+ public function cache_collate_forms()
48
  {
 
 
 
49
  global $wpdb;
50
+
51
+ // If the meta_key column exists...
52
+ if ( $this->column_exists( 'meta_key' ) ) {
53
+ // Update our existing columns.
54
+ $query = "ALTER TABLE {$this->table_name()}
55
+ MODIFY `meta_key` longtext {$this->charset_collate()},
56
+ MODIFY `meta_value` longtext {$this->charset_collate()};";
57
+ } // Otherwise... (The meta_key column does not exist.)
58
+ else {
59
+ // Create the new columns.
60
+ $query = "ALTER TABLE {$this->table_name()}
61
+ ADD `meta_key` longtext {$this->charset_collate()},
62
+ ADD `meta_value` longtext {$this->charset_collate()};";
63
+ }
64
  $wpdb->query( $query );
65
  }
66
 
includes/Database/Migrations/Forms.php CHANGED
@@ -2,6 +2,12 @@
2
 
3
  class NF_Database_Migrations_Forms extends NF_Abstracts_Migration
4
  {
 
 
 
 
 
 
5
  public function __construct()
6
  {
7
  parent::__construct(
@@ -10,16 +16,24 @@ class NF_Database_Migrations_Forms extends NF_Abstracts_Migration
10
  );
11
  }
12
 
 
 
 
 
 
 
 
 
13
  public function run()
14
  {
15
  $query = "CREATE TABLE IF NOT EXISTS {$this->table_name()} (
16
  `id` int NOT NULL AUTO_INCREMENT,
17
  `title` longtext,
18
- `key` longtext,
19
  `created_at` TIMESTAMP,
20
  `updated_at` DATETIME,
21
- `views` int(11),
22
- `subs` int(11),
23
  `form_title` longtext,
24
  `default_label_pos` varchar(15),
25
  `show_title` bit,
@@ -28,33 +42,38 @@ class NF_Database_Migrations_Forms extends NF_Abstracts_Migration
28
  `logged_in` bit,
29
  `seq_num` int,
30
  UNIQUE KEY (`id`)
31
- ) {$this->charset_collate()};";
32
 
33
  dbDelta( $query );
34
  }
35
-
36
  /**
37
- * Function to run our stage one upgrades.
 
 
38
  */
39
- public function do_stage_one()
40
  {
41
- /**
42
- * TODO:
43
- *
44
- DROP `key`,
45
- DROP `views`,
46
- DROP `subs`,
47
- *
48
- */
49
- $query = "ALTER TABLE {$this->table_name()}
50
- ADD `form_title` longtext {$this->collate()},
51
- ADD `default_label_pos` varchar(15) {$this->collate()},
52
- ADD `show_title` bit,
53
- ADD `clear_complete` bit,
54
- ADD `hide_complete` bit,
55
- ADD `logged_in` bit,
56
- ADD `seq_num` int";
57
  global $wpdb;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  $wpdb->query( $query );
59
  }
60
 
2
 
3
  class NF_Database_Migrations_Forms extends NF_Abstracts_Migration
4
  {
5
+
6
+ /**
7
+ * Constructor method for the NF_Database_Migrations_Actions class.
8
+ *
9
+ * @since 3.0.0
10
+ */
11
  public function __construct()
12
  {
13
  parent::__construct(
16
  );
17
  }
18
 
19
+
20
+ /**
21
+ * Function to run our initial migration.
22
+ *
23
+ * @since 3.0.0
24
+ *
25
+ * @updated 3.4.0
26
+ */
27
  public function run()
28
  {
29
  $query = "CREATE TABLE IF NOT EXISTS {$this->table_name()} (
30
  `id` int NOT NULL AUTO_INCREMENT,
31
  `title` longtext,
32
+ `key` longtext,
33
  `created_at` TIMESTAMP,
34
  `updated_at` DATETIME,
35
+ `views` int(11),
36
+ `subs` int(11),
37
  `form_title` longtext,
38
  `default_label_pos` varchar(15),
39
  `show_title` bit,
42
  `logged_in` bit,
43
  `seq_num` int,
44
  UNIQUE KEY (`id`)
45
+ ) {$this->charset_collate( true )};";
46
 
47
  dbDelta( $query );
48
  }
49
+
50
  /**
51
+ * Function to be run as part of our CacheCollateForms required update.
52
+ *
53
+ * @since 3.4.0
54
  */
55
+ public function cache_collate_forms()
56
  {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  global $wpdb;
58
+
59
+ // If the form_title column exists...
60
+ if ( $this->column_exists( 'form_title' ) ) {
61
+ // Update our existing columns.
62
+ $query = "ALTER TABLE {$this->table_name()}
63
+ MODIFY `form_title` longtext {$this->charset_collate()},
64
+ MODIFY `default_label_pos` varchar(15) {$this->charset_collate()};";
65
+ } // Otherwise... (The form_title column does not exist.)
66
+ else {
67
+ // Create the new columns.
68
+ $query = "ALTER TABLE {$this->table_name()}
69
+ ADD `form_title` longtext {$this->charset_collate()},
70
+ ADD `default_label_pos` varchar(15) {$this->charset_collate()},
71
+ ADD `show_title` bit,
72
+ ADD `clear_complete` bit,
73
+ ADD `hide_complete` bit,
74
+ ADD `logged_in` bit,
75
+ ADD `seq_num` int;";
76
+ }
77
  $wpdb->query( $query );
78
  }
79
 
includes/Database/Migrations/ObjectMeta.php CHANGED
@@ -2,6 +2,11 @@
2
 
3
  class NF_Database_Migrations_ObjectMeta extends NF_Abstracts_Migration
4
  {
 
 
 
 
 
5
  public function __construct()
6
  {
7
  parent::__construct(
@@ -10,6 +15,13 @@ class NF_Database_Migrations_ObjectMeta extends NF_Abstracts_Migration
10
  );
11
  }
12
 
 
 
 
 
 
 
 
13
  public function run()
14
  {
15
  $query = "CREATE TABLE IF NOT EXISTS {$this->table_name()} (
@@ -17,10 +29,30 @@ class NF_Database_Migrations_ObjectMeta extends NF_Abstracts_Migration
17
  `parent_id` int NOT NULL,
18
  `key` longtext NOT NULL,
19
  `value` longtext,
 
 
20
  UNIQUE KEY (`id`)
21
- ) {$this->charset_collate()};";
22
 
23
  dbDelta( $query );
24
  }
25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  }
2
 
3
  class NF_Database_Migrations_ObjectMeta extends NF_Abstracts_Migration
4
  {
5
+ /**
6
+ * Constructor method for the NF_Database_Migrations_ObjectMeta class.
7
+ *
8
+ * @since 3.0.0
9
+ */
10
  public function __construct()
11
  {
12
  parent::__construct(
15
  );
16
  }
17
 
18
+ /**
19
+ * Function to run our initial migration.
20
+ *
21
+ * @since 3.0.0
22
+ *
23
+ * @updated 3.4.0
24
+ */
25
  public function run()
26
  {
27
  $query = "CREATE TABLE IF NOT EXISTS {$this->table_name()} (
29
  `parent_id` int NOT NULL,
30
  `key` longtext NOT NULL,
31
  `value` longtext,
32
+ `meta_key` longtext,
33
+ `meta_value` longtext,
34
  UNIQUE KEY (`id`)
35
+ ) {$this->charset_collate( true )};";
36
 
37
  dbDelta( $query );
38
  }
39
 
40
+ /**
41
+ * Function to ensure proper collation of the object_meta table.
42
+ *
43
+ * @since 3.4.0
44
+ */
45
+ public function cache_collate_objects()
46
+ {
47
+ // If the meta_key column has not already been defined...
48
+ if ( ! $this->column_exists( 'meta_key' ) ) {
49
+ global $wpdb;
50
+ // Modify our table.
51
+ $query = "ALTER TABLE {$this->table_name()}
52
+ ADD `meta_key` longtext {$this->charset_collate()},
53
+ ADD `meta_value` longtext {$this->charset_collate()};";
54
+ $wpdb->query( $query );
55
+ }
56
+ }
57
+
58
  }
includes/Database/Migrations/Objects.php CHANGED
@@ -2,6 +2,11 @@
2
 
3
  class NF_Database_Migrations_Objects extends NF_Abstracts_Migration
4
  {
 
 
 
 
 
5
  public function __construct()
6
  {
7
  parent::__construct(
@@ -10,6 +15,13 @@ class NF_Database_Migrations_Objects extends NF_Abstracts_Migration
10
  );
11
  }
12
 
 
 
 
 
 
 
 
13
  public function run()
14
  {
15
  $query = "CREATE TABLE IF NOT EXISTS {$this->table_name()} (
@@ -18,10 +30,29 @@ class NF_Database_Migrations_Objects extends NF_Abstracts_Migration
18
  `title` longtext,
19
  `created_at` TIMESTAMP,
20
  `updated_at` DATETIME,
 
21
  UNIQUE KEY (`id`)
22
- ) {$this->charset_collate()};";
23
 
24
  dbDelta( $query );
25
  }
26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  }
2
 
3
  class NF_Database_Migrations_Objects extends NF_Abstracts_Migration
4
  {
5
+ /**
6
+ * Constructor method for the NF_Database_Migrations_Objects class.
7
+ *
8
+ * @since 3.0.0
9
+ */
10
  public function __construct()
11
  {
12
  parent::__construct(
15
  );
16
  }
17
 
18
+ /**
19
+ * Function to run our initial migration.
20
+ *
21
+ * @since 3.0.0
22
+ *
23
+ * @updated 3.4.0
24
+ */
25
  public function run()
26
  {
27
  $query = "CREATE TABLE IF NOT EXISTS {$this->table_name()} (
30
  `title` longtext,
31
  `created_at` TIMESTAMP,
32
  `updated_at` DATETIME,
33
+ `object_title` longtext,
34
  UNIQUE KEY (`id`)
35
+ ) {$this->charset_collate( true )};";
36
 
37
  dbDelta( $query );
38
  }
39
 
40
+ /**
41
+ * Function to ensure proper collation of the objects table.
42
+ *
43
+ * @since 3.4.0
44
+ */
45
+ public function cache_collate_objects()
46
+ {
47
+ // If the object_title column has not already been defined...
48
+ if ( ! $this->column_exists( 'object_title' ) ) {
49
+ global $wpdb;
50
+ // Modify our table.
51
+ $query = "ALTER TABLE {$this->table_name()}
52
+ ADD `object_title` longtext {$this->charset_collate()},
53
+ MODIFY `type` longtext {$this->charset_collate()};";
54
+ $wpdb->query( $query );
55
+ }
56
+ }
57
+
58
  }
includes/Database/Migrations/Relationships.php CHANGED
@@ -2,6 +2,11 @@
2
 
3
  class NF_Database_Migrations_Relationships extends NF_Abstracts_Migration
4
  {
 
 
 
 
 
5
  public function __construct()
6
  {
7
  parent::__construct(
@@ -10,6 +15,11 @@ class NF_Database_Migrations_Relationships extends NF_Abstracts_Migration
10
  );
11
  }
12
 
 
 
 
 
 
13
  public function run()
14
  {
15
  $query = "CREATE TABLE IF NOT EXISTS {$this->table_name()} (
@@ -21,9 +31,24 @@ class NF_Database_Migrations_Relationships extends NF_Abstracts_Migration
21
  `created_at` TIMESTAMP,
22
  `updated_at` DATETIME,
23
  UNIQUE KEY (`id`)
24
- ) {$this->charset_collate()};";
25
 
26
  dbDelta( $query );
27
  }
28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  }
2
 
3
  class NF_Database_Migrations_Relationships extends NF_Abstracts_Migration
4
  {
5
+ /**
6
+ * Constructor method for the NF_Database_Migrations_Relationships class.
7
+ *
8
+ * @since 3.0.0
9
+ */
10
  public function __construct()
11
  {
12
  parent::__construct(
15
  );
16
  }
17
 
18
+ /**
19
+ * Function to run our initial migration.
20
+ *
21
+ * @since 3.0.0
22
+ */
23
  public function run()
24
  {
25
  $query = "CREATE TABLE IF NOT EXISTS {$this->table_name()} (
31
  `created_at` TIMESTAMP,
32
  `updated_at` DATETIME,
33
  UNIQUE KEY (`id`)
34
+ ) {$this->charset_collate( true )};";
35
 
36
  dbDelta( $query );
37
  }
38
 
39
+ /**
40
+ * Function to ensure proper collation of the relationships table.
41
+ *
42
+ * @since 3.4.0
43
+ */
44
+ public function cache_collate_objects()
45
+ {
46
+ global $wpdb;
47
+ // Modify our table.
48
+ $query = "ALTER TABLE {$this->table_name()}
49
+ MODIFY `child_type` longtext {$this->charset_collate()} NOT NULL,
50
+ MODIFY `parent_type` longtext {$this->charset_collate()} NOT NULL;";
51
+ $wpdb->query( $query );
52
+ }
53
+
54
  }
includes/Database/Migrations/Upgrades.php CHANGED
@@ -2,6 +2,12 @@
2
 
3
  class NF_Database_Migrations_Upgrades extends NF_Abstracts_Migration
4
  {
 
 
 
 
 
 
5
  public function __construct()
6
  {
7
  parent::__construct(
@@ -10,16 +16,41 @@ class NF_Database_Migrations_Upgrades extends NF_Abstracts_Migration
10
  );
11
  }
12
 
 
 
 
 
 
 
 
 
13
  public function run()
14
  {
15
  $query = "CREATE TABLE IF NOT EXISTS {$this->table_name()} (
16
  `id` INT(11) NOT NULL,
17
  `cache` LONGTEXT,
18
  `stage` INT(11) NOT NULL DEFAULT 0,
 
19
  PRIMARY KEY ( id )
20
- ) {$this->charset_collate()};";
21
 
22
  dbDelta( $query );
23
  }
24
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  }
2
 
3
  class NF_Database_Migrations_Upgrades extends NF_Abstracts_Migration
4
  {
5
+
6
+ /**
7
+ * Constructor method for the NF_Database_Migrations_Fields class.
8
+ *
9
+ * @since 3.3.11
10
+ */
11
  public function __construct()
12
  {
13
  parent::__construct(
16
  );
17
  }
18
 
19
+
20
+ /**
21
+ * Function to run our initial migration.
22
+ *
23
+ * @since 3.3.11
24
+ *
25
+ * @updated 3.4.0
26
+ */
27
  public function run()
28
  {
29
  $query = "CREATE TABLE IF NOT EXISTS {$this->table_name()} (
30
  `id` INT(11) NOT NULL,
31
  `cache` LONGTEXT,
32
  `stage` INT(11) NOT NULL DEFAULT 0,
33
+ `maintenance` bit DEFAULT 0,
34
  PRIMARY KEY ( id )
35
+ ) {$this->charset_collate( true )};";
36
 
37
  dbDelta( $query );
38
  }
39
 
40
+ /**
41
+ * Function to define our maintenance column.
42
+ *
43
+ * @since 3.4.0
44
+ */
45
+ public function cache_collate_fields()
46
+ {
47
+ // If the maintenance column has not already been defined...
48
+ if ( ! $this->column_exists( 'maintenance' ) ) {
49
+ global $wpdb;
50
+ // Modify our table.
51
+ $query = "ALTER TABLE {$this->table_name()}
52
+ ADD `maintenance` bit DEFAULT 0;";
53
+ $wpdb->query( $query );
54
+ }
55
+ }
56
  }
includes/Database/Models/Action.php CHANGED
@@ -18,12 +18,32 @@ final class NF_Database_Models_Action extends NF_Abstracts_Model
18
  'key',
19
  'type',
20
  'active',
21
- 'created_at'
 
22
  );
23
 
24
  public function __construct( $db, $id, $parent_id = '' )
25
  {
26
  parent::__construct( $db, $id, $parent_id );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  }
28
 
29
  } // End NF_Database_Models_Action
18
  'key',
19
  'type',
20
  'active',
21
+ 'created_at',
22
+ 'label'
23
  );
24
 
25
  public function __construct( $db, $id, $parent_id = '' )
26
  {
27
  parent::__construct( $db, $id, $parent_id );
28
+
29
+ /**
30
+ * Remove new DB columns from our $_columns list if the user hasn't completed required upgrades stage 1.
31
+ */
32
+ $sql = "SHOW COLUMNS FROM {$db->prefix}nf3_actions LIKE 'label'";
33
+ $results = $db->get_results( $sql );
34
+ /**
35
+ * If we don't have the label column, we need to remove our new columns.
36
+ *
37
+ * Also, set our db stage 1 tracker to false.
38
+ */
39
+ if ( empty ( $results ) ) {
40
+ foreach( $this->_columns as $i => $col ) {
41
+ if( 'label' === $col ) {
42
+ unset( $this->_columns[ $i ] );
43
+ }
44
+ }
45
+ $this->db_stage_1_complete = false;
46
+ }
47
  }
48
 
49
  } // End NF_Database_Models_Action
includes/Database/Models/Form.php CHANGED
@@ -226,10 +226,43 @@ final class NF_Database_Models_Form extends NF_Abstracts_Model
226
  Ninja_Forms()->template( 'admin-notice-form-import.html.php', array( 'form_id'=> self::$imported_form_id ) );
227
  }
228
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
229
  public static function duplicate( $form_id )
230
  {
231
  global $wpdb;
232
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
  // Duplicate the Form Object.
234
  $wpdb->query( $wpdb->prepare(
235
  "
@@ -261,22 +294,28 @@ final class NF_Database_Models_Form extends NF_Abstracts_Model
261
  ", $form_id
262
  ));
263
 
 
 
 
 
264
  foreach( $old_fields as $old_field ){
 
265
  // Duplicate the Field Object.
266
  $wpdb->query( $wpdb->prepare(
267
  "
268
- INSERT INTO {$wpdb->prefix}nf3_fields ( `label`, `key`, `type`, `parent_id` )
269
- SELECT `label`, `key`, `type`, %d
270
  FROM {$wpdb->prefix}nf3_fields
271
  WHERE id = %d
272
  ", $new_form_id, $old_field->id
273
  ));
274
  $new_field_id = $wpdb->insert_id;
 
275
  // Duplicate the Field Meta.
276
  $wpdb->query( $wpdb->prepare(
277
  "
278
- INSERT INTO {$wpdb->prefix}nf3_field_meta ( `parent_id`, `key`, `value` )
279
- SELECT %d, `key`, `value`
280
  FROM {$wpdb->prefix}nf3_field_meta
281
  WHERE parent_id = %d;
282
  ", $new_field_id, $old_field->id
@@ -294,12 +333,16 @@ final class NF_Database_Models_Form extends NF_Abstracts_Model
294
  ", $form_id
295
  ));
296
 
 
 
 
 
297
  foreach( $old_actions as $old_action ){
298
  // Duplicate the Action Object.
299
  $wpdb->query( $wpdb->prepare(
300
  "
301
- INSERT INTO {$wpdb->prefix}nf3_actions ( `title`, `key`, `type`, `active`, `parent_id` )
302
- SELECT `title`, `key`, `type`, `active`, %d
303
  FROM {$wpdb->prefix}nf3_actions
304
  WHERE id = %d
305
  ", $new_form_id, $old_action->id
@@ -308,8 +351,8 @@ final class NF_Database_Models_Form extends NF_Abstracts_Model
308
  // Duplicate the Action Meta.
309
  $wpdb->query( $wpdb->prepare(
310
  "
311
- INSERT INTO {$wpdb->prefix}nf3_action_meta ( `parent_id`, `key`, `value` )
312
- SELECT %d, `key`, `value`
313
  FROM {$wpdb->prefix}nf3_action_meta
314
  WHERE parent_id = %d;
315
  ", $new_action_id, $old_action->id
@@ -324,9 +367,130 @@ final class NF_Database_Models_Form extends NF_Abstracts_Model
324
  $new_form->update_settings( $new_form->get_settings() );
325
  $new_form->save();
326
 
 
 
 
 
 
327
  return $new_form_id;
328
  }
329
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
330
  public static function export( $form_id, $return = FALSE )
331
  {
332
  //TODO: Set Date Format from Plugin Settings
226
  Ninja_Forms()->template( 'admin-notice-form-import.html.php', array( 'form_id'=> self::$imported_form_id ) );
227
  }
228
 
229
+ /**
230
+ * This static method is called to duplicate a form using the form ID.
231
+ *
232
+ * To duplicate a form we:
233
+ *
234
+ * Check to see if we've ran stage one of the db update process.
235
+ * Use SQL to insert a copy of our form and form meta.
236
+ * Grab all fields for a specific form.
237
+ * Loop over those fields and insert fields and field meta.
238
+ * Run ->update_settings() and ->save() on the form model.
239
+ * Call our WPN_Helper method to build a form cache.
240
+ *
241
+ * @since 3.0
242
+ * @update 3.4.0
243
+ * @param int $form_id ID of the form being duplicated.
244
+ * @return $new_form_id ID of our form duplicate.
245
+ */
246
  public static function duplicate( $form_id )
247
  {
248
  global $wpdb;
249
 
250
+
251
+ /**
252
+ * Check to see if we've got new field columns.
253
+ *
254
+ * We do this here instead of the get_sql_queries() method so that we don't hit the db multiple times.
255
+ */
256
+ $sql = "SHOW COLUMNS FROM {$wpdb->prefix}nf3_fields LIKE 'field_key'";
257
+ $results = $wpdb->get_results( $sql );
258
+
259
+ // If we don't have the field_key column, we need to remove our new columns.
260
+ if ( empty ( $results ) ) {
261
+ $db_stage_one_complete = false;
262
+ } else {
263
+ $db_stage_one_complete = true;
264
+ }
265
+
266
  // Duplicate the Form Object.
267
  $wpdb->query( $wpdb->prepare(
268
  "
294
  ", $form_id
295
  ));
296
 
297
+ // Get our field and field_meta table column names and values.
298
+ $fields_sql = self::get_sql_queries( 'field_table_columns' );
299
+ $field_meta_sql = self::get_sql_queries( 'field_meta_table_columns' );
300
+
301
  foreach( $old_fields as $old_field ){
302
+
303
  // Duplicate the Field Object.
304
  $wpdb->query( $wpdb->prepare(
305
  "
306
+ INSERT INTO {$wpdb->prefix}nf3_fields ( {$fields_sql[ 'insert' ]} )
307
+ SELECT {$fields_sql[ 'select' ]}
308
  FROM {$wpdb->prefix}nf3_fields
309
  WHERE id = %d
310
  ", $new_form_id, $old_field->id
311
  ));
312
  $new_field_id = $wpdb->insert_id;
313
+
314
  // Duplicate the Field Meta.
315
  $wpdb->query( $wpdb->prepare(
316
  "
317
+ INSERT INTO {$wpdb->prefix}nf3_field_meta ( {$field_meta_sql[ 'insert' ]} )
318
+ SELECT {$field_meta_sql[ 'select' ]}
319
  FROM {$wpdb->prefix}nf3_field_meta
320
  WHERE parent_id = %d;
321
  ", $new_field_id, $old_field->id
333
  ", $form_id
334
  ));
335
 
336
+ // Get our action and action_meta table columns and values.
337
+ $actions_sql = self::get_sql_queries( 'action_table_columns' );
338
+ $actions_meta_sql = self::get_sql_queries( 'action_meta_table_columns' );
339
+
340
  foreach( $old_actions as $old_action ){
341
  // Duplicate the Action Object.
342
  $wpdb->query( $wpdb->prepare(
343
  "
344
+ INSERT INTO {$wpdb->prefix}nf3_actions ( {$actions_sql[ 'insert' ]} )
345
+ SELECT {$actions_sql[ 'select' ]}
346
  FROM {$wpdb->prefix}nf3_actions
347
  WHERE id = %d
348
  ", $new_form_id, $old_action->id
351
  // Duplicate the Action Meta.
352
  $wpdb->query( $wpdb->prepare(
353
  "
354
+ INSERT INTO {$wpdb->prefix}nf3_action_meta ( {$actions_meta_sql[ 'insert' ]} )
355
+ SELECT {$actions_meta_sql[ 'select' ]}
356
  FROM {$wpdb->prefix}nf3_action_meta
357
  WHERE parent_id = %d;
358
  ", $new_action_id, $old_action->id
367
  $new_form->update_settings( $new_form->get_settings() );
368
  $new_form->save();
369
 
370
+ /*
371
+ * Build a cache for this new form.
372
+ */
373
+ WPN_Helper::build_nf_cache( $new_form_id );
374
+
375
  return $new_form_id;
376
  }
377
 
378
+ /**
379
+ * When duplicating a form, we need to build specific SQL queries.
380
+ *
381
+ * This is a fairly repetative task, so we've extrapolated the code to its own function.
382
+ *
383
+ * @since 3.4.0
384
+ * @param string $table_name Name of the table we want to update.
385
+ * @return array Associative array like: ['insert' => "`column1`, "`column2`", etc", 'select' => "`column1`, etc."]
386
+ */
387
+ private function get_sql_queries( $table_name, $db_stage_one_complete = true )
388
+ {
389
+ /**
390
+ * These arrays contain the columns in our database.
391
+ *
392
+ * Later, if the user hasn't ran stage one of our db update process, we'll remove the items that aren't supported.
393
+ */
394
+ $db_columns = array(
395
+ 'field_table_columns' => array(
396
+ 'label',
397
+ 'key',
398
+ 'type',
399
+ 'parent_id',
400
+ 'field_label',
401
+ 'field_key',
402
+ 'order',
403
+ 'required',
404
+ 'default_value',
405
+ 'label_pos',
406
+ 'personally_identifiable',
407
+ ),
408
+
409
+ 'field_meta_table_columns' => array(
410
+ 'parent_id',
411
+ 'key',
412
+ 'value',
413
+ 'meta_key',
414
+ 'meta_value',
415
+ ),
416
+
417
+ 'action_table_columns' => array(
418
+ 'title',
419
+ 'key',
420
+ 'type',
421
+ 'active',
422
+ 'parent_id',
423
+ 'label',
424
+ ),
425
+
426
+ 'action_meta_table_columns' => array(
427
+ 'parent_id',
428
+ 'key',
429
+ 'value',
430
+ 'meta_key',
431
+ 'meta_value',
432
+ ),
433
+ );
434
+
435
+ // If we haven't been passed a table name or the table name is invalid, return false.
436
+ if ( empty( $table_name ) || ! isset( $db_columns[ $table_name ] ) ) return false;
437
+
438
+ // If we have not completed stage one of our db update, then we unset new db columns.
439
+ if ( ! $db_stage_one_complete ) {
440
+ unset(
441
+ $field_table_columns[ 'field_label' ],
442
+ $field_table_columns[ 'field_key' ],
443
+ $field_table_columns[ 'order' ],
444
+ $field_table_columns[ 'required' ],
445
+ $field_table_columns[ 'default_value' ],
446
+ $field_table_columns[ 'label_pos' ],
447
+ $field_table_columns[ 'personally_identifiable' ]
448
+ );
449
+
450
+ unset(
451
+ $field_meta_table_columns[ 'meta_key' ],
452
+ $field_meta_table_columns[ 'meta_value' ]
453
+ );
454
+
455
+ unset(
456
+ $action_table_columns[ 'label' ]
457
+ );
458
+
459
+ unset(
460
+ $action_meta_table_columns[ 'meta_key' ],
461
+ $action_meta_table_columns[ 'meta_value' ]
462
+ );
463
+ }
464
+
465
+ /**
466
+ * $sql_insert is a var that tracks which table columns we want to insert.
467
+ */
468
+ $sql_insert = "";
469
+ /**
470
+ * $sql_select is a var that tracks the values for each field table column.
471
+ */
472
+ $sql_select = "";
473
+
474
+ /**
475
+ * Loop over our table column names and add them to our $sql_insert var.
476
+ */
477
+ foreach( $db_columns[ $table_name ] as $column_name ) {
478
+ $sql_insert .= "`{$column_name}`,";
479
+
480
+ if ( 'parent_id' == $column_name ) {
481
+ $sql_select .= "%d,";
482
+ } else {
483
+ $sql_select .= "`{$column_name}`,";
484
+ }
485
+ }
486
+
487
+ // Remove any trailing commas.
488
+ $sql_insert = rtrim( $sql_insert, ',' );
489
+ $sql_select = rtrim( $sql_select, ',' );
490
+
491
+ return array( 'insert' => $sql_insert, 'select' => $sql_select );
492
+ }
493
+
494
  public static function export( $form_id, $return = FALSE )
495
  {
496
  //TODO: Set Date Format from Plugin Settings
includes/Database/Models/Object.php CHANGED
@@ -13,7 +13,8 @@ final class NF_Database_Models_Object extends NF_Abstracts_Model
13
 
14
  protected $_columns = array(
15
  'type',
16
- 'created_at'
 
17
  );
18
 
19
  public function __construct( $db, $id, $parent_id = '', $parent_type = '' )
@@ -21,6 +22,25 @@ final class NF_Database_Models_Object extends NF_Abstracts_Model
21
  parent::__construct( $db, $id, $parent_id );
22
 
23
  $this->_parent_type = $parent_type;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  }
25
 
26
  public function save()
13
 
14
  protected $_columns = array(
15
  'type',
16
+ 'created_at',
17
+ 'object_title'
18
  );
19
 
20
  public function __construct( $db, $id, $parent_id = '', $parent_type = '' )
22
  parent::__construct( $db, $id, $parent_id );
23
 
24
  $this->_parent_type = $parent_type;
25
+
26
+ /**
27
+ * Remove new DB columns from our $_columns list if the user hasn't completed required upgrades stage 1.
28
+ */
29
+ $sql = "SHOW COLUMNS FROM {$db->prefix}nf3_objects LIKE 'object_title'";
30
+ $results = $db->get_results( $sql );
31
+ /**
32
+ * If we don't have the object_title column, we need to remove our new columns.
33
+ *
34
+ * Also, set our db stage 1 tracker to false.
35
+ */
36
+ if ( empty ( $results ) ) {
37
+ foreach( $this->_columns as $i => $col ) {
38
+ if( 'object_title' === $col ) {
39
+ unset( $this->_columns[ $i ] );
40
+ }
41
+ }
42
+ $this->db_stage_1_complete = false;
43
+ }
44
  }
45
 
46
  public function save()
includes/Display/Render.php CHANGED
@@ -108,6 +108,20 @@ final class NF_Display_Render
108
  }
109
  }
110
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  if( ! apply_filters( 'ninja_forms_display_show_form', true, $form_id, $form ) ) return;
112
 
113
  $currency = $form->get_setting( 'currency', Ninja_Forms()->get_setting( 'currency' ) );
108
  }
109
  }
110
 
111
+ // Get our maintenance value out of the DB.
112
+ $maintenance = WPN_Helper::form_in_maintenance( $form_id );
113
+
114
+ // If maintenance isn't empty and the bool is set to 1 then..
115
+ if( true == $maintenance ) {
116
+ // Set a filterable maintenance message and echo it out.
117
+ $maintenance_msg = apply_filters( 'nf_maintenance_message', __( 'This form is currently undergoing maintenance. Please try again later.', 'ninja-forms' ) );
118
+ echo $maintenance_msg;
119
+
120
+ // bail.
121
+ return false;
122
+ }
123
+
124
+
125
  if( ! apply_filters( 'ninja_forms_display_show_form', true, $form_id, $form ) ) return;
126
 
127
  $currency = $form->get_setting( 'currency', Ninja_Forms()->get_setting( 'currency' ) );
includes/Helper.php CHANGED
@@ -291,12 +291,13 @@ final class WPN_Helper
291
  *
292
  * @param $id (int) The form ID.
293
  * @param $data (string) The form cache.
 
294
  *
295
  * @since 3.3.7
 
296
  */
297
- public static function update_nf_cache( $id, $data ) {
298
- // Define our current stage here for use as we run various upgrades.
299
- $CURRENT_STAGE = 1;
300
  // Serialize our data.
301
  $cache = serialize( $data );
302
  global $wpdb;
@@ -306,14 +307,69 @@ final class WPN_Helper
306
  // If we don't already have the data...
307
  if ( empty( $result ) ) {
308
  // Insert it.
309
- $sql = $wpdb->prepare( "INSERT INTO `{$wpdb->prefix}nf3_upgrades` (id, cache, stage) VALUES (%d, %s, %s)", intval( $id ), $cache, $CURRENT_STAGE);
310
  } // Otherwise... (We do have the data.)
311
  else {
312
  // Update the existing record.
313
- $sql = $wpdb->prepare( "UPDATE `{$wpdb->prefix}nf3_upgrades` SET cache = %s WHERE id = %d", $cache, intval( $id ) ) ;
314
  }
315
  $wpdb->query( $sql );
316
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
317
 
318
  /**
319
  * Function to delete our cache.
@@ -336,4 +392,96 @@ final class WPN_Helper
336
  }
337
  }
338
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
339
  } // End Class WPN_Helper
291
  *
292
  * @param $id (int) The form ID.
293
  * @param $data (string) The form cache.
294
+ * @param $stage (int) The target stage of this update. Default to the current max stage.
295
  *
296
  * @since 3.3.7
297
+ * @updated 3.4.0
298
  */
299
+ public static function update_nf_cache( $id, $data, $stage = 0 ) {
300
+ $stage = ( $stage ) ? $stage : WPN_Helper::get_stage();
 
301
  // Serialize our data.
302
  $cache = serialize( $data );
303
  global $wpdb;
307
  // If we don't already have the data...
308
  if ( empty( $result ) ) {
309
  // Insert it.
310
+ $sql = $wpdb->prepare( "INSERT INTO `{$wpdb->prefix}nf3_upgrades` (id, cache, stage) VALUES (%d, %s, %s)", intval( $id ), $cache, intval( $stage ) );
311
  } // Otherwise... (We do have the data.)
312
  else {
313
  // Update the existing record.
314
+ $sql = $wpdb->prepare( "UPDATE `{$wpdb->prefix}nf3_upgrades` SET cache = %s, stage = %d WHERE id = %d", $cache, intval( $stage ), intval( $id ) );
315
  }
316
  $wpdb->query( $sql );
317
  }
318
+
319
+ /**
320
+ * Function to retrieve our upgrade stage.
321
+ * Remove this after the cache has been resolved.
322
+ *
323
+ * @return int
324
+ *
325
+ * @since 3.4.0
326
+ */
327
+ public static function get_stage() {
328
+ $ver = Ninja_Forms::$db_version;
329
+ $stack = explode( '.', $ver );
330
+ return intval( array_pop( $stack ) );
331
+ }
332
+
333
+ /**
334
+ * Function to build our form cache from the table.
335
+ *
336
+ * @param $id (int) The form ID.
337
+ * @since 3.3.18
338
+ * @return $form_cache Array of form data.
339
+ * @updated 3.4.0
340
+ */
341
+ public static function build_nf_cache( $id ) {
342
+ $form = Ninja_Forms()->form( $id )->get();
343
+
344
+ $form_cache = array(
345
+ 'id' => $id,
346
+ 'fields' => array(),
347
+ 'actions' => array(),
348
+ 'settings' => $form->get_settings(),
349
+ );
350
+
351
+ $fields = Ninja_Forms()->form( $id )->get_fields();
352
+
353
+ foreach( $fields as $field ){
354
+ // If the field is set.
355
+ if ( ! is_null( $field ) && ! empty( $field ) ) {
356
+ array_push( $form_cache[ 'fields' ], array( 'settings' => $field->get_settings(), 'id' => $field->get_id() ) );
357
+ }
358
+ }
359
+
360
+ $actions = Ninja_Forms()->form( $id )->get_actions();
361
+
362
+ foreach( $actions as $action ){
363
+ // If the action is set.
364
+ if ( ! is_null( $action ) && ! empty( $action ) ) {
365
+ array_push( $form_cache[ 'actions' ], array( 'settings' => $action->get_settings(), 'id' => $action->get_id() ) );
366
+ }
367
+ }
368
+
369
+ WPN_Helper::update_nf_cache( $id, $form_cache );
370
+
371
+ return $form_cache;
372
+ }
373
 
374
  /**
375
  * Function to delete our cache.
392
  }
393
  }
394
 
395
+ /**
396
+ * This funtion gets/creates the Ninja Forms gate keeper( a random integer
397
+ * between 1 and 100 ). We will use this number when deciding whether a
398
+ * particular install is eligible for an upgrade or whatever else we decide
399
+ * to use it for
400
+ *
401
+ * @return int $zuul
402
+ *
403
+ * @since 3.4.0
404
+ */
405
+ public static function get_zuul() {
406
+ $zuul = get_option( 'ninja_forms_zuul', -1 );
407
+
408
+ if( -1 === $zuul ) {
409
+ $zuul = rand( 1, 100 );
410
+ update_option( 'ninja_forms_zuul', $zuul, false );
411
+ }
412
+
413
+ return $zuul;
414
+ }
415
+
416
+ /**
417
+ * This function will return true/false based on an option( ninja_forms_zuul )
418
+ * and a threshold that we set. We can use this to limit updates
419
+ *
420
+ * @param $threshold
421
+ *
422
+ * @return bool
423
+ *
424
+ * @since 3.4.0
425
+ */
426
+ public static function gated_release( $threshold = 0 ) {
427
+ $gatekeeper = $threshold >= self::get_zuul();
428
+ $gatekeeper = apply_filters( 'ninja_forms_gatekeeper', $gatekeeper );
429
+
430
+ return $gatekeeper;
431
+ }
432
+
433
+ /**
434
+ * Is Maintenance
435
+ *
436
+ * Checks the upgrades table to see if the form the user is viewing
437
+ * is under maintenance mode.
438
+ *
439
+ * @since 3.4.0
440
+ *
441
+ * @param $form_id - The ID of the form we are checking.
442
+ *
443
+ * @return boolean
444
+ */
445
+ public static function form_in_maintenance( $form_id ) {
446
+ global $wpdb;
447
+
448
+ // Get our maintenance value from the DB and return it at the zero position.
449
+ $maintenance = $wpdb->get_row(
450
+ "SELECT `maintenance` FROM `{$wpdb->prefix}nf3_upgrades` WHERE `id` = {$form_id}", 'ARRAY_A'
451
+ );
452
+
453
+ /*
454
+ * If maintenance isn't empty and basic on maintenance's value
455
+ * return a boolean value.
456
+ */
457
+ if( ! empty( $maintenance ) && 1 == $maintenance[ 'maintenance' ] ) {
458
+ return true;
459
+ } else {
460
+ return false;
461
+ }
462
+ }
463
+
464
+ /**
465
+ * This function either put all forms in maintenance mode or remove maintenance
466
+ * mode for all forms. Depending on the input parameters
467
+ *
468
+ * @since 3.4.0
469
+ *
470
+ * @param $mode - Default 0 ( Take all forms out of maintenance mode )
471
+ */
472
+ public static function set_forms_maintenance_mode( $mode = 0 ) {
473
+ global $wpdb;
474
+
475
+ // default is 0, so if we get passed bad data, just use 0
476
+ if( ! in_array( $mode, array( 0, 1 ) ) ) {
477
+ $mode = 0;
478
+ }
479
+
480
+ // set maintenance flag to $mode (0 or 1)
481
+ $sql = $wpdb->prepare( "UPDATE `{$wpdb->prefix}nf3_upgrades` SET "
482
+ . "maintenance = %d", intval( $mode ) );
483
+
484
+ $wpdb->query( $sql );
485
+ }
486
+
487
  } // End Class WPN_Helper
includes/Templates/admin-menu-dashboard.html.php CHANGED
@@ -233,6 +233,39 @@
233
  ?>
234
  </script>
235
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
  <!-- Widget: Forms -->
237
  <script id="tmpl-nf-widget-forms" type="text/template">
238
  <header>
233
  ?>
234
  </script>
235
 
236
+ <!-- Section: Required Updates -->
237
+ <script id="tmpl-nf-requiredUpdates" type="text/template">
238
+ <div>
239
+ <h1><?php _e( 'Required Updates', 'ninja-forms' ); ?></h1>
240
+ <div>
241
+ <p>
242
+ <?php _e( "Ninja Forms needs to run some updates on your installation before you can continue. You'll be able to create and edit forms after the updates listed below have completed.", 'ninja-forms' ); ?>
243
+ </p>
244
+ <p>
245
+ <?php _e( "Normally, users will still be able to view and submit forms while these updates take place. If an update needs to modify database information, we'll put the affected form in maintenance mode until we get done with that update.", 'ninja-forms' ); ?>
246
+ </p>
247
+ <p>
248
+ <?php _e( "It's always a good idea to have an up to date backup of your WordPress site on hand. That's especially true when you run plugin and theme updates. Luckily, there are plenty of good backup plugins available.", 'ninja-forms' ); ?>
249
+ </p>
250
+ <p>
251
+ <?php _e( "When you're ready, just click the \"Do Required Updates\" button below to get started. You'll be able to create and edit forms in no time.", 'ninja-forms' ); ?>
252
+ </p>
253
+ </div>
254
+ <div id="nfUpgradeApp">
255
+ <table id="nf-upgrades-table">
256
+ <thead>
257
+ </thead>
258
+ <tbody>
259
+ </tbody>
260
+ </table>
261
+ </div>
262
+ <div>
263
+ <input class="nf-required-update nf-update-button" type='button' id='nf-required-updates-btn' name='nf-required-updates-btn' value="<?php _e( 'Do Required Updates' ); ?>" />
264
+ </div>
265
+ <div class="nf-update-progress jBox-content" id="nf-required-updates-progress"></div>
266
+ </div>
267
+ </script>
268
+
269
  <!-- Widget: Forms -->
270
  <script id="tmpl-nf-widget-forms" type="text/template">
271
  <header>
includes/Templates/admin-metabox-import-export-forms-import.html.php CHANGED
@@ -1,44 +1,30 @@
1
  <div class="wrap">
2
-
3
- <form action="" method="post" enctype="multipart/form-data">
4
- <input type="hidden" name="nf_import_security" id="nf_import_security"
5
- value="<?php echo wp_create_nonce( 'ninja_forms_import_form_nonce' );?>" />
6
- <table class="form-table">
7
- <tbody>
8
  <tr id="row_nf_import_form">
9
  <th scope="row">
10
- <label for="nf_import_form"><?php echo __( 'Select a file', 'ninja-forms' ); ?></label>
11
  </th>
12
  <td>
13
- <input type="file" name="nf_import_form" id="nf_import_form" class="widefat">
14
  </td>
15
  </tr>
16
- <tr id="row_nf_import_form_encoding">
17
- <th scope="row">
18
- <label for="nf_import_form_turn_off_encoding"><?php _e( 'Disable UTF-8 Encoding', 'ninja-forms' ); ?></label>
19
- </th>
20
- <td>
21
- <input type="checkbox" name="nf_import_form_turn_off_encoding"
22
- id="nf_import_form_turn_off_encoding">
23
- <label style="color:red;font-style: italic;"
24
- for="nf_import_form_turn_off_encoding">
25
- If you are having trouble importing forms, please
26
- click here to disable UTF-8 encoding
27
- and try again.
28
- </label>
29
- </td>
30
  </tr>
31
  <tr id="row_nf_import_form_submit">
32
  <th scope="row">
33
- <label for="nf_import_form_submit"><?php _e( 'Import Form', 'ninja-forms' ); ?></label>
34
  </th>
35
  <td>
36
- <input type="submit" id="nf_import_form_submit" class="button-secondary" value="<?php echo __( 'Import Form', 'ninja-forms' ) ;?>">
37
  </td>
38
  </tr>
39
- </tbody>
40
- </table>
41
-
42
- </form>
43
-
44
  </div>
1
  <div class="wrap">
2
+ <table class="form-table">
3
+ <tbody>
4
+ <tr id="row-nf-import-response" style="display:none;background-color:#ffc;">
5
+ <th></th>
6
+ <td><?php printf( __( 'Form Imported Successfully. %sView Form%s', 'ninja-forms' ), '<a id="nf-import-url" href="#">', '</a>' ); ?></td>
7
+ </tr>
8
  <tr id="row_nf_import_form">
9
  <th scope="row">
10
+ <label for="nf-import-file"><?php echo __( 'Select a file', 'ninja-forms' ); ?></label>
11
  </th>
12
  <td>
13
+ <input type="file" id="nf-import-file" class="widefat">
14
  </td>
15
  </tr>
16
+ <tr id="row-nf-import-type-error" style="display:none;color:red;">
17
+ <th></th>
18
+ <td><?php printf( __( 'Please select a Ninja Forms export. %sMust be in .nff format%s.', 'ninja-forms' ), '<strong>', '</strong>' ); ?></td>
 
 
 
 
 
 
 
 
 
 
 
19
  </tr>
20
  <tr id="row_nf_import_form_submit">
21
  <th scope="row">
22
+ <label for="nf-import-form-submit"><?php _e( 'Import Form', 'ninja-forms' ); ?></label>
23
  </th>
24
  <td>
25
+ <input type="button" id="nf-import-form-submit" class="button-secondary" value="<?php echo __( 'Import Form', 'ninja-forms' ) ;?>">
26
  </td>
27
  </tr>
28
+ </tbody>
29
+ </table>
 
 
 
30
  </div>
includes/Updates/CacheCollateActions.php ADDED
@@ -0,0 +1,342 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit;
2
+
3
+ /**
4
+ * Class NF_Updates_CacheCollateActions
5
+ *
6
+ * This class manages the step process of running through the CacheCollateActions required update.
7
+ * It will define an object to pull data from (if necessary) to pick back up if exited early.
8
+ * It will run an upgrade function to alter the nf3_actions and nf3_action_meta tables.
9
+ * Then, it will step over each form on the site, following this process:
10
+ * - Actions that exist in the data tables but not in the cache will be deleted.
11
+ * - Actions that exist in the cache but not in the data tables will be inserted.
12
+ * - Actions that exist in the data tables but have an incorrect form ID will be inserted as a new ID and referenced from the cache.
13
+ * - Actions that exist in both will be updated from the cache to ensure the data is correct.
14
+ * After completing the above for every form on the site, it will remove the data object that manages its location.
15
+ */
16
+ class NF_Updates_CacheCollateActions extends NF_Abstracts_RequiredUpdate
17
+ {
18
+
19
+ private $data = array();
20
+
21
+ private $running = array();
22
+
23
+ /**
24
+ * Stores information about the current form being processed.
25
+ * @var array
26
+ */
27
+ private $form;
28
+
29
+ /**
30
+ * Stores the actions for the current form being processed.
31
+ * @var array
32
+ */
33
+ private $actions;
34
+
35
+ /**
36
+ * Associative array of actions keyed by action id.
37
+ * @var array
38
+ */
39
+ private $actions_by_id = array();
40
+
41
+ /**
42
+ * Non-associative array of action ids.
43
+ * @var array
44
+ */
45
+ private $action_ids = array();
46
+
47
+ /**
48
+ * Hard limit for the number of querys we run during a single step.
49
+ * @var integer
50
+ */
51
+ private $limit = 10;
52
+
53
+ /**
54
+ * Array of action ids that need an update.
55
+ * @var array
56
+ */
57
+ private $update = array();
58
+
59
+ /**
60
+ * List of setting keys we don't want to save in the database.
61
+ * @var array
62
+ */
63
+ private $blacklist = array(
64
+ 'objectType',
65
+ 'objectDomain',
66
+ 'editActive',
67
+ 'title',
68
+ 'key',
69
+ );
70
+
71
+ /**
72
+ * The table names for our database queries.
73
+ */
74
+ private $table;
75
+ private $meta_table;
76
+
77
+ /**
78
+ * Constructor
79
+ *
80
+ * @param $data (Array) The data object passed in by the AJAX call.
81
+ * @param $running (Array) The array of required updates being run.
82
+ *
83
+ * @since 3.4.0
84
+ */
85
+ public function __construct( $data = array(), $running )
86
+ {
87
+ // Build our arguments array.
88
+ $args = array(
89
+ 'slug' => 'CacheCollateActions',
90
+ 'class_name' => 'NF_Updates_CacheCollateActions',
91
+ 'debug' => false,
92
+ );
93
+ $this->data = $data;
94
+ $this->running = $running;
95
+
96
+ // Call the parent constructor.
97
+ parent::__construct( $args );
98
+
99
+ // Set our table names.
100
+ $this->table = $this->db->prefix . 'nf3_actions';
101
+ $this->meta_table = $this->db->prefix . 'nf3_action_meta';
102
+
103
+ // Begin processing.
104
+ $this->process();
105
+ }
106
+
107
+ /**
108
+ * Function to loop over the batch.
109
+ *
110
+ * @since 3.4.0
111
+ */
112
+ public function process()
113
+ {
114
+ // If we've not already started...
115
+ if ( ! isset( $this->running[ 0 ][ 'running' ] ) ) {
116
+ // Run our startup method.
117
+ $this->startup();
118
+ }
119
+
120
+ /**
121
+ * Get all of our database variables up and running.
122
+ * Sets up class vars that are used in subsequent methods.
123
+ */
124
+ $this->setup_vars();
125
+
126
+ /**
127
+ * Update action values and meta if necessary.
128
+ */
129
+ $this->maybe_update_actions();
130
+
131
+ /**
132
+ * Saves our current location, along with any processing data we may need for the next step.
133
+ * If we're done with our step, runs cleanup instead.
134
+ */
135
+ $this->end_of_step();
136
+
137
+ // Respond to the AJAX call.
138
+ $this->respond();
139
+ }
140
+
141
+ /**
142
+ * Function to run any setup steps necessary to begin processing.
143
+ *
144
+ * @since 3.4.0
145
+ */
146
+ public function startup()
147
+ {
148
+ // Record that we're processing the update.
149
+ $this->running[ 0 ][ 'running' ] = true;
150
+ // If we're not debugging...
151
+ if ( ! $this->debug ) {
152
+ // Ensure that our data tables are updated.
153
+ $this->migrate( 'cache_collate_actions' );
154
+ }
155
+ // Get a list of our forms...
156
+ $sql = "SELECT ID FROM `{$this->db->prefix}nf3_forms`";
157
+ $forms = $this->db->get_results( $sql, 'ARRAY_A' );
158
+ $this->running[ 0 ][ 'forms' ] = $forms;
159
+ // Record the total number of steps in this batch.
160
+ $this->running[ 0 ][ 'steps' ] = count( $forms );
161
+ // Record our current step (defaulted to 0 here).
162
+ $this->running[ 0 ][ 'current' ] = 0;
163
+ }
164
+
165
+ /**
166
+ * Setup our global variables used in other methods.
167
+ *
168
+ * @since 3.4.0
169
+ * @return void
170
+ */
171
+ private function setup_vars()
172
+ {
173
+ // See which form we're currently working with.
174
+ $this->form = array_pop( $this->running[ 0 ][ 'forms' ] );
175
+
176
+ // Get the actions for that form.
177
+ $this->actions = Ninja_Forms()->form( $this->form[ 'ID' ] )->get_actions();
178
+
179
+ // For each action...
180
+ foreach ( $this->actions as $action ) {
181
+ // Add the ID to the list.
182
+ array_push( $this->action_ids, $action->get_id() );
183
+ $this->actions_by_id[ $action->get_id() ] = $action->get_settings();
184
+ }
185
+
186
+ // If we're continuing an old process...
187
+ if ( isset( $this->form[ 'update' ] ) ) {
188
+ // Fetch our remaining udpates.
189
+ $this->update = $this->form[ 'update' ];
190
+ } // Otherwise... (We're beginning a new process.)
191
+ else {
192
+ // Copy all IDs to our update list.
193
+ $this->update = $this->action_ids;
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Function to cleanup any lingering temporary elements of a required update after completion.
199
+ *
200
+ * @since 3.4.0
201
+ */
202
+ public function cleanup()
203
+ {
204
+ // Remove the current process from the array.
205
+ array_shift( $this->running );
206
+ // Record to our updates setting that this update is complete.
207
+ $this->confirm_complete();
208
+ // If we have no updates left to process...
209
+ if ( empty( $this->running ) ) {
210
+ // Call the parent cleanup method.
211
+ parent::cleanup();
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Check to see if we've locked processing.
217
+ * If we have, then we need to run this process again.
218
+ *
219
+ * If we haven't locked processing, prepare to end this process.
220
+ *
221
+ * @since 3.4.0
222
+ * @return void
223
+ */
224
+ private function end_of_step()
225
+ {
226
+ // If we have locked processing...
227
+ if ( $this->lock_process ) {
228
+ // Record that we have more to do.
229
+ $this->form[ 'update' ] = $this->update;
230
+ array_push( $this->running[ 0 ][ 'forms' ], $this->form );
231
+ } // Otherwise... (Processing isn't locked.)
232
+ else {
233
+ // If we have actions...
234
+ if ( ! empty( $this->action_ids ) ) {
235
+ // Update our meta keys.
236
+ $sql = "UPDATE `{$this->meta_table}` SET `meta_key` = `key` WHERE `parent_id` IN(" . implode( ',', $this->action_ids ) . ")";
237
+ $this->query( $sql );
238
+ }
239
+ /**
240
+ * Update our form cache with any action changes.
241
+ */
242
+ $this->update_form_cache();
243
+ // Increment our step count.
244
+ $this->running[ 0 ][ 'current' ] = intval( $this->running[ 0 ][ 'current' ] ) +1;
245
+ }
246
+
247
+
248
+ // Prepare to output our number of steps and current step.
249
+ $this->response[ 'stepsTotal' ] = $this->running[ 0 ][ 'steps' ];
250
+ $this->response[ 'currentStep' ] = $this->running[ 0 ][ 'current' ];
251
+
252
+ // If we do not have locked processing...
253
+ if ( ! $this->lock_process ) {
254
+ // If all steps have been completed...
255
+ if ( empty( $this->running[ 0 ] [ 'forms' ] ) ) {
256
+ // Run our cleanup method.
257
+ $this->cleanup();
258
+ }
259
+ }
260
+
261
+ // Record our current location in the process.
262
+ update_option( 'ninja_forms_doing_required_updates', $this->running );
263
+ // Prepare to output the number of updates remaining.
264
+ $this->response[ 'updatesRemaining' ] = count( $this->running );
265
+ }
266
+
267
+ /**
268
+ * If we've made any changes to our form actions, update our form cache to match.
269
+ *
270
+ * @since 3.4.0
271
+ * @return void
272
+ */
273
+ private function update_form_cache()
274
+ {
275
+ // Get the cache for that form.
276
+ $cache = WPN_Helper::get_nf_cache( $this->form[ 'ID' ] );
277
+ // Bust the cache.
278
+ $cache[ 'actions' ] = array();
279
+ // For each action...
280
+ foreach ( $this->actions_by_id as $id => $settings ) {
281
+ // Append the settings for that action to the cache.
282
+ $action = array();
283
+ $action[ 'settings' ] = $settings;
284
+ $action[ 'id' ] = $id;
285
+ array_push( $cache[ 'actions' ], $action );
286
+ }
287
+ // Save the cache, passing 2 as the current stage.
288
+ WPN_Helper::update_nf_cache( $this->form[ 'ID' ], $cache, 2 );
289
+ }
290
+
291
+ /**
292
+ * Loop over all of our actions and update our database if necessary.
293
+ * Check each setting against $this->blacklist to make sure we want to insert that value.
294
+ *
295
+ * @since 3.4.0
296
+ * @return void
297
+ */
298
+ private function maybe_update_actions()
299
+ {
300
+ // Declare placeholder values.
301
+ $sub_sql = array();
302
+ $meta_values = array();
303
+
304
+ // While we have actions to update...
305
+ while ( 0 < count( $this->update ) ) {
306
+ // If we have hit our limit...
307
+ if ( 1 > $this->limit ) {
308
+ // Lock processing.
309
+ $this->lock_process = true;
310
+ // Exit the loop.
311
+ break;
312
+ }
313
+ // Get our action to be updated.
314
+ $action = array_pop( $this->update );
315
+ // Get our settings.
316
+ $settings = $this->actions_by_id[ $action ];
317
+ // Update the new label column.
318
+ array_push( $sub_sql, "WHEN `id` = " . intval( $action ) . " THEN '" . $this->prepare( $settings[ 'label' ] ) . "'" );
319
+ // For each setting...
320
+ foreach ( $settings as $key => $setting ) {
321
+ // If the key is not blacklisted...
322
+ if ( ! in_array( $key, $this->blacklist ) ) {
323
+ // Add the value to be updated.
324
+ $action = intval( $action );
325
+ array_push( $meta_values, "WHEN `key` = '{$key}' AND `parent_id` = {$action} THEN '" . $this->prepare( $setting ) . "'" );
326
+ }
327
+ }
328
+ $this->limit--;
329
+ }
330
+
331
+ // If we've got updates to run...
332
+ if ( ! empty( $sub_sql ) ) {
333
+ // Update our actions table.
334
+ $sql = "UPDATE `{$this->table}` SET `label` = CASE " . implode ( ' ', $sub_sql ) . " ELSE `label` END;";
335
+ $this->query( $sql );
336
+ // Update our meta values.
337
+ $sql = "UPDATE `{$this->meta_table}` SET `meta_value` = CASE " . implode( ' ', $meta_values ) . " ELSE `meta_value` END;";
338
+ $this->query( $sql );
339
+ }
340
+ }
341
+
342
+ }
includes/Updates/CacheCollateCleanup.php ADDED
@@ -0,0 +1,507 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit;
2
+
3
+ /**
4
+ * Class NF_Updates_CacheCollateCleanup
5
+ *
6
+ * This class manages the step process of running through the CacheCollateCleanup required update.
7
+ * It will define an object to pull data from (if necessary) to pick back up if exited early.
8
+ * It will then step over each table in our structure and ensure that orphan records are removed from storage.
9
+ * It will then step over all submissions, removing any orphans.
10
+ * It will then step over all submissions (by form), updating or removing any orphan field records.
11
+ * After completing the above for every form on the site, it will remove the data object that manages its location.
12
+ */
13
+ class NF_Updates_CacheCollateCleanup extends NF_Abstracts_RequiredUpdate
14
+ {
15
+
16
+ private $data = array();
17
+
18
+ private $running = array();
19
+
20
+ /**
21
+ * The denominator object for calculating our steps.
22
+ * @var Integer
23
+ */
24
+ private $divisor = 500;
25
+
26
+ private $stage;
27
+
28
+ private $stage_complete = false;
29
+
30
+ /**
31
+ * Constructor
32
+ *
33
+ * @param $data (Array) The data object passed in by the AJAX call.
34
+ * @param $running (Array) The array of required updates being run.
35
+ *
36
+ * @since 3.4.0
37
+ */
38
+ public function __construct( $data = array(), $running )
39
+ {
40
+ // Build our arguments array.
41
+ $args = array(
42
+ 'slug' => 'CacheCollateCleanup',
43
+ 'class_name' => 'NF_Updates_CacheCollateCleanup',
44
+ 'debug' => false,
45
+ );
46
+ $this->data = $data;
47
+ $this->running = $running;
48
+
49
+ // Call the parent constructor.
50
+ parent::__construct( $args );
51
+
52
+ // Begin processing.
53
+ $this->process();
54
+ }
55
+
56
+ /**
57
+ * Function to loop over the batch.
58
+ *
59
+ * @since 3.4.0
60
+ */
61
+ public function process()
62
+ {
63
+ // If we've not already started...
64
+ if ( ! isset( $this->running[ 0 ][ 'running' ] ) ) {
65
+ // Run our startup method.
66
+ $this->startup();
67
+ }
68
+ $this->stage = $this->running[ 0 ][ 'stages' ][ 0 ];
69
+
70
+ // Determine what process to run.
71
+ switch ( $this->stage[ 'table' ] ) {
72
+ case 'submissions':
73
+ // If we've cleaned out all orphan submissions...
74
+ if ( $this->stage[ 'purged' ] ) {
75
+ // Work on clearing out orphan field records.
76
+ $this->adopt_fields();
77
+ } // Otherwise... (We still have orphan submissions.)
78
+ else {
79
+ // Remove orphan submissions.
80
+ $this->adopt_subs();
81
+ }
82
+ break;
83
+ default:
84
+ // If we need to step over this process...
85
+ if ( $this->divisor < $this->stage[ 'parent_total' ] ) {
86
+ // Call stepped deletion.
87
+ $this->do_step_delete();
88
+ } // Otherwise... (We don't need to step over this.)
89
+ else {
90
+ // Call simple deletion.
91
+ $this->do_easy_delete();
92
+ }
93
+ // Increment our step count.
94
+ $this->running[ 0 ][ 'current' ] += 1;
95
+ break;
96
+ }
97
+ // If we have completed the current stage...
98
+ if ( $this->stage_complete ) {
99
+ // Remove it from our list.
100
+ array_shift( $this->running[ 0 ][ 'stages' ] );
101
+ } // Otherwise... (We have not completed the stage)
102
+ else {
103
+ // Record our changes.
104
+ $this->running[ 0 ][ 'stages' ][ 0 ] = $this->stage;
105
+ }
106
+ // Prepare to output our number of steps and current step.
107
+ $this->response[ 'stepsTotal' ] = $this->running[ 0 ][ 'steps' ];
108
+ $this->response[ 'currentStep' ] = $this->running[ 0 ][ 'current' ];
109
+
110
+ // If we have no stages left...
111
+ if ( empty( $this->running[ 0 ][ 'stages' ] ) ) {
112
+ // Run our cleanup method.
113
+ $this->cleanup();
114
+ }
115
+
116
+ // Prepare to output the number of updates remaining.
117
+ $this->response[ 'updatesRemaining' ] = count( $this->running );
118
+
119
+ // Record our current location in the process.
120
+ update_option( 'ninja_forms_doing_required_updates', $this->running );
121
+
122
+ // Respond to the AJAX call.
123
+ $this->respond();
124
+ }
125
+
126
+ /**
127
+ * Function to run any setup steps necessary to begin processing.
128
+ *
129
+ * @since 3.4.0
130
+ */
131
+ public function startup()
132
+ {
133
+ // Record that we're processing the update.
134
+ $this->running[ 0 ][ 'running' ] = true;
135
+
136
+ // Get the number of records in the forms table.
137
+ $form_count = $this->get_total( 'nf3_forms' );
138
+ // Get the number of records in the fields table.
139
+ $action_count = $this->get_total( 'nf3_actions' );
140
+ // Get the number of records in the actions table.
141
+ $field_count = $this->get_total( 'nf3_fields' );
142
+ // Get the number of records in the objects table.
143
+ $object_count = $this->get_total( 'nf3_objects' );
144
+
145
+ // Get all form_ids from the db for the submissions portion.
146
+ $sql = "SELECT `id` FROM `{$this->db->prefix}nf3_forms`";
147
+ $forms = $this->db->get_results( $sql, 'ARRAY_A' );
148
+
149
+ $stages = array(
150
+ array(
151
+ 'table' => $this->db->prefix . 'nf3_form_meta',
152
+ 'parent' => $this->db->prefix . 'nf3_forms',
153
+ 'parent_total' => $form_count,
154
+ ),
155
+ array(
156
+ 'table' => $this->db->prefix . 'nf3_actions',
157
+ 'parent' => $this->db->prefix . 'nf3_forms',
158
+ 'parent_total' => $form_count,
159
+ ),
160
+ array(
161
+ 'table' => $this->db->prefix . 'nf3_action_meta',
162
+ 'parent' => $this->db->prefix . 'nf3_actions',
163
+ 'parent_total' => $action_count,
164
+ ),
165
+ array(
166
+ 'table' => $this->db->prefix . 'nf3_fields',
167
+ 'parent' => $this->db->prefix . 'nf3_forms',
168
+ 'parent_total' => $form_count,
169
+ ),
170
+ array(
171
+ 'table' => $this->db->prefix . 'nf3_field_meta',
172
+ 'parent' => $this->db->prefix . 'nf3_fields',
173
+ 'parent_total' => $field_count,
174
+ ),
175
+ array(
176
+ 'table' => $this->db->prefix . 'nf3_object_meta',
177
+ 'parent' => $this->db->prefix . 'nf3_objects',
178
+ 'parent_total' => $object_count,
179
+ ),
180
+ array(
181
+ 'table' => 'submissions',
182
+ 'parent_total' => $form_count,
183
+ 'purged' => false,
184
+ 'forms' => $forms,
185
+ ),
186
+ );
187
+
188
+ $add = 0;
189
+ // Set the steps for form meta (enforcing a minimum step count).
190
+ $add = ceil( $form_count / $this->divisor );
191
+ $steps = ( 0 == $add ) ? 1 : $add;
192
+ // Add actions and fields.
193
+ $steps *= 3;
194
+ // Add action meta (enforcing a minimum step count).
195
+ $add = ceil( $action_count / $this->divisor );
196
+ $add = ( 0 == $add ) ? 1 : $add;
197
+ $steps += $add;
198
+ // Add field meta (enforcing a minimum step count).
199
+ $add = ceil( $field_count / $this->divisor );
200
+ $add = ( 0 == $add ) ? 1 : $add;
201
+ $steps += $add;
202
+ // Add object meta (enforcing a minimum step count).
203
+ $add = ceil( $object_count / $this->divisor );
204
+ $add = ( 0 == $add ) ? 1 : $add;
205
+ $steps += $add;
206
+ // Add one plus the form count for submissions (enforcing a minimum step count).
207
+ $add = $form_count + 1;
208
+ $add = ( 1 == $add ) ? 2 : $add;
209
+ $steps += $add;
210
+
211
+ $this->running[ 0 ][ 'stages' ] = $stages;
212
+
213
+ // Record the total number of steps in this batch.
214
+ $this->running[ 0 ][ 'steps' ] = $steps;
215
+ // Record our current step (defaulted to 0 here).
216
+ $this->running[ 0 ][ 'current' ] = 0;
217
+ }
218
+
219
+ /**
220
+ * Function to cleanup any lingering temporary elements of a required update after completion.
221
+ *
222
+ * @since 3.4.0
223
+ */
224
+ public function cleanup()
225
+ {
226
+ // Remove the current process from the array.
227
+ array_shift( $this->running );
228
+ // Record to our updates setting that this update is complete.
229
+ $this->confirm_complete();
230
+ // If we have no updates left to process...
231
+ if ( empty( $this->running ) ) {
232
+ // Call the parent cleanup method.
233
+ parent::cleanup();
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Function to get the number of objects in a table.
239
+ *
240
+ * @param $table (String) The name of the target table.
241
+ *
242
+ * @return (Int) The count of rows.
243
+ *
244
+ * @since 3.4.0
245
+ */
246
+ public function get_total( $table )
247
+ {
248
+ $sql = "SELECT COUNT( `id` ) AS Total FROM `{$this->db->prefix}{$table}`;";
249
+ $result = $this->db->get_results( $sql, 'ARRAY_A' );
250
+ return intval( $result[ 0 ][ 'Total' ] );
251
+ }
252
+
253
+ /**
254
+ * Function to perform a simple, single-step delete of orphan data.
255
+ *
256
+ * @since 3.4.0
257
+ */
258
+ private function do_easy_delete()
259
+ {
260
+ // Remove the orphan records.
261
+ $sql = "DELETE FROM `{$this->stage[ 'table' ]}`
262
+ WHERE `parent_id` NOT IN(
263
+ SELECT `id` FROM `{$this->stage[ 'parent' ]}`
264
+ );";
265
+ $this->query( $sql );
266
+ // Confirm that this stage is complete.
267
+ $this->stage_complete = true;
268
+ }
269
+
270
+ /**
271
+ * Function to perform a multi-step delete of orphan data.
272
+ *
273
+ * @since 3.4.0
274
+ */
275
+ private function do_step_delete()
276
+ {
277
+ // Get records from our table.
278
+ $sub_sql = "SELECT DISTINCT( `parent_id` ) AS id FROM `{$this->stage[ 'table' ]}` ";
279
+ // If we have a previous last record...
280
+ if ( isset( $this->stage[ 'last' ] ) ) {
281
+ // Make sure we exclude anything before it from the result.
282
+ $sub_sql .= "WHERE `parent_id` > " . $this->stage[ 'last' ] . " ";
283
+ }
284
+ $sub_sql .= "ORDER BY `parent_id` ASC
285
+ LIMIT {$this->divisor};";
286
+ $result = $this->db->get_results( $sub_sql, 'ARRAY_A' );
287
+ // Get the last affected row.
288
+ $last = end( $result );
289
+ // Squash our results to get a non-associative array.
290
+ $result = $this->array_squash( $result );
291
+ $this->stage[ 'last' ] = intval( $last[ 'id' ] );
292
+ // Get records from the parent table.
293
+ $sql = "SELECT `id` FROM `{$this->stage[ 'parent' ]}`
294
+ WHERE `id` IN(" . implode( ', ', $result ) .
295
+ ");";
296
+ $parent_result = $this->db->get_results( $sql, 'ARRAY_A' );
297
+ // If we didn't get the same number of results...
298
+ if ( count( $result ) !== count( $parent_result ) ) {
299
+ // Merge our results.
300
+ $result = array_merge( $result, $parent_result );
301
+ // Convert the array to something we can sort by duplicates.
302
+ $temp = array_count_values( array_column( $result, 'id' ) );
303
+ // Get rid of all values that had more than 1 result.
304
+ $temp = array_filter( $temp, array( $this, 'uniquify' ) );
305
+ // Schedule all of the single result values for deletion.
306
+ $delete = implode( ', ', array_keys( $temp ) );
307
+ $sql = "DELETE FROM `{$this->stage[ 'table' ]}`
308
+ WHERE `parent_id` IN( {$delete} );";
309
+ $this->query( $sql );
310
+ }
311
+ // If we've not already determined the limit of this table...
312
+ if ( ! isset( $this->stage[ 'max' ] ) ) {
313
+ // Fetch the maximum value.
314
+ $sql = "SELECT MAX( `parent_id` ) FROM `{$this->stage[ 'table' ]}`";
315
+ $result = $this->db->get_results( $sql, 'ARRAY_A' );
316
+ // Save a reference to it.
317
+ $this->stage[ 'max' ] = intval( $result[ 0 ][ 'parent_id' ] );
318
+ }
319
+ // If our last record is equal to our maximum record...
320
+ if ( $this->stage[ 'last' ] == $this->stage[ 'max' ] ) {
321
+ // Record that we're done with this stage.
322
+ $this->stage_complete = true;
323
+ }
324
+ }
325
+
326
+ /**
327
+ * Function called by array_filter to remove ALL duplicate results.
328
+ *
329
+ * @param $v (Int) The number of results.
330
+ *
331
+ * @return (Boolean)
332
+ *
333
+ * @since 3.4.0
334
+ */
335
+ private function uniquify( $v )
336
+ {
337
+ // If we have 1 result, keep it.
338
+ return $v == 1;
339
+ }
340
+
341
+ /**
342
+ * Function to remove orphan submissions from the posts and postmeta tables.
343
+ *
344
+ * @since 3.4.0
345
+ */
346
+ private function adopt_subs()
347
+ {
348
+ // Fetch a limited number of orphan subs to delete.
349
+ $sub_sql = "SELECT m.post_id AS id from `{$this->db->prefix}postmeta` AS m
350
+ LEFT OUTER JOIN `{$this->db->prefix}posts` AS p
351
+ ON m.post_id = p.id
352
+ WHERE m.meta_key = '_form_id'
353
+ AND p.post_type = 'nf_sub'
354
+ AND m.meta_value NOT IN(
355
+ SELECT `id` from `{$this->db->prefix}nf3_forms`
356
+ )
357
+ ORDER BY m.post_id ASC
358
+ LIMIT {$this->divisor};";
359
+ $result = $this->db->get_results( $sub_sql, 'ARRAY_A' );
360
+ // Count them.
361
+ $count = count( $result );
362
+ // Squash our results to get a non-associative array.
363
+ $result = $this->array_squash( $result );
364
+ // If we have records to be deleted...
365
+ if ( 0 < $count ) {
366
+ // Remove their postmeta.
367
+ $sql = "DELETE FROM `{$this->db->prefix}postmeta`
368
+ WHERE post_id IN(" . implode( ', ', $result ) . ");";
369
+ $this->query( $sql );
370
+ // Remove the posts.
371
+ $sql = "DELETE FROM `{$this->db->prefix}posts`
372
+ WHERE id IN(" . implode( ', ', $result ) . ");";
373
+ $this->query( $sql );
374
+ }
375
+ // If the number of affected rows was less than our divisor...
376
+ if ( $count < $this->divisor ) {
377
+ // Mark that we've completed the process.
378
+ $this->stage[ 'purged' ] = true;
379
+ // Increment our step count.
380
+ $this->running[ 0 ][ 'current' ] += 1;
381
+ }
382
+ }
383
+
384
+ /**
385
+ * Function to remove orphan field data that's still attached
386
+ * to valid submissions from the postmeta table.
387
+ *
388
+ * @since 3.4.0
389
+ */
390
+ private function adopt_fields()
391
+ {
392
+ // Get the form to work with.
393
+ $form = array_pop( $this->stage[ 'forms' ] );
394
+ // Fetch meta rows that match our criteria.
395
+ $sql = "SELECT r.meta_id, r.meta_key AS field_id
396
+ FROM `{$this->db->prefix}posts` AS p
397
+ LEFT JOIN `{$this->db->prefix}postmeta` AS r
398
+ ON r.post_id = p.id
399
+ LEFT JOIN `{$this->db->prefix}postmeta` AS f
400
+ ON f.post_id = p.id
401
+ WHERE p.post_type = 'nf_sub'
402
+ AND f.meta_key = '_form_id'
403
+ AND f.meta_value = " . intval( $form[ 'id' ] ) . "
404
+ AND r.meta_key LIKE '_field_%' ";
405
+ // If last is set...
406
+ if ( isset( $form[ 'last' ] ) ) {
407
+ // Make sure we're getting new results instead of old ones.
408
+ $sql .= "AND r.meta_id > {$form[ 'last' ]} ";
409
+ }
410
+ $sql .= "ORDER BY r.meta_id ASC
411
+ LIMIT {$this->divisor};";
412
+ $results = $this->db->get_results( $sql, 'ARRAY_A' );
413
+ // Count them.
414
+ $count = count( $result );
415
+ // Get all fields associated with this form.
416
+ $sql = "SELECT id FROM `{$this->db->prefix}nf3_fields` WHERE parent_id = " . intval( $form[ 'id' ] ) . ";";
417
+ $fields = $this->db->get_results( $sql, 'ARRAY_A' );
418
+ // Squash our results to get a non-associative array.
419
+ $fields = $this->array_squash( $fields );
420
+ $bad_records = array();
421
+ // For each result...
422
+ foreach( $results as $result ) {
423
+ // Pull the text out of our meta.
424
+ $id = str_replace( '_field_', '', $result[ 'field_id' ] );
425
+ $id = intval( $id );
426
+ // If this field isn't on the form...
427
+ if ( ! in_array( $id, $fields ) ) {
428
+ // Add it to our list to check later.
429
+ $bad_records[ $id ][] = $result[ 'meta_id' ];
430
+ }
431
+ }
432
+
433
+ // Get a list of any old_field_ids from our field table update.
434
+ $sql = "SELECT f.id AS new_id, m.meta_value AS id
435
+ FROM `{$this->db->prefix}nf3_fields` AS f
436
+ LEFT JOIN `{$this->db->prefix}nf3_field_meta` AS m
437
+ ON f.id = m.parent_id
438
+ WHERE f.parent_id = " . intval( $form[ 'id' ] ) . "
439
+ AND m.meta_key = 'old_field_id';";
440
+ $old_ids = $this->db->get_results( $sql, 'ARRAY_A' );
441
+ // Squash our results to get an associative array.
442
+ $old_ids = $this->array_squash( $old_ids );
443
+
444
+ // For each id in the bad records list...
445
+ foreach ( $bad_records as $field => $meta ) {
446
+ // If we have a new ID for that record...
447
+ if ( isset( $old_ids[ $field ] ) ) {
448
+ // Update our submissions.
449
+ $sql = "UPDATE `{$this->db->prefix}postmeta`
450
+ SET `meta_key` = '_field_" . $old_ids[ $field ] . "'
451
+ WHERE `meta_id` IN(" . implode( ', ', $meta ) . ");";
452
+ $this->query( $sql );
453
+ } // Otherwise... (We don't have a new ID for it.)
454
+ else {
455
+ // Delete the orphan record.
456
+ $sql = "DELETE FROM `{$this->db->prefix}postmeta`
457
+ WHERE `meta_id` IN(" . implode( ', ', $meta ) . ");";
458
+ $this->query( $sql );
459
+ }
460
+ }
461
+ // If the number of affected rows was less than our divisor...
462
+ if ( $count < $this->divisor ) {
463
+ // Increment our step count.
464
+ $this->running[ 0 ][ 'current' ] += 1;
465
+ } // Otherwise... (We need to continue.)
466
+ else {
467
+ // Put our form back on the stack.
468
+ array_push( $this->stage[ 'forms' ], $form );
469
+ }
470
+ // If there are no forms left to process...
471
+ if ( empty( $this->stage[ 'forms' ] ) ) {
472
+ // Mark that this stage is done.
473
+ $this->stage_complete = true;
474
+ }
475
+ }
476
+
477
+ /**
478
+ * Function to compress our db results into a more useful format.
479
+ *
480
+ * @param $data (Array) The result to be compressed.
481
+ *
482
+ * @return (Array) Associative if our data was complex.
483
+ * Non-associative if our data was a single item.
484
+ *
485
+ * @since 3.4.0
486
+ */
487
+ private function array_squash( $data )
488
+ {
489
+ $response = array();
490
+ // For each item in the array...
491
+ foreach ( $data as $row ) {
492
+ // If the item has more than 1 attribute...
493
+ if ( 1 < count( $row ) ) {
494
+ // Assign the data to an associated result.
495
+ $response[ $row[ 'id' ] ] = $row;
496
+ // Unset the id setting, as that will be the key.
497
+ unset( $response[ $row[ 'id' ] ][ 'id' ] );
498
+ } // Otherwise... (We only have 1 attribute.)
499
+ else {
500
+ // Add the id to the stack in a non-associated result.
501
+ $response[] = intval( $row[ 'id' ] );
502
+ }
503
+ }
504
+ return $response;
505
+ }
506
+
507
+ }
includes/Updates/CacheCollateFields.php ADDED
@@ -0,0 +1,720 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit;
2
+
3
+ /**
4
+ * Class NF_Updates_CacheCollateFields
5
+ *
6
+ * This class manages the step process of running through the CacheCollateFields required update.
7
+ * It will define an object to pull data from (if necessary) to pick back up if exited early.
8
+ * It will run an upgrade function to alter the nf3_fields and nf3_field_meta tables.
9
+ * Then, it will step over each form on the site, following this process:
10
+ * - Fields that exist in the data tables but not in the cache will be deleted.
11
+ * - Fields that exist in the cache but not in the data tables will be inserted.
12
+ * - Fields that exist in the data tables but have an incorrect form ID will be inserted as a new ID and referenced from the cache.
13
+ * - Fields that exist in both will be updated from the cache to ensure the data is correct.
14
+ * After completing the above for every form on the site, it will remove the data object that manages its location.
15
+ */
16
+ class NF_Updates_CacheCollateFields extends NF_Abstracts_RequiredUpdate
17
+ {
18
+
19
+ private $data = array();
20
+
21
+ private $running = array();
22
+
23
+ /**
24
+ * Non-associatve array of field ids from the cache.
25
+ * @var array
26
+ */
27
+ private $field_ids = array();
28
+
29
+ /**
30
+ * Associative array of field ids from the cache, using the field id as the key.
31
+ * $fields_by_id[ field_id ] = $settings;
32
+ * @var array
33
+ */
34
+ private $fields_by_id = array();
35
+
36
+ /**
37
+ * Non-associative array that tracks what we should we insert because it exists in our cache but not in the Fields table.
38
+ * @var array
39
+ */
40
+ private $insert = array();
41
+
42
+ /**
43
+ * Non-associatve array that tracks field ids that should be deleted from fields DB table.
44
+ * @var array
45
+ */
46
+ private $delete = array();
47
+
48
+ /**
49
+ * Associative array that tracks field ids that have changed.
50
+ * $submission_updates[ old_field_id ] = new_field_id;
51
+ * @var array
52
+ */
53
+ private $submission_updates = array();
54
+
55
+ /**
56
+ * Associatve array that tracks newly inserted fields.
57
+ * $insert_ids[ field_id ] = field_id;
58
+ * @var array
59
+ */
60
+ private $insert_ids = array();
61
+
62
+ /**
63
+ * Hard limit for the number of querys we run during a single step.
64
+ * @var integer
65
+ */
66
+ private $limit = 10;
67
+
68
+ /**
69
+ * Stores information about the current form being processed.
70
+ * @var array
71
+ */
72
+ private $form;
73
+
74
+ /**
75
+ * The table names for our database queries.
76
+ */
77
+ private $table;
78
+ private $meta_table;
79
+
80
+ /**
81
+ * Constructor
82
+ *
83
+ * @param $data (Array) The data object passed in by the AJAX call.
84
+ * @param $running (Array) The array of required updates being run.
85
+ *
86
+ * @since 3.4.0
87
+ */
88
+ public function __construct( $data = array(), $running )
89
+ {
90
+ // Build our arguments array.
91
+ $args = array(
92
+ 'slug' => 'CacheCollateFields',
93
+ 'class_name' => 'NF_Updates_CacheCollateFields',
94
+ 'debug' => false,
95
+ );
96
+ $this->data = $data;
97
+ $this->running = $running;
98
+
99
+ // Call the parent constructor.
100
+ parent::__construct( $args );
101
+
102
+ // Set our table names.
103
+ $this->table = $this->db->prefix . 'nf3_fields';
104
+ $this->meta_table = $this->db->prefix . 'nf3_field_meta';
105
+
106
+ // Begin processing.
107
+ $this->process();
108
+ }
109
+
110
+ /**
111
+ * Function to loop over the batch.
112
+ *
113
+ * @since 3.4.0
114
+ */
115
+ public function process()
116
+ {
117
+ // If we've not already started...
118
+ if ( ! isset( $this->running[ 0 ][ 'running' ] ) ) {
119
+ // Run our startup method.
120
+ $this->startup();
121
+ }
122
+
123
+ /**
124
+ * Get all of our database variables up and running.
125
+ * Sets up class vars that are used in subsequent methods.
126
+ */
127
+ $this->setup_vars();
128
+
129
+
130
+ /**
131
+ * Run SQL queries to delete fields if necessary.
132
+ */
133
+ $this->maybe_delete_fields();
134
+ /**
135
+ * Insert fields if necessary.
136
+ * Also sets up the class var $submission_updates with duplicate ids that need replaced.
137
+ */
138
+ $this->maybe_insert_fields();
139
+ /**
140
+ * Update submission post meta if necessary.
141
+ * Uses the class var $submission_updates setup in the method above.
142
+ */
143
+ $this->maybe_update_submissions();
144
+ /**
145
+ * If we have fields that exist in the DB for a form, update those with cache settings.
146
+ */
147
+ $this->maybe_update_fields();
148
+ /**
149
+ * Update our form cache with any field id changes.
150
+ */
151
+ $this->update_form_cache();
152
+ /**
153
+ * Saves our current location, along with any processing data we may need for the next step.
154
+ * If we're done with our step, runs cleanup instead.
155
+ */
156
+ $this->end_of_step();
157
+
158
+
159
+
160
+ /**
161
+ * Respond to the AJAX call.
162
+ */
163
+ $this->respond();
164
+ }
165
+
166
+
167
+
168
+ /**
169
+ * Function to run any setup steps necessary to begin processing.
170
+ *
171
+ * @since 3.4.0
172
+ */
173
+ public function startup()
174
+ {
175
+ // Record that we're processing the update.
176
+ $this->running[ 0 ][ 'running' ] = true;
177
+ // If we're not debugging...
178
+ if ( ! $this->debug ) {
179
+ // Ensure that our data tables are updated.
180
+ $this->migrate( 'cache_collate_fields' );
181
+ }
182
+ // Get a list of our forms...
183
+ $sql = "SELECT ID FROM `{$this->db->prefix}nf3_forms`";
184
+ $forms = $this->db->get_results( $sql, 'ARRAY_A' );
185
+ $this->running[ 0 ][ 'forms' ] = $forms;
186
+ // Record the total number of steps in this batch.
187
+ $this->running[ 0 ][ 'steps' ] = count( $forms );
188
+ // Record our current step (defaulted to 0 here).
189
+ $this->running[ 0 ][ 'current' ] = 0;
190
+ }
191
+
192
+ /**
193
+ * Function to cleanup any lingering temporary elements of a required update after completion.
194
+ *
195
+ * @since 3.4.0
196
+ */
197
+ public function cleanup()
198
+ {
199
+ // Remove the current process from the array.
200
+ array_shift( $this->running );
201
+ // Record to our updates setting that this update is complete.
202
+ $this->confirm_complete();
203
+ // If we have no updates left to process...
204
+ if ( empty( $this->running ) ) {
205
+ // Call the parent cleanup method.
206
+ parent::cleanup();
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Function to delete unncessary items from our existing tables.
212
+ *
213
+ * @param $items (Array) The list of ids to be deleted.
214
+ *
215
+ * @since 3.4.0
216
+ */
217
+ public function maybe_delete_fields()
218
+ {
219
+ if ( empty( $this->delete ) ) {
220
+ return false;
221
+ }
222
+
223
+ // Delete all meta for those fields.
224
+ $sql = "DELETE FROM `{$this->meta_table}` WHERE parent_id IN(" . implode( ', ', $this->delete ) . ")";
225
+ $this->query( $sql );
226
+ // Delete the fields.
227
+ $sql = "DELETE FROM `{$this->table}` WHERE id IN(" . implode( ', ', $this->delete ) . ")";
228
+ $this->query( $sql );
229
+
230
+ $this->delete = array();
231
+ }
232
+
233
+ /**
234
+ * Most of the methods in this class use class vars to access and store data.
235
+ *
236
+ * This method sets the initial state of these class vars.
237
+ * Class vars include:
238
+ * $form <- reference to the form currently being processed.
239
+ * $field_ids <- non-associatve array of field ids from the cache.
240
+ * $insert <- array that tracks what we should we insert because it exists in our cache but not in the Fields table.
241
+ * $submission_updates <- array that tracks fields that have had their id changed.
242
+ * $fields_by_id <- associative array of field ids from the cache, using the field id as the key.
243
+ *
244
+ * If we are not running a form for the first time,
245
+ * we set class vars based on what we have been passed.
246
+ * After setting those class vars, we bail early.
247
+ *
248
+ * If we are running for the first time, set have to hit the database to
249
+ * get the information for class vars.
250
+ *
251
+ * We need to compare the fields in our form cache to those in the fields table.
252
+ * Ultimately, we're trying to make sure that our fields table matches our form cache.
253
+ *
254
+ * Since we're treating the cache as the truth, we want to remove fields that don't exist in the cache.
255
+ * We also want to insert any fields that exist in the cache, but not in the fields table.
256
+ *
257
+ * This method doesn't perform those operations, but it sets the class vars that the appropriate
258
+ * methods use to figure out what to add and remove.
259
+ *
260
+ * @since 3.4.0
261
+ * @return void
262
+ */
263
+ private function setup_vars()
264
+ {
265
+ // Set the form we're currently working with.
266
+ $this->form = array_pop( $this->running[ 0 ][ 'forms' ] );
267
+ // Enable maintenance mode on the front end when the fields start processing.
268
+ $this->enable_maintenance_mode( $this->db->prefix, $this->form[ 'ID' ] );
269
+
270
+ // Get the fields for our form from the cache.
271
+ $form_cache = WPN_Helper::get_nf_cache( $this->form[ 'ID' ] );
272
+ // Create an empty $fields array.
273
+ $fields = array();
274
+ /**
275
+ * Loop over our cached form fields and instantiate a model for each.
276
+ * Update its settings to match those in the cache.
277
+ * Add it to our $fields array.
278
+ */
279
+ foreach( $form_cache[ 'fields' ] as $cached_field ){
280
+ // Create a new model for this field.
281
+ $field = new NF_Database_Models_Field( $this->db, $cached_field[ 'id' ], $this->form[ 'ID' ] );
282
+ // Update settings to match cache.
283
+ $field->update_settings( $cached_field[ 'settings' ] );
284
+ // Add this to our $fields array, using the field id as the key.
285
+ $fields[ $field->get_id() ] = $field;
286
+ }
287
+
288
+ /**
289
+ * For each field in our cache, add it to our class vars:
290
+ * field_ids <- non-associatve array of field ids from the cache.
291
+ * fields_by_id <- associative array of field ids from the cache, using the field id as the key.
292
+ */
293
+ foreach ( $fields as $field ) {
294
+ array_push( $this->field_ids, $field->get_id() );
295
+ $this->fields_by_id[ $field->get_id() ] = $field->get_settings();
296
+ }
297
+
298
+ /**
299
+ * If we're continuing a process, set our class vars appropriately.
300
+ * Bail early so that nothing else fires.
301
+ */
302
+ if ( isset( $this->form[ 'field_ids' ] ) ) {
303
+ $this->field_ids = $this->form[ 'field_ids' ];
304
+ $this->insert = $this->form[ 'insert' ];
305
+ $this->submission_updates = $this->form[ 'submission_updates' ];
306
+ return false;
307
+ }
308
+ /**
309
+ * We need to cross reference the Fields table to see if these ids exist for this form.
310
+ * If they exist in the table, we don't need to insert them.
311
+ */
312
+ $sql = "SELECT id FROM `{$this->table}` WHERE parent_id = {$this->form[ 'ID' ]}";
313
+ $db_fields = $this->db->get_results( $sql, 'ARRAY_A' );
314
+ $db_field_ids = array();
315
+ /**
316
+ * Loop over every field that exists in the table:
317
+ * If it doesn't exist in the cache, add it to our delete class var so that it is deleted later.
318
+ * If it does exist in both, add it to our $db_field_ids array for later comparison.
319
+ */
320
+ foreach ( $db_fields as $field ) {
321
+ // If we have no reference to it in the cache...
322
+ if ( ! in_array( $field[ 'id' ], $this->field_ids ) ) {
323
+ // Schedule it for deletion.
324
+ array_push( $this->delete, $field[ 'id' ] );
325
+ } else { // Push the id onto our comparison array.
326
+ array_push( $db_field_ids, $field[ 'id' ] );
327
+ }
328
+ }
329
+
330
+ /**
331
+ * Loop over every field that exists in our form cache to see if we need to insert it.
332
+ */
333
+ foreach ( $this->field_ids as $field ) {
334
+ // If we have no reference to it in the fields table...
335
+ if ( ! in_array( $field, $db_field_ids ) ) {
336
+ // Schedule it for insertion.
337
+ array_push( $this->insert, $field );
338
+ }
339
+ }
340
+ /**
341
+ * Cross reference the Fields table to see if these ids exist on other Forms.
342
+ * If an id exists on another form, then we need to change the current field's id and add that field to our submission_updates class var.
343
+ */
344
+ if ( ! empty( $this->field_ids ) ) {
345
+ $sql = "SELECT id FROM `{$this->table}` WHERE id IN(" . implode( ', ', $this->field_ids ) . ") AND parent_id <> {$this->form[ 'ID' ]}";
346
+ $duplicates = $this->db->get_results( $sql, 'ARRAY_A' );
347
+ } else {
348
+ $duplicates = array();
349
+ }
350
+ /**
351
+ * If we got something back, there were duplicates.
352
+ */
353
+ if ( ! empty( $duplicates ) ) {
354
+ /**
355
+ * Loop over our duplicates and add it to our insert class var if it isn't already there.
356
+ * Also, add this field to our submission_updates class var so that we can handle the id change later.
357
+ */
358
+ foreach ( $duplicates as $duplicate ) {
359
+ if ( ! in_array( $duplicate[ 'id' ], $this->insert ) ) {
360
+ array_push( $this->insert, $duplicate[ 'id' ] );
361
+ }
362
+
363
+ $this->submission_updates[ $duplicate[ 'id' ] ] = true;
364
+ }
365
+ }
366
+ }
367
+
368
+ /**
369
+ * Our setup_vars method adds fields to the insert class var.
370
+ * Any fields that are in this array need to be inserted into our database.
371
+ *
372
+ * This is the first insert/update method to run, so it doesn't check lock_process.
373
+ * If the insert class var is empty, then we bail early.
374
+ *
375
+ * @since 3.4.0
376
+ * @return void
377
+ */
378
+ private function maybe_insert_fields()
379
+ {
380
+ // If we don't have any items to insert, bail early.
381
+ if ( empty( $this->insert ) ) {
382
+ return false;
383
+ }
384
+
385
+ // Store the meta items outside the loop for faster insertion.
386
+ $meta_items = array();
387
+ $flush_ids = array();
388
+ // While we still have items to insert...
389
+ while ( 0 < count( $this->insert ) ) {
390
+ // If we have hit our limit...
391
+ if ( 1 > $this->limit ) {
392
+ // Lock processing.
393
+ $this->lock_process = true;
394
+ // Exit the loop.
395
+ break;
396
+ }
397
+ // Get our item to be inserted.
398
+ $inserting = array_pop( $this->insert );
399
+ $settings = $this->fields_by_id[ $inserting ];
400
+
401
+ /*
402
+ * We want to preserve the field ids from the cache if we can.
403
+ * To do this, we check our $this->submission_updates array for this current field.
404
+ * If it doesn't exist in the array, we can trust the cached field id.
405
+ * If it exists in that array, then this is a duplicate.
406
+ */
407
+ if ( ! isset( $this->submission_updates[ $inserting ] ) ) {
408
+ $maybe_field_id = intval( $inserting ); // Use the cached field id.
409
+ } else {
410
+ $maybe_field_id = 'NULL'; // Setting 'NULL' uses SQL auto-increment.
411
+ }
412
+
413
+ // Insert into the fields table.
414
+ $sql = "INSERT INTO `{$this->table}` ( `id`, label, `key`, `type`, parent_id, field_label, field_key, `order`, required, default_value, label_pos, personally_identifiable ) VALUES ( " .
415
+ $maybe_field_id . ", '" .
416
+ $this->prepare( $settings[ 'label' ] ) . "', '".
417
+ $this->prepare( $settings[ 'key' ] ) . "', '" .
418
+ $this->prepare( $settings[ 'type' ] ) . "', " .
419
+ intval( $this->form[ 'ID' ] ) . ", '" .
420
+ $this->prepare( $settings[ 'label' ] ) . "', '" .
421
+ $this->prepare( $settings[ 'key' ] ) . "', " .
422
+ intval( $settings[ 'order' ] ) . ", " .
423
+ intval( $settings[ 'required' ] ) . ", '" .
424
+ $this->prepare( $settings[ 'default_value' ] ) . "', '" .
425
+ $this->prepare( $settings[ 'label_pos' ] ) . "', " .
426
+ intval( $settings[ 'personally_identifiable' ] ) . " )";
427
+
428
+ $this->query( $sql );
429
+
430
+ // Set a default new_id for debugging.
431
+ $new_id = 0;
432
+ // If we're not in debug mode...
433
+ if ( ! $this->debug ) {
434
+ // Get the ID of the new field.
435
+ $new_id = $this->db->insert_id;
436
+ $settings[ 'old_field_id' ] = $inserting;
437
+ }
438
+ // Save a reference to this insertion.
439
+ $this->insert_ids[ $inserting ] = $new_id;
440
+
441
+ // Update our submission_updates array with the new ID of this field so that we can use it later.
442
+ if ( isset ( $this->submission_updates[ $inserting ] ) ) {
443
+ $this->submission_updates[ $inserting ] = $new_id;
444
+ }
445
+
446
+ // Push the new ID onto our list of IDs to flush.
447
+ array_push( $flush_ids, $new_id );
448
+
449
+ // For each meta of the field...
450
+ foreach ( $settings as $meta => $value ) {
451
+ // If it's not empty...
452
+ if ( ( ! empty( $value ) || '0' == $value ) ) {
453
+ // Add the data to the list.
454
+ array_push( $meta_items, "( " . intval( $new_id ) . ", '" . $meta . "', '" . $this->prepare( $value ) . "', '" . $meta . "', '" . $this->prepare( $value ) . "' )" );
455
+ }
456
+ }
457
+ // Remove the item from the list of fields.
458
+ unset( $this->fields_by_id[ $inserting ] );
459
+ $field_index = array_search( $inserting, $this->field_ids );
460
+ unset( $this->field_ids[ $field_index ] );
461
+ // Reduce the limit.
462
+ $this->limit--;
463
+ }
464
+
465
+ if ( ! empty ( $flush_ids ) ) {
466
+ // Flush our existing meta.
467
+ $sql = "DELETE FROM `{$this->meta_table}` WHERE parent_id IN(" . implode( ', ', $flush_ids ) . ")";
468
+ $this->query( $sql );
469
+ }
470
+
471
+ // Insert our meta.
472
+ $sql = "INSERT INTO `{$this->meta_table}` ( parent_id, `key`, value, meta_key, meta_value ) VALUES " . implode( ', ', $meta_items );
473
+ $this->query( $sql );
474
+ }
475
+
476
+ /**
477
+ * If we have any duplicate field ids, we need to update any existing submissions with the new field ID.
478
+ *
479
+ * The $this->submission_updates array will look like:
480
+ *
481
+ * $this->submission_updates[ original_id ] = new_id;
482
+ *
483
+ * This method:
484
+ * Checks to see if we have any fields in our $this->submission_updates array (have a changed ID)
485
+ * Makes sure that processing isn't locked
486
+ * Loops over fields in our $this->submission_updates array
487
+ * Fetches submission post meta for the specific form ID and _field_OLDID
488
+ * Uses a SQL UPDATE statement to replace _field_OLDID with _field_NEWID
489
+ *
490
+ * @since 3.4.0
491
+ * @return void
492
+ */
493
+ private function maybe_update_submissions()
494
+ {
495
+ // If we don't have any submissions to update OR the lock_process is true, bail early.
496
+ if ( empty ( $this->submission_updates ) || $this->lock_process ) {
497
+ return false;
498
+ }
499
+
500
+ /*
501
+ * Keep track of old field ids we've used.
502
+ * Initially, we set our record array to our current submission updates array.
503
+ * When we finish updating an old field, we remove it from the record array.
504
+ * When we're done with all fields, we set the submission updates array to the record array.
505
+ */
506
+ $submission_updates_record = $this->submission_updates;
507
+ // Meta key update limit; How many meta keys do we want to update at a time?
508
+ $meta_key_limit = 200;
509
+ // Loop through submission updates and query the postmeta table for any meta_key values of _field_{old_id}.
510
+ foreach ( $this->submission_updates as $old_id => $new_id ) {
511
+ // Make sure that we haven't reached our query limit.
512
+ if ( 1 > $this->limit ) {
513
+ // Lock processing.
514
+ $this->lock_process = true;
515
+ // Exit the loop.
516
+ break;
517
+ }
518
+
519
+ // This sql is designed to grab our old _field_X post meta keys so that we can replace them with new _field_X meta keys.
520
+ $sql = "SELECT
521
+ old_field_id.meta_id
522
+ FROM
523
+ `{$this->db->prefix}posts` p
524
+ INNER JOIN `{$this->db->prefix}postmeta` old_field_id ON old_field_id.post_id = p.ID
525
+ AND old_field_id.meta_key = '_field_{$old_id}'
526
+ INNER JOIN `{$this->db->prefix}postmeta` form_id ON form_id.post_id = p.ID
527
+ AND form_id.meta_key = '_form_id'
528
+
529
+ WHERE old_field_id.meta_key = '_field_{$old_id}'
530
+ AND form_id.meta_value = {$this->form[ 'ID' ]}
531
+ AND p.post_type = 'nf_sub'
532
+ LIMIT {$meta_key_limit}";
533
+ // Fetch our sql results.
534
+ $meta_ids = $this->db->get_results( $sql, 'ARRAY_N' );
535
+ if ( ! empty( $meta_ids ) ) {
536
+ // Implode our meta ids so that we can use the result in our update sql.
537
+ $imploded_ids = implode( ',', call_user_func_array( 'array_merge', $meta_ids ) );
538
+ // Update all our fetched meta ids with the new _field_ meta key.
539
+ $sql = "UPDATE `{$this->db->prefix}postmeta`
540
+ SET meta_key = '_field_{$new_id}'
541
+ WHERE meta_id IN ( {$imploded_ids} )";
542
+
543
+ $this->query( $sql );
544
+ }
545
+
546
+ /*
547
+ * Let's make sure that we're done processing all post meta for this old field ID.
548
+ *
549
+ * If the number of meta rows retrieved equals our limit:
550
+ * lock processing
551
+ * break out of this loop
552
+ * Else
553
+ * we're done with this old field, remove it from our list
554
+ * subtract from our $this->limit var
555
+ */
556
+ if ( $meta_key_limit === count( $meta_ids ) ) {
557
+ // Keep anything else from processing.
558
+ $this->lock_process = true;
559
+ // Exit this foreach loop.
560
+ break;
561
+ } else { // We're done with this old field.
562
+ // Remove the field ID from our submission array.
563
+ unset( $submission_updates_record[ $old_id ] );
564
+ // Decrement our query limit.
565
+ $this->limit--;
566
+ }
567
+
568
+ } // End foreach
569
+ // Set our submission updates array to our record array so that we remove any completed old ids.
570
+ $this->submission_updates = $submission_updates_record;
571
+ }
572
+
573
+ /**
574
+ * If we still have field_ids in our class var, then we need to update the field table.
575
+ *
576
+ * If lock_process is true or we have no field_ids, we bail early.
577
+ *
578
+ * @since 3.4.0
579
+ * @return void
580
+ */
581
+ private function maybe_update_fields()
582
+ {
583
+ // If we have no fields to insert OR lock_process is true, bail early.
584
+ if ( empty ( $this->field_ids ) || $this->lock_process ) {
585
+ return false;
586
+ }
587
+
588
+ // Store the meta items outside the loop for faster insertion.
589
+ $meta_items = array();
590
+ $flush_ids = array();
591
+ // While we still have items to update...
592
+ while ( 0 < count( $this->field_ids ) ) {
593
+ // If we have hit our limit...
594
+ if ( 1 > $this->limit ) {
595
+ // Lock processing.
596
+ $this->lock_process = true;
597
+ // Exit the loop.
598
+ break;
599
+ }
600
+ // Get our item to be updated.
601
+ $updating = array_pop( $this->field_ids );
602
+ array_push( $flush_ids, $updating );
603
+ $settings = $this->fields_by_id[ $updating ];
604
+ // Update the fields table.
605
+ $sql = "UPDATE `{$this->table}` SET label = '"
606
+ . $this->prepare( $settings[ 'label' ] )
607
+ . "', `key` = '" . $this->prepare( $settings[ 'key' ] )
608
+ . "', `type` = '" . $this->prepare( $settings[ 'type' ] )
609
+ . "', field_label = '" . $this->prepare( $settings[ 'label' ] )
610
+ . "', field_key = '" . $this->prepare( $settings[ 'key' ] )
611
+ . "', `order` = " . intval( $settings[ 'order' ] )
612
+ . ", required = " . intval( $settings[ 'required' ] )
613
+ . ", default_value = '" . $this->prepare( $settings[ 'default_value' ] )
614
+ . "', label_pos = '" . $this->prepare( $settings[ 'label_pos' ] )
615
+ . "', personally_identifiable = " . intval( $settings[ 'personally_identifiable' ] )
616
+ . " WHERE id = " . intval( $updating );
617
+ $this->query( $sql );
618
+ // For each meta of the field...
619
+ foreach ( $settings as $meta => $value ) {
620
+ // If it's not empty...
621
+ if ( ( ! empty( $value ) || '0' == $value ) ) {
622
+ // Add the data to the list.
623
+ array_push( $meta_items, "( " . intval( $updating ) . ", '" . $meta . "', '" . $this->prepare( $value ) . "', '" . $meta . "', '" . $this->prepare( $value ) . "' )" );
624
+ }
625
+ }
626
+ // Remove the item from the list of fields.
627
+ unset( $this->fields_by_id[ $updating ] );
628
+ // Reduce the limit.
629
+ $this->limit--;
630
+ }
631
+ if ( ! empty ( $flush_ids ) ) {
632
+ // Flush our existing meta.
633
+ $sql = "DELETE FROM `{$this->meta_table}` WHERE parent_id IN(" . implode( ', ', $flush_ids ) . ")";
634
+ $this->query( $sql );
635
+ }
636
+
637
+ // Insert our updated meta.
638
+ $sql = "INSERT INTO `{$this->meta_table}` ( parent_id, `key`, value, meta_key, meta_value ) VALUES " . implode( ', ', $meta_items );
639
+ $this->query( $sql );
640
+ }
641
+
642
+ /**
643
+ * If we've inserted any fields that have changed ids, we want to update those ids in our cache.
644
+ * This method grabs the cache, updates any field ids, then updates the cache.
645
+ *
646
+ * @since 3.4.0
647
+ * @return void
648
+ */
649
+ private function update_form_cache()
650
+ {
651
+ // Get a copy of the cache.
652
+ $cache = WPN_Helper::get_nf_cache($this->form[ 'ID' ] );
653
+ // For each field in the cache...
654
+ foreach( $cache[ 'fields' ] as &$field ) {
655
+ // If we have a new ID for this field...
656
+ if ( isset( $this->insert_ids[ $field[ 'id' ] ] ) ) {
657
+ // Update it.
658
+ $field[ 'id' ] = intval( $this->insert_ids[ $field[ 'id' ] ] );
659
+ }
660
+ // TODO: Might also need to append some new settings here (Label)?
661
+ }
662
+ // Save the cache, passing 3 as the current stage.
663
+ WPN_Helper::update_nf_cache( $this->form[ 'ID' ], $cache, 3 );
664
+ }
665
+
666
+ /**
667
+ * After we've done our processing, but before we get to step cleanup, we need to store process information.
668
+ *
669
+ * This method updates our form class var so that it can be passed to the next step.
670
+ * If we've completed this step, it calls the cleanup method.
671
+ *
672
+ * @since 3.4.0
673
+ * @return void
674
+ */
675
+ private function end_of_step()
676
+ {
677
+ // If we have locked processing...
678
+ if ( $this->lock_process ) {
679
+ // If we're continuing a process...
680
+ if ( isset( $this->form[ 'field_ids' ] ) ) {
681
+ // Reset the field_ids array.
682
+ $this->field_ids = array();
683
+ // For each field left to process...
684
+ foreach ( $this->fields_by_id as $id => $field ) {
685
+ // If we've not already processed this field...
686
+ if ( in_array( $id, $this->form[ 'field_ids' ] ) ) {
687
+ // Save a reference to its ID.
688
+ array_push( $this->field_ids, $id );
689
+ }
690
+ }
691
+ }
692
+ // Store our current data location.
693
+ $this->form[ 'insert' ] = $this->insert;
694
+ $this->form[ 'field_ids' ] = $this->field_ids;
695
+ $this->form[ 'submission_updates' ] = $this->submission_updates;
696
+ array_push( $this->running[ 0 ][ 'forms' ], $this->form );
697
+ } else { // Otherwise... (The step is complete.)
698
+ // Increment our step count.
699
+ $this->running[ 0 ][ 'current' ] = intval( $this->running[ 0 ][ 'current' ] ) + 1;
700
+ // Disable maintenance mode on the front end of the site.
701
+ $this->disable_maintenance_mode( $this->db->prefix, $this->form[ 'ID' ] );
702
+ }
703
+
704
+ // Prepare to output our number of steps and current step.
705
+ $this->response[ 'stepsTotal' ] = $this->running[ 0 ][ 'steps' ];
706
+ $this->response[ 'currentStep' ] = $this->running[ 0 ][ 'current' ];
707
+
708
+ // If all steps have been completed...
709
+ if ( empty( $this->running[ 0 ][ 'forms' ] ) ) {
710
+ // Run our cleanup method.
711
+ $this->cleanup();
712
+ }
713
+
714
+ // Record our current location in the process.
715
+ update_option( 'ninja_forms_doing_required_updates', $this->running );
716
+ // Prepare to output the number of updates remaining.
717
+ $this->response[ 'updatesRemaining' ] = count( $this->running );
718
+ }
719
+
720
+ }
includes/Updates/CacheCollateForms.php ADDED
@@ -0,0 +1,243 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit;
2
+
3
+ /**
4
+ * Class NF_Updates_CacheCollateForms
5
+ *
6
+ * This class manages the step process of running through the CacheCollateForms required update.
7
+ * It will define an object to pull data from (if necessary) to pick back up if exited early.
8
+ * It will run an upgrade function to alter the nf3_forms and nf3_form_meta tables.
9
+ * Then, it will step over each form on the site, following this process:
10
+ * - New columns in the nf3_forms table will be populated with data from the cache.
11
+ * - New and existing columns in the nf3_form_meta tables will be populated from the cache.
12
+ * - A new record of the cache will be saved to the nf3_upgrades table (if it does not already exist).
13
+ * After completing the above for every form on the site, it will remove the data object that manages its location.
14
+ */
15
+ class NF_Updates_CacheCollateForms extends NF_Abstracts_RequiredUpdate
16
+ {
17
+
18
+ private $data = array();
19
+
20
+ private $running = array();
21
+
22
+ /**
23
+ * Stores information about the current form being processed.
24
+ * @var array
25
+ */
26
+ private $form;
27
+
28
+ /**
29
+ * Declare a blacklist for settings to not be inserted.
30
+ * @var array
31
+ */
32
+ private $blacklist = array(
33
+ 'title',
34
+ 'objectType',
35
+ 'editActive',
36
+ );
37
+
38
+ /**
39
+ * The table names for our database queries.
40
+ */
41
+ private $table;
42
+ private $meta_table;
43
+
44
+ /**
45
+ * Constructor
46
+ *
47
+ * @param $data (Array) The data object passed in by the AJAX call.
48
+ * @param $running (Array) The array of required updates being run.
49
+ *
50
+ * @since 3.4.0
51
+ */
52
+ public function __construct( $data = array(), $running )
53
+ {
54
+ // Build our arguments array.
55
+ $args = array(
56
+ 'slug' => 'CacheCollateForms',
57
+ 'class_name' => 'NF_Updates_CacheCollateForms',
58
+ 'debug' => false,
59
+ );
60
+ $this->data = $data;
61
+ $this->running = $running;
62
+
63
+ // Call the parent constructor.
64
+ parent::__construct( $args );
65
+
66
+ // Set our table names.
67
+ $this->table = $this->db->prefix . 'nf3_forms';
68
+ $this->meta_table = $this->db->prefix . 'nf3_form_meta';
69
+
70
+ // Begin processing.
71
+ $this->process();
72
+ }
73
+
74
+ /**
75
+ * Function to loop over the batch.
76
+ *
77
+ * @since 3.4.0
78
+ */
79
+ public function process()
80
+ {
81
+ // If we've not already started...
82
+ if ( ! isset( $this->running[ 0 ][ 'running' ] ) ) {
83
+ // Run our startup method.
84
+ $this->startup();
85
+ }
86
+
87
+ // See which form we're currently working with.
88
+ $this->form = array_pop( $this->running[ 0 ][ 'forms' ] );
89
+
90
+ /**
91
+ * If we don't have a cache for this form, we need to add one for later processing.
92
+ * We're going to check that the cache exists and create one if we don't have one.
93
+ */
94
+ $cache = WPN_Helper::get_nf_cache( $this->form[ 'ID' ] );
95
+
96
+ if ( empty ( $cache ) ) { // No cache exists.
97
+ // Call our build_nf_cache static method.
98
+ $form_cache = WPN_Helper::build_nf_cache( $this->form[ 'ID' ] );
99
+ }
100
+
101
+ /**
102
+ * Update our form table with the appropriate form settings.
103
+ */
104
+ $this->update_form();
105
+
106
+ /**
107
+ * Check to see if we're done with processing this form and prepare to respond.
108
+ */
109
+ $this->end_of_step();
110
+
111
+ // Respond to the AJAX call.
112
+ $this->respond();
113
+ }
114
+
115
+ /**
116
+ * Function to run any setup steps necessary to begin processing.
117
+ *
118
+ * @since 3.4.0
119
+ */
120
+ public function startup()
121
+ {
122
+ // Record that we're processing the update.
123
+ $this->running[ 0 ][ 'running' ] = true;
124
+ // If we're not debugging...
125
+ if ( ! $this->debug ) {
126
+ // Ensure that our data tables are updated.
127
+ $this->migrate( 'cache_collate_forms' );
128
+ }
129
+ // Get a list of our forms...
130
+ $sql = "SELECT ID FROM `{$this->table}`";
131
+ $forms = $this->db->get_results( $sql, 'ARRAY_A' );
132
+ $this->running[ 0 ][ 'forms' ] = $forms;
133
+ // Record the total number of steps in this batch.
134
+ $this->running[ 0 ][ 'steps' ] = count( $forms );
135
+ // Record our current step (defaulted to 0 here).
136
+ $this->running[ 0 ][ 'current' ] = 0;
137
+ }
138
+
139
+ /**
140
+ * Function to cleanup any lingering temporary elements of a required update after completion.
141
+ *
142
+ * @since 3.4.0
143
+ */
144
+ public function cleanup()
145
+ {
146
+ // Remove the current process from the array.
147
+ array_shift( $this->running );
148
+ // Record to our updates setting that this update is complete.
149
+ $this->confirm_complete();
150
+ // If we have no updates left to process...
151
+ if ( empty( $this->running ) ) {
152
+ // Call the parent cleanup method.
153
+ parent::cleanup();
154
+ }
155
+ }
156
+
157
+ /**
158
+ *
159
+ * @since 3.4.0
160
+ * @return void
161
+ */
162
+ private function end_of_step()
163
+ {
164
+ // Update the upgrades table, passing in 1 for the current stage.
165
+ $cache = WPN_Helper::get_nf_cache( $this->form[ 'ID' ] );
166
+ WPN_Helper::update_nf_cache( $this->form[ 'ID' ], $cache, 1 );
167
+
168
+ // Increment our step count.
169
+ $this->running[ 0 ][ 'current' ] = intval( $this->running[ 0 ][ 'current' ] ) + 1;
170
+
171
+ // Prepare to output our number of steps and current step.
172
+ $this->response[ 'stepsTotal' ] = $this->running[ 0 ][ 'steps' ];
173
+ $this->response[ 'currentStep' ] = $this->running[ 0 ][ 'current' ];
174
+
175
+ // If all steps have been completed...
176
+ if ( empty( $this->running[ 0 ][ 'forms' ] ) ) {
177
+ // Run our cleanup method.
178
+ $this->cleanup();
179
+ }
180
+
181
+ // Record our current location in the process.
182
+ update_option( 'ninja_forms_doing_required_updates', $this->running );
183
+ // Prepare to output the number of updates remaining.
184
+ $this->response[ 'updatesRemaining' ] = count( $this->running );
185
+ }
186
+
187
+ /**
188
+ * Update our form table for the current form.
189
+ * We have new table columns, so we want to make sure that those are populated properly.
190
+ *
191
+ * Also checks meta values against our $this->blacklist.
192
+ *
193
+ * @since 3.4.0
194
+ * @return [type] [description]
195
+ */
196
+ private function update_form()
197
+ {
198
+ // Get the settings for that form.
199
+ $settings = Ninja_Forms()->form( $this->form[ 'ID' ] )->get()->get_settings();
200
+
201
+ // Get our seq_number from meta.
202
+ $sql = "SELECT `value` FROM `{$this->meta_table}` WHERE `key` = '_seq_num' AND `parent_id` = " . intval( $this->form[ 'ID' ] );
203
+ $result = $this->db->query( $sql, 'ARRAY_A' );
204
+ // Default to 1.
205
+ $seq_num = 1;
206
+ if ( ! empty( $result[ 0 ][ 'value' ] ) ) {
207
+ // If we got back something, set it to the proper value.
208
+ $seq_num = intval( $result[ 0 ][ 'value' ] );
209
+ }
210
+
211
+ // If logged in is false...
212
+ if ( ! $settings[ 'logged_in' ] || 'false' === $settings[ 'logged_in' ] ) {
213
+ $logged_in = 0;
214
+ } // Otherwise... (logged in is true.)
215
+ else {
216
+ $logged_in = 1;
217
+ }
218
+
219
+ // Save the new columns to the forms table.
220
+ $sql = "UPDATE `{$this->table}` SET form_title = '" . $this->prepare( $settings[ 'title' ] ) . "', default_label_pos = '" . $settings[ 'default_label_pos' ] . "', show_title = " . intval( $settings[ 'show_title' ] ) . ", clear_complete = " . intval( $settings[ 'clear_complete' ] ) . ", hide_complete = " . intval( $settings[ 'hide_complete' ] ) . ", logged_in = {$logged_in}, seq_num = {$seq_num} WHERE id = " . intval( $this->form[ 'ID' ] ) . ";";
221
+ $this->query( $sql );
222
+
223
+ // Remove the existing meta from the form_meta table.
224
+ $sql = "DELETE FROM `{$this->meta_table}` WHERE parent_id = " . intval( $this->form[ 'ID' ] );
225
+ $this->query( $sql );
226
+
227
+ $insert_items = array();
228
+ // Add _seq_num since it's protected and won't be a setting.
229
+ array_push( $insert_items, "( " . intval( $this->form[ 'ID' ] ) . ", '_seq_num', '{$seq_num}', '_seq_num', '{$seq_num}' )" );
230
+ // For each form setting...
231
+ foreach ( $settings as $key => $setting ) {
232
+ // If it's not a restricted setting...
233
+ if ( ! in_array( $key, $this->blacklist ) ) {
234
+ // Add it to the stack.
235
+ array_push( $insert_items, "( " . intval( $this->form[ 'ID' ] ) . ", '{$key}', '" . $this->prepare( $setting ) . "', '{$key}', '" . $this->prepare( $setting ) . "' )" );
236
+ }
237
+ }
238
+ // Insert the new meta values.
239
+ $sql = "INSERT INTO `{$this->meta_table}` ( parent_id, `key`, `value`, meta_key, meta_value ) VALUES " . implode( ', ', $insert_items );
240
+ $this->query( $sql );
241
+ }
242
+
243
+ }
includes/Updates/CacheCollateObjects.php ADDED
@@ -0,0 +1,298 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ( ! defined( 'ABSPATH' ) ) exit;
2
+
3
+ /**
4
+ * Class NF_Updates_CacheCollateForms
5
+ *
6
+ * This class manages the step process of running through the CacheCollateObjects required update.
7
+ * It will define an object to pull data from (if necessary) to pick back up if exited early.
8
+ * It will run an upgrade function to alter the nf3_objects and nf3_object_meta tables.
9
+ * Then, it will step over each object in the db, following this process:
10
+ * - Append the object_title
11
+ * Then, it will step over each object_meta in the db, following this process:
12
+ * - Copy over the meta_key
13
+ * - Append the meta_value
14
+ * After completing the above for every form on the site, it will remove the data object that manages its location.
15
+ */
16
+ class NF_Updates_CacheCollateObjects extends NF_Abstracts_RequiredUpdate
17
+ {
18
+
19
+ private $data = array();
20
+
21
+ private $running = array();
22
+
23
+ /**
24
+ * The table names for our database queries.
25
+ */
26
+ private $table;
27
+ private $meta_table;
28
+
29
+ /**
30
+ * The row counts of our database tables.
31
+ */
32
+ private $table_rows = 0;
33
+ private $meta_rows = 0;
34
+
35
+ /**
36
+ * The denominator object for calculating our steps.
37
+ * @var Integer
38
+ */
39
+ private $divisor = 500;
40
+
41
+ /**
42
+ * Constructor
43
+ *
44
+ * @param $data (Array) The data object passed in by the AJAX call.
45
+ * @param $running (Array) The array of required updates being run.
46
+ *
47
+ * @since 3.4.0
48
+ */
49
+ public function __construct( $data = array(), $running )
50
+ {
51
+ // Build our arguments array.
52
+ $args = array(
53
+ 'slug' => 'CacheCollateObjects',
54
+ 'class_name' => 'NF_Updates_CacheCollateObjects',
55
+ 'debug' => false,
56
+ );
57
+ $this->data = $data;
58
+ $this->running = $running;
59
+
60
+ // Call the parent constructor.
61
+ parent::__construct( $args );
62
+
63
+ // Set our table names.
64
+ $this->table = $this->db->prefix . 'nf3_objects';
65
+ $this->meta_table = $this->db->prefix . 'nf3_object_meta';
66
+
67
+ // Begin processing.
68
+ $this->process();
69
+ }
70
+
71
+ /**
72
+ * Function to loop over the batch.
73
+ *
74
+ * @since 3.4.0
75
+ */
76
+ public function process()
77
+ {
78
+ // If we've not already started...
79
+ if ( ! isset( $this->running[ 0 ][ 'running' ] ) ) {
80
+ // Run our startup method.
81
+ $this->startup();
82
+ } // Otherwise... (We're picking up an old process.)
83
+ else {
84
+ // Get our number of remaining table rows.
85
+ if ( isset( $this->running[ 0 ][ 'table_rows' ] ) ) {
86
+ $this->table_rows = intval( $this->running[ 0 ][ 'table_rows' ] );
87
+ }
88
+ // Get our number of remaining meta rows.
89
+ if ( isset( $this->running[ 0 ][ 'meta_rows' ] ) ) {
90
+ $this->meta_rows = intval( $this->running[ 0 ][ 'meta_rows' ] );
91
+ }
92
+ }
93
+
94
+ // Update values in the objects table.
95
+ $this->maybe_update_objects();
96
+
97
+ // Update values in the object_meta table.
98
+ $this->maybe_update_object_meta();
99
+
100
+ // Increment our step count.
101
+ $this->running[ 0 ][ 'current' ] += 1;
102
+ // Prepare to output our number of steps and current step.
103
+ $this->response[ 'stepsTotal' ] = $this->running[ 0 ][ 'steps' ];
104
+ $this->response[ 'currentStep' ] = $this->running[ 0 ][ 'current' ];
105
+ $this->running[ 0 ][ 'table_rows' ] = $this->table_rows;
106
+ $this->running[ 0 ][ 'meta_rows' ] = $this->meta_rows;
107
+ // If we have no meta left to update at this point...
108
+ if ( 0 >= $this->table_rows && 0 >= $this->meta_rows ) {
109
+ // Run our cleanup process.
110
+ $this->cleanup();
111
+ }
112
+ // Prepare to output the number of updates remaining.
113
+ $this->response[ 'updatesRemaining' ] = count( $this->running );
114
+
115
+ // Record our current location in the process.
116
+ update_option( 'ninja_forms_doing_required_updates', $this->running );
117
+
118
+ // Respond to the AJAX call.
119
+ $this->respond();
120
+ }
121
+
122
+ /**
123
+ * Function to run any setup steps necessary to begin processing.
124
+ *
125
+ * @since 3.4.0
126
+ */
127
+ public function startup()
128
+ {
129
+ // Record that we're processing the update.
130
+ $this->running[ 0 ][ 'running' ] = true;
131
+ // If we're not debugging...
132
+ if ( ! $this->debug ) {
133
+ // Ensure that our data tables are updated.
134
+ $this->migrate( 'cache_collate_objects' );
135
+ }
136
+ // Get the number of rows in the objects table.
137
+ $sql = "SELECT COUNT( `id` ) as Total FROM `{$this->table}`";
138
+ $result = $this->db->get_results( $sql, 'ARRAY_A' );
139
+ // If we got something back...
140
+ if ( ! empty( $result ) ) {
141
+ // Record the total.
142
+ $this->table_rows = intval( $result[ 0 ][ 'Total' ] );
143
+ }
144
+ // If the table was empty...
145
+ if ( 0 == $this->table_rows ) {
146
+ /**
147
+ * Clean out the object_meta table.
148
+ * It should contain nothing if there is nothing in the objects table.
149
+ */
150
+ $sql = "DELETE FROM `{$this->meta_table}`";
151
+ $this->query( $sql );
152
+ // Lock processing.
153
+ $this->lock_process = true;
154
+ }
155
+ // If processing is locked...
156
+ if ( $this->lock_process ) {
157
+ // Prepare to output our number of steps and current step.
158
+ $this->response[ 'stepsTotal' ] = 1;
159
+ $this->response[ 'currentStep' ] = 1;
160
+ // Skip straight to our cleanup method.
161
+ $this->cleanup();
162
+ // Prepare to output the number of updates remaining.
163
+ $this->response[ 'updatesRemaining' ] = count( $this->running );
164
+ // Record our current location in the process.
165
+ update_option( 'ninja_forms_doing_required_updates', $this->running );
166
+ $this->respond();
167
+ }
168
+ // Get the number of rows in the object_meta table.
169
+ $sql = "SELECT COUNT( `id` ) as Total FROM `{$this->meta_table}`";
170
+ $result = $this->db->get_results( $sql, 'ARRAY_A' );
171
+ // If we got something back...
172
+ if ( ! empty( $result ) ) {
173
+ // Record the total.
174
+ $this->meta_rows = intval( $result[ 0 ][ 'Total' ] );
175
+ }
176
+ $steps = ceil( $this->table_rows / $this->divisor );
177
+ $steps += ceil( $this->meta_rows / $this->divisor );
178
+
179
+ // Record the total number of steps in this batch.
180
+ $this->running[ 0 ][ 'steps' ] = $steps;
181
+ // Record our current step (defaulted to 0 here).
182
+ $this->running[ 0 ][ 'current' ] = 0;
183
+ }
184
+
185
+ /**
186
+ * Function to cleanup any lingering temporary elements of a required update after completion.
187
+ *
188
+ * @since 3.4.0
189
+ */
190
+ public function cleanup()
191
+ {
192
+ // Remove the current process from the array.
193
+ array_shift( $this->running );
194
+ // Record to our updates setting that this update is complete.
195
+ $this->confirm_complete();
196
+ // If we have no updates left to process...
197
+ if ( empty( $this->running ) ) {
198
+ // Call the parent cleanup method.
199
+ parent::cleanup();
200
+ }
201
+ }
202
+
203
+ /**
204
+ * Function to manage the updating of our objects table.
205
+ *
206
+ * @return Void
207
+ *
208
+ * @since 3.4.0
209
+ */
210
+ private function maybe_update_objects()
211
+ {
212
+ // If we have no table rows left to process, exit early.
213
+ if ( 0 >= $this->table_rows ) return false;
214
+ // Compile our query.
215
+ $sql = "SELECT `id`, `title` FROM `{$this->table}` ";
216
+ // If we are picking up an old process...
217
+ if ( isset( $this->running[ 0 ][ 'last_updated' ] ) ) {
218
+ // Make sure we don't fetch old values.
219
+ $sql .= "WHERE `id` > " . intval( $this->running[ 0 ][ 'last_updated' ] ) . " ";
220
+ }
221
+ // Ensure that we gate the number of records that we fetch.
222
+ $sql .= "ORDER BY `id` ASC LIMIT {$this->divisor}";
223
+ $result = $this->db->get_results( $sql, 'ARRAY_A' );
224
+ // Build our Update query.
225
+ $sub_sql = array();
226
+ foreach ( $result as $object ) {
227
+ array_push( $sub_sql, "WHEN `id` = " . intval( $object[ 'id' ] ) . " THEN '" . $this->prepare( $object[ 'title' ] ) . "'" );
228
+ }
229
+ // If we have values to update...
230
+ if ( ! empty( $sub_sql ) ) {
231
+ // Run the update.
232
+ $sql = "UPDATE `{$this->table}` SET `object_title` = CASE " . implode( ' ', $sub_sql ) . " ELSE `object_title` END;";
233
+ $this->query( $sql );
234
+ // Get the last item updated.
235
+ $last = array_pop( $result );
236
+ // Record it to our process object.
237
+ $this->running[ 0 ][ 'last_updated' ] = $result[ 'id' ];
238
+ // Also record that we ran the update on this table.
239
+ $this->running[ 0 ][ 'updating_table' ] = $this->table;
240
+ // Reduce our table rows by the divisor.
241
+ $this->table_rows = $this->table_rows - $this->divisor;
242
+ // Lock processing.
243
+ $this->lock_process = true;
244
+ }
245
+ }
246
+
247
+ /**
248
+ * Function to manage the updating of our object_meta table.
249
+ *
250
+ * @return Void
251
+ *
252
+ * @since 3.4.0
253
+ */
254
+ private function maybe_update_object_meta()
255
+ {
256
+ // If we've locked processing, exit early.
257
+ if ( $this->lock_process ) return false;
258
+ // If we have no meta rows left to process, exit early.
259
+ if ( 0 >= $this->meta_rows ) return false;
260
+ // Compile our query.
261
+ $sql = "SELECT `id`, `key`, `value` FROM `{$this->meta_table}` ";
262
+ // If we are picking up an old process...
263
+ if ( $this->meta_table == $this->running[ 0 ][ 'updating_table' ] ) {
264
+ // Make sure we don't fetch old values.
265
+ $sql .= "WHERE `id` > " . intval( $this->running[ 0 ][ 'last_updated' ] ) . " ";
266
+ }
267
+ // Ensure that we gate the number of records that we fetch.
268
+ $sql .= "ORDER BY `id` ASC LIMIT {$this->divisor}";
269
+ $result = $this->db->get_results( $sql, 'ARRAY_A' );
270
+ // Build our Update query.
271
+ $sub_sql = array();
272
+ $meta_ids = array();
273
+ foreach ( $result as $object ) {
274
+ array_push( $sub_sql, "WHEN `id` = " . intval( $object[ 'id' ] ) . " THEN '" . $this->prepare( $object[ 'value' ] ) . "'" );
275
+ array_push( $meta_ids, $object[ 'id' ] );
276
+ }
277
+ // If we have values to update...
278
+ if ( ! empty( $sub_sql ) ) {
279
+ // Run the update on the meta_key column.
280
+ $sql = "UPDATE `{$this->meta_table}` SET `meta_key` = `key` WHERE `id` IN(" . implode( ', ', $meta_ids ) . ");";
281
+ $this->query( $sql );
282
+ // Run the update on the meta_value column.
283
+ $sql = "UPDATE `{$this->meta_table}` SET `meta_value` = CASE " . implode( ' ', $sub_sql ) . " ELSE `meta_value` END;";
284
+ $this->query( $sql );
285
+ // Get the last item updated.
286
+ $last = array_pop( $result );
287
+ // Record it to our process object.
288
+ $this->running[ 0 ][ 'last_updated' ] = $result[ 'id' ];
289
+ // Also record that we ran the update on this table.
290
+ $this->running[ 0 ][ 'updating_table' ] = $this->meta_table;
291
+ // Reduce our meta rows by the divisor.
292
+ $this->meta_rows -= $this->divisor;
293
+ // Lock processing.
294
+ $this->lock_process = true;
295
+ }
296
+ }
297
+
298
+ }
lib/NF_Upgrade.php CHANGED
@@ -12,6 +12,7 @@ function ninja_forms_ajax_migrate_database(){
12
  $nuke_multisite = false;
13
  $migrations->nuke( $sure, $really_sure, $nuke_multisite );
14
  $migrations->migrate();
 
15
  echo json_encode( array( 'migrate' => 'true' ) );
16
  wp_die();
17
  }
12
  $nuke_multisite = false;
13
  $migrations->nuke( $sure, $really_sure, $nuke_multisite );
14
  $migrations->migrate();
15
+ delete_option( 'ninja_forms_required_updates' );
16
  echo json_encode( array( 'migrate' => 'true' ) );
17
  wp_die();
18
  }
ninja-forms.php CHANGED
@@ -3,7 +3,7 @@
3
  Plugin Name: Ninja Forms
4
  Plugin URI: http://ninjaforms.com/
5
  Description: Ninja Forms is a webform builder with unparalleled ease of use and features.
6
- Version: 3.3.21.3
7
  Author: The WP Ninjas
8
  Author URI: http://ninjaforms.com
9
  Text Domain: ninja-forms
@@ -57,7 +57,13 @@ if( get_option( 'ninja_forms_load_deprecated', FALSE ) && ! ( isset( $_POST[ 'nf
57
  /**
58
  * @since 3.0
59
  */
60
- const VERSION = '3.3.21.3';
 
 
 
 
 
 
61
 
62
  const WP_MIN_VERSION = '4.8';
63
 
@@ -74,6 +80,16 @@ if( get_option( 'ninja_forms_load_deprecated', FALSE ) && ! ( isset( $_POST[ 'nf
74
  * @var string $dir
75
  */
76
  public static $dir = '';
 
 
 
 
 
 
 
 
 
 
77
 
78
  /**
79
  * Plugin URL
@@ -204,11 +220,6 @@ if( get_option( 'ninja_forms_load_deprecated', FALSE ) && ! ( isset( $_POST[ 'nf
204
  }
205
 
206
  $saved_version = get_option( 'ninja_forms_version' );
207
- // If this is a fresh install... (The version has never been saved.)
208
- if ( ! $saved_version ) {
209
- // Assume we have clean data.
210
- update_option( 'ninja_forms_data_is_clean', 'true' );
211
- }
212
  // If we have a recorded version...
213
  // AND that version is less than our current version...
214
  if ( $saved_version && version_compare( $saved_version, self::VERSION, '<' ) ) {
@@ -230,12 +241,28 @@ if( get_option( 'ninja_forms_load_deprecated', FALSE ) && ! ( isset( $_POST[ 'nf
230
  if ( $saved_version && version_compare( $saved_version, '3.3.0', '<' ) ) {
231
  // Set it to the baseline (1.0) so that our upgrade process will run properly.
232
  add_option( 'ninja_forms_db_version', '1.0', '', 'no' );
233
- }
 
 
 
 
234
  else {
235
- // Set it to 1.1.
236
- add_option( 'ninja_forms_db_version', '1.1', '', 'no' );
 
 
 
 
 
 
 
 
 
 
237
  }
238
  }
 
 
239
 
240
  /*
241
  * Register our autoloader
@@ -263,6 +290,7 @@ if( get_option( 'ninja_forms_load_deprecated', FALSE ) && ! ( isset( $_POST[ 'nf
263
  self::$instance->controllers[ 'form' ] = new NF_AJAX_Controllers_Form();
264
  self::$instance->controllers[ 'fields' ] = new NF_AJAX_Controllers_Fields();
265
  self::$instance->controllers[ 'batch_process' ] = new NF_AJAX_REST_BatchProcess();
 
266
  self::$instance->controllers[ 'preview' ] = new NF_AJAX_Controllers_Preview();
267
  self::$instance->controllers[ 'submission' ] = new NF_AJAX_Controllers_Submission();
268
  self::$instance->controllers[ 'savedfields' ] = new NF_AJAX_Controllers_SavedFields();
@@ -414,27 +442,6 @@ if( get_option( 'ninja_forms_load_deprecated', FALSE ) && ! ( isset( $_POST[ 'nf
414
  // Ensure all of our tables have been defined.
415
  $migrations = new NF_Database_Migrations();
416
  $migrations->migrate();
417
- // If our db version is below 1.1...
418
- if ( version_compare( get_option( 'ninja_forms_db_version' ), '1.1', '<' ) ) {
419
- // Do our stage 1 updates.
420
- $migrations->do_stage_one();
421
- // Update our db version.
422
- update_option( 'ninja_forms_db_version', '1.1' );
423
- }
424
- // Fix for legacy versions that upgraded without a set DB version.
425
- // If our version is exactly 1.1...
426
- if ( version_compare( get_option( 'ninja_forms_db_version' ), '1.1', '==' ) ) {
427
- global $wpdb;
428
- // Fetch the form_title column from the fields table.
429
- $sql = "SHOW FULL COLUMNS FROM `{$wpdb->prefix}nf3_forms` WHERE Field = 'form_title'";
430
- $result = $wpdb->get_results( $sql, 'ARRAY_A' );
431
- // If we didn't get a result...
432
- if ( empty( $result ) ) {
433
- // Do our stage 1 updates, even though they should have already run.
434
- $migrations->do_stage_one();
435
- }
436
- }
437
-
438
  }
439
  }
440
 
@@ -472,11 +479,30 @@ if( get_option( 'ninja_forms_load_deprecated', FALSE ) && ! ( isset( $_POST[ 'nf
472
  }
473
 
474
  add_filter( 'ninja_forms_dashboard_menu_items', array( $this, 'maybe_hide_dashboard_items' ) );
 
 
 
 
475
 
476
- // If we don't have clean data...
477
- if ( ! get_option( 'ninja_forms_data_is_clean' ) ) {
478
- // Register a new notice.
479
- add_filter( 'ninja_forms_admin_notices', array( $this, 'data_cleanup_notice' ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
480
  }
481
  }
482
 
@@ -553,6 +579,14 @@ if( get_option( 'ninja_forms_load_deprecated', FALSE ) && ! ( isset( $_POST[ 'nf
553
  $items[ 'services' ]
554
  );
555
  }
 
 
 
 
 
 
 
 
556
  return $items;
557
  }
558
 
@@ -866,8 +900,9 @@ if( get_option( 'ninja_forms_load_deprecated', FALSE ) && ! ( isset( $_POST[ 'nf
866
 
867
  if( Ninja_Forms()->form()->get_forms() ) return;
868
 
869
- // Assume we're on a clean installation.
870
- update_option( 'ninja_forms_data_is_clean', 'true' );
 
871
  $form = Ninja_Forms::template( 'formtemplate-contactform.nff', array(), TRUE );
872
  Ninja_Forms()->form()->import_form( $form );
873
  }
@@ -901,24 +936,101 @@ if( get_option( 'ninja_forms_load_deprecated', FALSE ) && ! ( isset( $_POST[ 'nf
901
  }
902
  }
903
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
904
 
905
  /**
906
- * Function to register an admin notice if we detect that this installation has "unclean" Ninja Forms data.
907
- *
908
- * @since 3.3.1
909
- *
910
- * @param $notices (Array) Our array of admin notices.
911
- * @return $notices (Array) Our array of admin notices.
 
912
  */
913
- public function data_cleanup_notice( $notices ) {
914
- $notices[ 'data_cleanup' ] = array(
915
- 'title' => __( 'Data Cleanup', 'ninja-forms' ),
916
- 'msg' => sprintf( __( 'Ninja Forms has detected data on your site leftover from old forms or Ninja Forms versions.%sWe would like to run a quick cleanup process to remove this old data. Your forms will not be impacted by this process, but it may take several minutes to complete.%sPlease %sclick here%s to begin.', 'ninja-forms' ), '<br />', '<br /><br />', '<a href="' . admin_url( 'admin.php?page=ninja-forms&action=cleanup' ) . '">', '</a>' ),
917
- 'int' => 0,
918
- 'ignore_spam' => true,
919
- 'dismiss' => 0
920
- );
921
- return $notices;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
922
  }
923
 
924
  } // End Class Ninja_Forms
3
  Plugin Name: Ninja Forms
4
  Plugin URI: http://ninjaforms.com/
5
  Description: Ninja Forms is a webform builder with unparalleled ease of use and features.
6
+ Version: 3.4.0
7
  Author: The WP Ninjas
8
  Author URI: http://ninjaforms.com
9
  Text Domain: ninja-forms
57
  /**
58
  * @since 3.0
59
  */
60
+
61
+ const VERSION = '3.4.0';
62
+
63
+ /**
64
+ * @since 3.4.0
65
+ */
66
+ const DB_VERSION = '1.4';
67
 
68
  const WP_MIN_VERSION = '4.8';
69
 
80
  * @var string $dir
81
  */
82
  public static $dir = '';
83
+
84
+ /**
85
+ * Plugin Database Version
86
+ *
87
+ * This may be overwritten at a later point in this file.
88
+ *
89
+ * @since 3.4.0
90
+ * @var string $db_version
91
+ */
92
+ public static $db_version = self::DB_VERSION;
93
 
94
  /**
95
  * Plugin URL
220
  }
221
 
222
  $saved_version = get_option( 'ninja_forms_version' );
 
 
 
 
 
223
  // If we have a recorded version...
224
  // AND that version is less than our current version...
225
  if ( $saved_version && version_compare( $saved_version, self::VERSION, '<' ) ) {
241
  if ( $saved_version && version_compare( $saved_version, '3.3.0', '<' ) ) {
242
  // Set it to the baseline (1.0) so that our upgrade process will run properly.
243
  add_option( 'ninja_forms_db_version', '1.0', '', 'no' );
244
+ } // OR If this isn't a fresh install...
245
+ elseif ( $saved_version ) {
246
+ // Set it to the expected (1.1) so that our upgrade process will handle that.
247
+ add_option( 'ninja_forms_db_version', '1.1', '', 'no' );
248
+ } // Otherwise... (This is a fresh install.)
249
  else {
250
+ // Set it to the current DB version.
251
+ add_option( 'ninja_forms_db_version', self::DB_VERSION, '', 'no' );
252
+ // Establish that our upgrades don't need to take place.
253
+ $required_updates = Ninja_Forms()->config( 'RequiredUpdates' );
254
+ $updated = array();
255
+ // Get a timestamp.
256
+ date_default_timezone_set( 'UTC' );
257
+ $now = date( "Y-m-d H:i:s" );
258
+ foreach( $required_updates as $slug => $update ) {
259
+ $updated[ $slug ] = $now;
260
+ }
261
+ update_option( 'ninja_forms_required_updates', $updated );
262
  }
263
  }
264
+ // Set our static db version.
265
+ self::$db_version = get_option( 'ninja_forms_db_version', self::$db_version );
266
 
267
  /*
268
  * Register our autoloader
290
  self::$instance->controllers[ 'form' ] = new NF_AJAX_Controllers_Form();
291
  self::$instance->controllers[ 'fields' ] = new NF_AJAX_Controllers_Fields();
292
  self::$instance->controllers[ 'batch_process' ] = new NF_AJAX_REST_BatchProcess();
293
+ self::$instance->controllers[ 'required_updates' ] = new NF_AJAX_REST_RequiredUpdate();
294
  self::$instance->controllers[ 'preview' ] = new NF_AJAX_Controllers_Preview();
295
  self::$instance->controllers[ 'submission' ] = new NF_AJAX_Controllers_Submission();
296
  self::$instance->controllers[ 'savedfields' ] = new NF_AJAX_Controllers_SavedFields();
442
  // Ensure all of our tables have been defined.
443
  $migrations = new NF_Database_Migrations();
444
  $migrations->migrate();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
445
  }
446
  }
447
 
479
  }
480
 
481
  add_filter( 'ninja_forms_dashboard_menu_items', array( $this, 'maybe_hide_dashboard_items' ) );
482
+
483
+ // Remove already completed updates from our filtered list of required updates.
484
+ add_filter( 'ninja_forms_required_updates', array( $this, 'remove_completed_updates' ), 99 );
485
+ add_filter( 'ninja_forms_required_updates', array( $this, 'remove_bad_updates' ), 99 );
486
 
487
+
488
+ // Get our list of required updates.
489
+ $required_updates = Ninja_Forms()->config( 'RequiredUpdates' );
490
+ global $wpdb;
491
+ $sql = "SELECT COUNT( `id` ) AS total FROM `{$wpdb->prefix}nf3_forms`;";
492
+ $result = $wpdb->get_results( $sql, 'ARRAY_A' );
493
+ $threshold = 5; // Threshold percentage for our required updates.
494
+ // If we got back a list of updates...
495
+ // AND If we have any forms on the site...
496
+ // AND If the gate is open...
497
+ if ( ! empty( $required_updates )
498
+ && 0 < $result[ 0 ][ 'total' ]
499
+ && WPN_Helper::gated_release( $threshold ) ) {
500
+ // Record that we have updates to run.
501
+ update_option( 'ninja_forms_needs_updates', 1 );
502
+ } // Otherwise... (Sanity check)
503
+ else {
504
+ // Record that there are no required updates.
505
+ update_option( 'ninja_forms_needs_updates', 0 );
506
  }
507
  }
508
 
579
  $items[ 'services' ]
580
  );
581
  }
582
+ if ( 1 == get_option( 'ninja_forms_needs_updates' ) ) {
583
+ unset(
584
+ $items[ 'widgets' ],
585
+ $items[ 'apps' ],
586
+ $items[ 'memberships' ],
587
+ $items[ 'services' ]
588
+ );
589
+ }
590
  return $items;
591
  }
592
 
900
 
901
  if( Ninja_Forms()->form()->get_forms() ) return;
902
 
903
+ // Go ahead and create our randomn number for gated releases in the future
904
+ $zuul = WPN_Helper::get_zuul();
905
+
906
  $form = Ninja_Forms::template( 'formtemplate-contactform.nff', array(), TRUE );
907
  Ninja_Forms()->form()->import_form( $form );
908
  }
936
  }
937
  }
938
  }
939
+
940
+ /**
941
+ * Function to deregister already completed updates from the list of required updates.
942
+ *
943
+ * @since 3.3.14
944
+ *
945
+ * @param $updates (Array) Our array of required updates.
946
+ * @return $updates (Array) Our array of required updates.
947
+ */
948
+ public function remove_completed_updates( $updates ) {
949
+ $processed = get_option( 'ninja_forms_required_updates', array() );
950
+ // For each update in our list...
951
+ foreach ( $updates as $slug => $update ) {
952
+ // If we have already processed it...
953
+ if ( isset( $processed[ $slug ] ) ) {
954
+ // Remove it from the list.
955
+ unset( $updates[ $slug ] );
956
+ }
957
+ }
958
+ return $updates;
959
+ }
960
 
961
  /**
962
+ * Function to deregister updates that have required updates that either
963
+ * don't exist, or are malformed
964
+ *
965
+ * @since UPDATE_TO_LATEST version
966
+ *
967
+ * @param $updates (Array) Our array of required updates.
968
+ * @return $updates (Array) Our array of required updates.
969
  */
970
+ public function remove_bad_updates( $updates ) {
971
+
972
+ $processed = get_option( 'ninja_forms_required_updates', array() );
973
+
974
+ $sorted = array();
975
+ $queue = array();
976
+ // While we have not finished removing bad updates...
977
+ while ( count( $sorted ) < count( $updates ) ) {
978
+ // For each update we wish to run...
979
+ foreach ( $updates as $slug => $update ) {
980
+ // Migrate the slug to a property.
981
+ $update[ 'slug' ] = $slug;
982
+ // If we've not already added this to the sorted list...
983
+ if ( ! in_array( $update, $sorted ) ) {
984
+ // If it has requirements...
985
+ if ( ! empty( $update[ 'requires' ] ) ) {
986
+ $enqueued = 0;
987
+ // For each requirement...
988
+ foreach ( $update[ 'requires' ] as $requirement ) {
989
+ // If the requirement doesn't exist...
990
+ if ( ! isset( $updates[ $requirement ] ) ) {
991
+ // unset the update b/c we are missing requirements
992
+ unset( $updates[ $slug ] );
993
+
994
+ $nf_bad_update_transient = get_transient( 'nf_bad_update_requirement' );
995
+
996
+ if( ! $nf_bad_update_transient ) {
997
+ // send telemetry so we can keep up with these
998
+ Ninja_Forms()->dispatcher()->send( 'incomplete_update',
999
+ array(
1000
+ 'update' => $slug,
1001
+ 'missing_requirement' => $requirement
1002
+ )
1003
+ );
1004
+
1005
+ set_transient( 'nf_bad_update_requirement', $requirement, 30 * 3600 );
1006
+ }
1007
+ }
1008
+ // If the requirement has already been added to the stack...
1009
+ if ( in_array( $requirement, $queue ) ) {
1010
+ $enqueued++;
1011
+ } // OR If the requirement has already been processed...
1012
+ elseif ( isset( $processed[ $requirement ] ) ) {
1013
+ $enqueued++;
1014
+ }
1015
+ }
1016
+ // If all requirement are met...
1017
+ if ( $enqueued == count( $update[ 'requires' ] ) ) {
1018
+ // Add it to the list.
1019
+ array_push( $sorted, $update );
1020
+ // Record that we enqueued it.
1021
+ array_push( $queue, $slug );
1022
+ }
1023
+ } // Otherwise... (It has no requirements.)
1024
+ else {
1025
+ // Add it to the list.
1026
+ array_push( $sorted, $update );
1027
+ // Record that we enqueued it.
1028
+ array_push( $queue, $slug );
1029
+ }
1030
+ }
1031
+ }
1032
+ }
1033
+ return $sorted;
1034
  }
1035
 
1036
  } // End Class Ninja_Forms
readme.txt CHANGED
@@ -3,7 +3,7 @@ Contributors: wpninjasllc, kstover, jameslaws, kbjohnson90, klhall1987, krmoorho
3
  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,
4
  Requires at least: 4.8
5
  Tested up to: 5.0
6
- Stable tag: 3.3.21.3
7
  License: GPLv2 or later
8
 
9
  Drag and drop fields in an intuitive UI to create contact forms, email subscription forms, order forms, payment forms, send emails and more!
@@ -111,14 +111,22 @@ For help and video tutorials, please visit our website: [Ninja Forms Documentati
111
 
112
  == Upgrade Notice ==
113
 
114
- = 3.3.21.3 (10 January 2019) =
115
 
116
- *Security:*
117
 
118
- * (2.9x) Duplicated previous blind SQL injection patch for our deprecated 2.9x codebase. Many thanks to Plugin Vulnerabilities for reporting that our initial pass missed this.
 
119
 
120
  == Changelog ==
121
 
 
 
 
 
 
 
 
122
  = 3.3.21.3 (10 January 2019) =
123
 
124
  *Security:*
3
  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,
4
  Requires at least: 4.8
5
  Tested up to: 5.0
6
+ Stable tag: 3.4.0
7
  License: GPLv2 or later
8
 
9
  Drag and drop fields in an intuitive UI to create contact forms, email subscription forms, order forms, payment forms, send emails and more!
111
 
112
  == Upgrade Notice ==
113
 
114
+ = 3.4.0 (14 January 2019) =
115
 
116
+ *Changes:*
117
 
118
+ * Implemented a new import process, which should be more reliable with large form imports.
119
+ * Upgraded our data structure to reduce loading times for forms and the form builder.
120
 
121
  == Changelog ==
122
 
123
+ = 3.4.0 (14 January 2019) =
124
+
125
+ *Changes:*
126
+
127
+ * Implemented a new import process, which should be more reliable with large form imports.
128
+ * Upgraded our data structure to reduce loading times for forms and the form builder.
129
+
130
  = 3.3.21.3 (10 January 2019) =
131
 
132
  *Security:*