PluginProbe ʕ •ᴥ•ʔ
WP 2FA – Two-factor authentication for WordPress / 4.0.0
WP 2FA – Two-factor authentication for WordPress v4.0.0
4.0.0 1.7.1 2.0.0 2.0.1 2.1.0 2.2.0 2.2.1 2.3.0 2.4.0 2.4.1 2.4.2 2.5.0 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.7.0 2.8.0 2.9.0 2.9.1 2.9.2 2.9.3 3.0.0 3.0.1 3.1.0 3.1.1 3.1.1.2 trunk 1.2.0 1.3.0 1.4.0 1.4.1 1.4.2 1.5.0 1.5.1 1.5.2 1.6.0 1.6.1 1.6.2 1.7.0
wp-2fa / includes / classes / Admin / Settings / templates / import-export.php
wp-2fa / includes / classes / Admin / Settings / templates Last commit date
about-us 1 day ago partials 1 day ago passkeys 1 day ago policies 1 day ago reports 1 day ago support 1 day ago customize-code-page.php 1 day ago customize-setup-wizard.php 1 day ago customize-user-profile.php 1 day ago customize-user-prompts.php 1 day ago email-settings.php 1 day ago general-settings.php 1 day ago generic-settings.php 1 day ago import-export.php 1 day ago index.php 1 day ago plugins-integrations.php 1 day ago providers-integrations.php 1 day ago sidebar.php 1 day ago white-labeling.php 1 day ago
import-export.php
642 lines
1 <?php
2 /**
3 * Import/Export Settings Template
4 *
5 * @package wp-2fa
6 */
7
8 use WP2FA\WP2FA;
9 use WP2FA\Admin\Settings_Builder;
10 use WP2FA\Admin\Helpers\Email_Templates;
11
12 $ie_nonce = \wp_create_nonce( 'wp-2fa-export-settings' );
13 $ie_key = 'wp-2fa';
14 $ie_current_policy = WP2FA::get_wp2fa_setting( 'enforcement-policy' );
15 $ie_show_site_specific = ( 'certain-roles-only' === $ie_current_policy || 'certain-users-only' === $ie_current_policy );
16 $ie_show_custom_email = ( 'use-custom-email' === Email_Templates::get_wp2fa_email_templates( 'email_from_setting' ) );
17
18 ?>
19 <div class="settings-page" id="import-export-wrap">
20
21 <?php
22 Settings_Builder::build_option(
23 array(
24 'parent' => \esc_html__( 'Settings', 'wp-2fa' ),
25 'type' => 'breadcrumb',
26 'custom_class' => 'back-policies-settings-main-wrapper',
27 'default' => \esc_html__( 'Export / import', 'wp-2fa' ),
28 )
29 );
30
31 Settings_Builder::build_option(
32 array(
33 'title' => \esc_html__( 'Export / import', 'wp-2fa' ),
34 'id' => 'import-export-tab',
35 'type' => 'tab-title',
36 )
37 );
38
39 Settings_Builder::build_option(
40 array(
41 'text' => \wp_sprintf(
42 // translators: 1. Link to documentation.
43 \esc_html__( 'Export the plugin\'s settings to a file, or import settings from a previously exported file. Use this to back up your configuration or copy the same settings to another website. %1$s.', 'wp-2fa' ),
44 \wp_sprintf( '<a href="%s" target="_blank">%s</a>', 'https://melapress.com/support/kb/wp-2fa-plugin-getting-started/?#utm_source=plugin&utm_medium=wp2fa&utm_campaign=guide_getting_started_wp2fa', \esc_html__( 'Learn more', 'wp-2fa' ) )
45 ),
46 'class' => 'description-settings-card',
47 'id' => 'import-export-tab',
48 'type' => 'description',
49 )
50 );
51
52 ?>
53
54 <div class="settings-card">
55
56 <!-- Export Section -->
57 <div class="import-export-section">
58 <div class="section-content">
59 <h3 class="section-heading"><?php \esc_html_e( 'Export', 'wp-2fa' ); ?></h3>
60 <p class="section-description">
61 <?php \esc_html_e( 'Once the settings are exported a download will automatically start.', 'wp-2fa' ); ?><br>
62 <?php \esc_html_e( 'The settings are exported to a JSON file.', 'wp-2fa' ); ?>
63 </p>
64
65 <?php if ( $ie_show_site_specific ) : ?>
66 <div class="export-option-row">
67 <label for="ie-export-site-specific">
68 <input type="checkbox" id="ie-export-site-specific" value="site-specific">
69 <?php \esc_html_e( 'Also export the site specific settings as enforced users and roles (the policy will be set to Do not enforce if left unchecked)', 'wp-2fa' ); ?>
70 </label>
71 </div>
72 <?php endif; ?>
73
74 <?php if ( $ie_show_custom_email ) : ?>
75 <div class="export-option-row">
76 <label for="export-custom-from-email">
77 <input type="checkbox" id="export-custom-from-email" name="export-custom-from-email" value="site-specific">
78 <?php \esc_html_e( 'Add the custom from email to the export', 'wp-2fa' ); ?>
79 </label>
80 </div>
81 <?php endif; ?>
82 </div>
83 <div class="section-actions">
84 <button type="button" class="button button-primary" id="export-settings-btn">
85 <?php \esc_html_e( 'Export', 'wp-2fa' ); ?>
86 </button>
87 </div>
88
89 </div>
90
91 <hr class="import-export-divider">
92
93 <!-- Import Section -->
94 <div class="import-export-section">
95 <div class="section-content">
96 <h3 class="section-heading"><?php \esc_html_e( 'Import', 'wp-2fa' ); ?></h3>
97 <p class="section-description">
98 <?php \esc_html_e( 'Once you choose a JSON settings file, it is validated before import and any problems are flagged.', 'wp-2fa' ); ?>
99 </p>
100 <span id="file-name-display" class="file-name-display"></span>
101 </div>
102 <div class="section-actions">
103 <input type="file" id="import-settings-file" accept=".json" style="display: none;">
104 <button type="button" class="button button-secondary" id="select-file-btn">
105 <?php \esc_html_e( 'Select file', 'wp-2fa' ); ?>
106 </button>
107 <button type="button" class="button button-primary" id="import-settings-btn" disabled>
108 <?php \esc_html_e( 'Validate and import', 'wp-2fa' ); ?>
109 </button>
110 </div>
111
112 </div>
113
114 </div>
115
116 <!-- Import Validation Modal -->
117 <div id="ie-import-modal">
118 <div class="ie-modal-content">
119 <span class="ie-modal-close">&times;</span>
120 <h3 id="ie-modal-title"></h3>
121 <ul id="ie-settings-output"></ul>
122 <div id="ie-modal-actions"></div>
123 </div>
124 </div>
125
126 </div>
127
128 <script>
129 (function() {
130 var ieKey = <?php echo \wp_json_encode( $ie_key ); ?>;
131 var ieNonce = <?php echo \wp_json_encode( $ie_nonce ); ?>;
132 var ieI18n = {
133 checking: <?php echo \wp_json_encode( \esc_html__( 'Checking import contents', 'wp-2fa' ) ); ?>,
134 checksPassed: <?php echo \wp_json_encode( \esc_html__( 'Ready to import', 'wp-2fa' ) ); ?>,
135 checksFailed: <?php echo \wp_json_encode( \esc_html__( 'Issues found', 'wp-2fa' ) ); ?>,
136 importing: <?php echo \wp_json_encode( \esc_html__( 'Importing settings', 'wp-2fa' ) ); ?>,
137 imported: <?php echo \wp_json_encode( \esc_html__( 'Settings imported', 'wp-2fa' ) ); ?>,
138 importError: <?php echo \wp_json_encode( \esc_html__( 'This setting could not be imported. Please review the setting value and try again.', 'wp-2fa' ) ); ?>,
139 wrongFormat: <?php echo \wp_json_encode( \esc_html__( 'Please upload a valid JSON file.', 'wp-2fa' ) ); ?>,
140 cancel: <?php echo \wp_json_encode( \esc_html__( 'Cancel', 'wp-2fa' ) ); ?>,
141 ready: <?php echo \wp_json_encode( \esc_html__( 'The settings file has been tested and the configuration is ready to be imported. Would you like to proceed?', 'wp-2fa' ) ); ?>,
142 proceeded: <?php echo \wp_json_encode( \esc_html__( 'The configuration has been successfully imported. Click OK to close this window', 'wp-2fa' ) ); ?>,
143 proceed: <?php echo \wp_json_encode( \esc_html__( 'Proceed', 'wp-2fa' ) ); ?>,
144 ok: <?php echo \wp_json_encode( \esc_html__( 'OK', 'wp-2fa' ) ); ?>,
145 oldVersionTitle: <?php echo \wp_json_encode( \esc_html__( 'Incompatible import file detected', 'wp-2fa' ) ); ?>,
146 oldVersion: <?php echo \wp_json_encode( \wp_kses(
147 __( 'We detected that this file was exported from WP 2FA version 3.x. This format is no longer supported in version 4.0 and above and cannot be imported directly.<br><br>To resolve this, generate a fresh export from a site already running WP 2FA 4.0 or later and use that file here.<br><br><a href="https://melapress.com/support/submit-ticket/?utm_source=plugin&utm_medium=wp2fa&utm_campaign=settings-exportimport-submit-ticket" target="_blank">Contact support</a> if you have any questions.', 'wp-2fa' ),
148 array( 'br' => array(), 'a' => array( 'href' => array(), 'target' => array() ) )
149 ) ); ?>
150 };
151
152 document.addEventListener('DOMContentLoaded', function() {
153 var selectFileBtn = document.getElementById('select-file-btn');
154 var fileInput = document.getElementById('import-settings-file');
155 var importBtn = document.getElementById('import-settings-btn');
156 var fileNameDisplay = document.getElementById('file-name-display');
157 var exportBtn = document.getElementById('export-settings-btn');
158 var modal = document.getElementById('ie-import-modal');
159 var modalTitle = document.getElementById('ie-modal-title');
160 var settingsOutput = document.getElementById('ie-settings-output');
161 var modalActions = document.getElementById('ie-modal-actions');
162 var modalClose = document.querySelector('.ie-modal-close');
163
164 // Select file button.
165 if (selectFileBtn) {
166 selectFileBtn.addEventListener('click', function() {
167 fileInput.click();
168 });
169 }
170
171 // File selection.
172 if (fileInput) {
173 fileInput.addEventListener('change', function(e) {
174 var file = e.target.files[0];
175 if (file) {
176 fileNameDisplay.textContent = file.name;
177 importBtn.disabled = false;
178 } else {
179 fileNameDisplay.textContent = '';
180 importBtn.disabled = true;
181 }
182 });
183 }
184
185 // Close modal.
186 if (modalClose) {
187 modalClose.addEventListener('click', function() {
188 modal.style.display = 'none';
189 });
190 }
191
192 // Export settings.
193 if (exportBtn) {
194 exportBtn.addEventListener('click', function() {
195 var customEmail = false;
196 var siteSpecific = false;
197 var cbEmail = document.getElementById('export-custom-from-email');
198 var cbSite = document.getElementById('ie-export-site-specific');
199
200 if (cbEmail && cbEmail.checked) {
201 customEmail = true;
202 }
203 if (cbSite && cbSite.checked) {
204 siteSpecific = true;
205 }
206
207 var formData = new FormData();
208 formData.append('action', ieKey + '_export_settings');
209 formData.append('nonce', ieNonce);
210 formData.append('export-custom-from-email', customEmail);
211 formData.append('export-site-specific', siteSpecific);
212
213 fetch(ajaxurl, { method: 'POST', body: formData })
214 .then(function(r) { return r.json(); })
215 .then(function(result) {
216 if (result.data) {
217 var json = JSON.stringify(result.data);
218 var blob = new Blob([json], { type: 'application/json' });
219 var link = document.createElement('a');
220 link.href = URL.createObjectURL(blob);
221 link.download = ieKey + '_settings.json';
222 link.click();
223 URL.revokeObjectURL(link.href);
224 }
225 });
226 });
227 }
228
229 // Validate & Import.
230 if (importBtn) {
231 importBtn.addEventListener('click', function() {
232 if (!fileInput.files || !fileInput.files[0]) {
233 return;
234 }
235
236 var ext = fileInput.value.split('.').pop().toLowerCase();
237 if (ext !== 'json') {
238 alert(ieI18n.wrongFormat);
239 return;
240 }
241
242 buildFileInfo('false');
243 });
244 }
245
246 /**
247 * Read the JSON file and start checking/importing each setting.
248 */
249 function buildFileInfo(doImport) {
250 // Reset modal actions.
251 if (doImport === 'false') {
252 settingsOutput.innerHTML = '';
253 modalActions.innerHTML =
254 '<button type="button" class="button button-secondary ie-modal-close-btn">' + ieI18n.cancel + '</button>' +
255 ' <button type="button" class="button button-primary" id="ie-proceed-btn">' + ieI18n.proceed + '</button>';
256 modalActions.classList.add('ie-disabled');
257 } else {
258 modalActions.classList.add('ie-disabled');
259 }
260
261 var reader = new FileReader();
262 reader.readAsText(fileInput.files[0]);
263
264 reader.onload = function() {
265 var parsed = JSON.parse(reader.result);
266 var options = Array.isArray(parsed) ? parsed : JSON.parse(parsed);
267
268 // Check if the export file contains a version identifier.
269 var hasVersion = options.some(function(opt) {
270 return opt && opt.option_name === 'wp_2fa_export_version';
271 });
272
273 if (!hasVersion) {
274 modal.style.display = 'block';
275 modalTitle.textContent = ieI18n.oldVersionTitle;
276 settingsOutput.innerHTML = '';
277 modalActions.innerHTML = '<p class="ie-ready-text">' + ieI18n.oldVersion + '</p>';
278 modalActions.classList.remove('ie-disabled');
279 return;
280 }
281
282 for (var i = 0; i < options.length; i++) {
283 if (options[i] === '' || !options[i]) {
284 continue;
285 }
286 var optName = options[i].option_name;
287 var optValue = options[i].option_value;
288
289 // Skip the version metadata entry.
290 if (optName === 'wp_2fa_export_version') {
291 continue;
292 }
293
294 var label = optName.replace(ieKey + '_', '').replace(/_/g, ' ').replace(/-/g, ' ');
295
296 if (doImport === 'false') {
297 var li = document.createElement('li');
298 li.setAttribute('data-ie-option', optName);
299 li.innerHTML = '<div class="ie-option-label">' + label + '</div>';
300 settingsOutput.appendChild(li);
301 }
302
303 checkSettingPreImport(optName, optValue, doImport);
304 }
305 };
306
307 // Bind action buttons after they're rendered.
308 setTimeout(function() {
309 var closeBtn = modalActions.querySelector('.ie-modal-close-btn');
310 if (closeBtn) {
311 closeBtn.addEventListener('click', function() {
312 modal.style.display = 'none';
313 });
314 }
315 var proceedBtn = document.getElementById('ie-proceed-btn');
316 if (proceedBtn) {
317 proceedBtn.addEventListener('click', function() {
318 buildFileInfo('true');
319 });
320 }
321 }, 50);
322 }
323
324 /**
325 * AJAX-check (or import) a single setting.
326 */
327 function checkSettingPreImport(optName, optValue, doImport) {
328 var encodedValue = btoa(unescape(encodeURIComponent(optValue)));
329
330 modal.style.display = 'block';
331
332 if (doImport === 'true') {
333 modalTitle.textContent = ieI18n.importing;
334 // Mark existing status icons as "complete" so we can count new ones.
335 var oldIcons = settingsOutput.querySelectorAll('[data-ie-option] .ie-status');
336 for (var s = 0; s < oldIcons.length; s++) {
337 oldIcons[s].classList.add('ie-complete');
338 }
339 } else {
340 modalTitle.textContent = ieI18n.checking;
341 }
342
343 var formData = new FormData();
344 formData.append('action', ieKey + '_check_setting_pre_import');
345 formData.append('setting_name', optName);
346 formData.append('setting_value_b64', encodedValue);
347 formData.append('process_import', doImport);
348 formData.append('nonce', ieNonce);
349
350 fetch(ajaxurl, { method: 'POST', body: formData })
351 .then(function(r) { return r.json(); })
352 .then(function(result) {
353 var li = settingsOutput.querySelector('[data-ie-option="' + optName + '"]');
354 if (!li) {
355 return;
356 }
357
358 if (result.success) {
359 if (doImport === 'true' && result.data && result.data.import_confirmation) {
360 li.insertAdjacentHTML('beforeend', '<span class="ie-status ie-success">' + result.data.import_confirmation + '</span>');
361 } else {
362 li.insertAdjacentHTML('beforeend', '<span class="ie-status ie-success dashicons dashicons-yes-alt"></span>');
363 }
364 } else {
365 var reason = result.data && result.data.failure_reason ? result.data.failure_reason : ieI18n.importError;
366 li.insertAdjacentHTML('beforeend',
367 '<span class="ie-status ie-error">' +
368 ' <span class="dashicons dashicons-info" aria-hidden="true"></span>' +
369 ' <span class="ie-error-message">' + reason + '</span>' +
370 '</span>'
371 );
372 }
373
374 // Check if all settings have been processed.
375 var totalOptions = settingsOutput.querySelectorAll('[data-ie-option]').length;
376 var doneOptions = settingsOutput.querySelectorAll('[data-ie-option] .ie-status:not(.ie-complete)').length;
377
378 if (totalOptions === doneOptions) {
379 if (doImport === 'true') {
380 modalTitle.textContent = ieI18n.imported;
381 modalActions.innerHTML =
382 '<p class="ie-ready-text">' + ieI18n.proceeded + '</p>' +
383 '<button type="button" class="button button-secondary ie-modal-close-btn">' + ieI18n.ok + '</button>';
384 modalActions.classList.remove('ie-disabled');
385 var closeBtn = modalActions.querySelector('.ie-modal-close-btn');
386 if (closeBtn) {
387 closeBtn.addEventListener('click', function() {
388 modal.style.display = 'none';
389 });
390 }
391 } else {
392 var errorCount = settingsOutput.querySelectorAll('.ie-error').length;
393 if (errorCount) {
394 modalTitle.textContent = ieI18n.checksFailed;
395 } else {
396 modalTitle.textContent = ieI18n.checksPassed;
397 }
398
399 var proceedLabel = errorCount ? 'Proceed and skip invalid settings' : ieI18n.proceed;
400 modalActions.innerHTML =
401 '<p class="ie-ready-text">' + ieI18n.ready + '</p>' +
402 '<button type="button" class="button button-secondary ie-modal-close-btn">' + ieI18n.cancel + '</button>' +
403 ' <button type="button" class="button button-primary" id="ie-proceed-btn">' + proceedLabel + '</button>';
404 modalActions.classList.remove('ie-disabled');
405
406 var closeBtn2 = modalActions.querySelector('.ie-modal-close-btn');
407 if (closeBtn2) {
408 closeBtn2.addEventListener('click', function() {
409 modal.style.display = 'none';
410 });
411 }
412 var proceedBtn2 = document.getElementById('ie-proceed-btn');
413 if (proceedBtn2) {
414 proceedBtn2.addEventListener('click', function() {
415 buildFileInfo('true');
416 });
417 }
418 }
419 }
420 });
421 }
422
423 });
424 })();
425 </script>
426
427 <style>
428 .import-export-section {
429 margin-bottom: 30px;
430 display: flex;
431 gap: 30px;
432 align-items: flex-start;
433 }
434
435 .import-export-section:last-child {
436 margin-bottom: 0;
437 }
438
439 .section-actions {
440 display: flex;
441 flex-direction: column;
442 gap: 10px;
443 min-width: 140px;
444 flex-shrink: 0;
445 }
446
447 .section-content {
448 flex: 1;
449 }
450
451 .section-heading {
452 margin: 0 0 12px 0;
453 font-size: 16px;
454 font-weight: 600;
455 color: #333;
456 }
457
458 .section-description {
459 margin: 0 0 12px 0;
460 font-size: 14px;
461 color: #666;
462 line-height: 1.5;
463 }
464
465 .export-option-row {
466 margin: 8px 0;
467 font-size: 14px;
468 }
469
470 .export-option-row label {
471 display: flex;
472 align-items: flex-start;
473 gap: 6px;
474 cursor: pointer;
475 color: #444;
476 line-height: 1.4;
477 }
478
479 .export-option-row input[type="checkbox"] {
480 margin-top: 3px;
481 flex-shrink: 0;
482 }
483
484 .file-name-display {
485 display: block;
486 font-size: 14px;
487 color: #666;
488 padding: 8px 12px;
489 border: 1px solid #ddd;
490 border-radius: 4px;
491 background: #f9f9f9;
492 margin-top: 8px;
493 min-height: 20px;
494 }
495
496 .import-export-divider {
497 margin: 30px 0;
498 border: none;
499 border-top: 1px solid #e5e5e5;
500 }
501
502 #import-export-wrap .button {
503 cursor: pointer;
504 font-size: 14px;
505 padding: 8px 16px;
506 border-radius: 4px;
507 white-space: nowrap;
508 }
509
510 #import-export-wrap .button-primary:hover:not(:disabled) {
511 background-color: #005a87;
512 }
513
514 #import-export-wrap #import-settings-btn {
515 margin-bottom: 20px;
516 }
517
518 #import-export-wrap .button:disabled {
519 opacity: 0.5;
520 cursor: not-allowed;
521 }
522
523 #select-file-btn {
524 border: 1px dashed;
525 }
526
527 /* Import Modal */
528 #ie-import-modal {
529 display: none;
530 position: fixed;
531 z-index: 9999;
532 left: 0;
533 top: 0;
534 width: 100%;
535 height: 100%;
536 overflow: auto;
537 background-color: rgba(0, 0, 0, 0.4);
538 }
539
540 .ie-modal-content {
541 background-color: #fefefe;
542 margin: 5% auto;
543 padding: 24px;
544 border: 1px solid #888;
545 border-radius: 6px;
546 width: 80%;
547 max-width: 800px;
548 position: relative;
549 }
550
551 .ie-modal-close {
552 color: #aaa;
553 position: absolute;
554 right: 16px;
555 top: 12px;
556 font-size: 28px;
557 font-weight: bold;
558 cursor: pointer;
559 }
560
561 .ie-modal-close:hover {
562 color: #333;
563 }
564
565 #ie-modal-title {
566 font-size: 20px;
567 margin: 0 0 16px;
568 padding-right: 30px;
569 }
570
571 #ie-settings-output {
572 list-style: none;
573 margin: 0 0 16px;
574 padding: 0;
575 }
576
577 [data-ie-option] {
578 line-height: 28px;
579 padding: 2px 0;
580 display: flex;
581 align-items: flex-start;
582 flex-wrap: nowrap;
583 gap: 10px;
584 }
585
586 .ie-option-label {
587 display: inline-block;
588 min-width: 285px;
589 flex: 0 0 285px;
590 font-size: 14px;
591 font-weight: 500;
592 text-transform: capitalize;
593 }
594
595 .ie-status {
596 margin-left: 0;
597 font-size: 14px;
598 display: inline-flex;
599 align-items: center;
600 gap: 6px;
601 flex: 1 1 auto;
602 }
603
604 .ie-success {
605 color: green;
606 }
607
608 .ie-error {
609 color: red;
610 align-items: flex-start;
611 line-height: 1.4;
612 }
613
614 .ie-error .ie-error-message {
615 font-size: 14px;
616 display: inline;
617 }
618
619 .ie-error .dashicons {
620 flex: 0 0 auto;
621 margin-top: 2px;
622 }
623
624 #ie-modal-actions {
625 margin-top: 16px;
626 }
627
628 #ie-modal-actions.ie-disabled {
629 opacity: 0.5;
630 pointer-events: none;
631 }
632
633 .ie-ready-text {
634 margin: 0 0 12px;
635 font-size: 14px;
636 }
637
638 #ie-modal-actions .button {
639 margin-right: 8px;
640 }
641 </style>
642