11<?php
2- /*
3- Plugin Name: Simple WP Site Exporter
4- Description: Exports the site files and database as a zip archive.
5- Version: 1.8.0
6- Author: EngineScript
7- License: GPL v3 or later
8- Text Domain: Simple-WP-Site-Exporter
9- */
2+ /**
3+ * Plugin Name: Simple WP Site Exporter
4+ * Description: Exports the site files and database as a zip archive.
5+ * Version: 1.8.0
6+ * Author: EngineScript
7+ * License: GPL v3 or later
8+ * Text Domain: Simple-WP-Site-Exporter
9+ */
1010
1111// Prevent direct access. Note: Using return here instead of exit.
1212if ( ! defined ( 'ABSPATH ' ) ) {
@@ -40,6 +40,7 @@ function sse_load_textdomain() {
4040 * or use statements as they are part of WordPress core.
4141 *
4242 * Core classes used:
43+ *
4344 * @see WP_Error - WordPress error handling class
4445 * @see ZipArchive - PHP ZipArchive class
4546 * @see RecursiveIteratorIterator - PHP SPL iterator
@@ -64,15 +65,14 @@ function sse_get_client_ip() {
6465 if ( isset ( $ _SERVER ['REMOTE_ADDR ' ] ) ) {
6566 $ client_ip = sanitize_text_field ( wp_unslash ( $ _SERVER ['REMOTE_ADDR ' ] ) );
6667 }
67-
68+
6869 // Basic IP validation.
6970 if ( filter_var ( $ client_ip , FILTER_VALIDATE_IP ) !== false ) {
7071 return $ client_ip ;
7172 }
72-
73- return 'unknown ' ;
7473
75- } //end sse_get_client_ip()
74+ return 'unknown ' ;
75+ } // end sse_get_client_ip()
7676
7777
7878/**
@@ -99,8 +99,7 @@ function sse_store_log_in_database( $message, $level ) {
9999 }
100100
101101 update_option ( 'sse_error_logs ' , $ logs );
102-
103- } //end sse_store_log_in_database()
102+ } // end sse_store_log_in_database()
104103
105104
106105/**
@@ -114,8 +113,7 @@ function sse_output_log_message( $formatted_message ) {
114113 if ( function_exists ( 'wp_debug_log ' ) ) {
115114 wp_debug_log ( $ formatted_message );
116115 }
117-
118- } //end sse_output_log_message()
116+ } // end sse_output_log_message()
119117
120118
121119/**
@@ -151,37 +149,35 @@ function sse_log( $message, $level = 'info' ) {
151149 if ( 'error ' === $ level || 'security ' === $ level ) {
152150 sse_store_log_in_database ( $ message , $ level );
153151 }
154-
155- } //end sse_log()
152+ } // end sse_log()
156153
157154/**
158- * Safely get the PHP execution time limit
155+ * Safely get the PHP execution time limit.
159156 *
160- * @return int Current PHP execution time limit in seconds
157+ * @return int Current PHP execution time limit in seconds.
161158 */
162159function sse_get_execution_time_limit () {
163- // Get the current execution time limit
160+ // Get the current execution time limit.
164161 $ max_exec_time = ini_get ( 'max_execution_time ' );
165-
166- // Handle all possible return types from ini_get()
162+
163+ // Handle all possible return types from ini_get().
167164 if ( false === $ max_exec_time ) {
168- // ini_get failed
165+ // Ini_get failed.
169166 return 30 ;
170167 }
171-
168+
172169 if ( '' === $ max_exec_time ) {
173- // Empty string returned
170+ // Empty string returned.
174171 return 30 ;
175172 }
176-
173+
177174 if ( ! is_numeric ( $ max_exec_time ) ) {
178- // Non-numeric value returned
175+ // Non-numeric value returned.
179176 return 30 ;
180177 }
181-
182- return (int ) $ max_exec_time ;
183178
184- } //end sse_get_execution_time_limit()
179+ return (int ) $ max_exec_time ;
180+ } // end sse_get_execution_time_limit()
185181
186182// --- Admin Menu ---
187183/**
@@ -191,9 +187,9 @@ function sse_get_execution_time_limit() {
191187 */
192188function sse_admin_menu () {
193189 add_management_page (
194- esc_html__ ( 'Simple WP Site Exporter ' , 'Simple-WP-Site-Exporter ' ), // Escaped title
195- esc_html__ ( 'Site Exporter ' , 'Simple-WP-Site-Exporter ' ), // Escaped menu title
196- 'manage_options ' , // Capability required
190+ esc_html__ ( 'Simple WP Site Exporter ' , 'Simple-WP-Site-Exporter ' ), // Escaped title.
191+ esc_html__ ( 'Site Exporter ' , 'Simple-WP-Site-Exporter ' ), // Escaped menu title.
192+ 'manage_options ' , // Capability required.
197193 'simple-wp-site-exporter ' ,
198194 'sse_exporter_page_html '
199195 );
@@ -269,7 +265,7 @@ function sse_handle_export() {
269265 }
270266
271267 sse_prepare_execution_environment ();
272-
268+
273269 $ export_paths = sse_setup_export_directories ();
274270 if ( is_wp_error ( $ export_paths ) ) {
275271 wp_die ( esc_html ( $ export_paths ->get_error_message () ) );
@@ -314,8 +310,7 @@ function sse_validate_export_request() { // phpcs:ignore WordPress.Security.Nonc
314310 }
315311
316312 return true ;
317-
318- } //end sse_validate_export_request()
313+ } // end sse_validate_export_request()
319314
320315
321316/**
@@ -335,8 +330,7 @@ function sse_prepare_execution_environment() {
335330 }
336331
337332 sse_log ( 'Execution time limit appears adequate for export operations ' , 'info ' );
338-
339- } //end sse_prepare_execution_environment()
333+ } // end sse_prepare_execution_environment()
340334
341335/**
342336 * Sets up export directories and returns path information.
@@ -352,7 +346,7 @@ function sse_setup_export_directories() {
352346 $ export_dir_name = 'simple-wp-site-exporter-exports ' ;
353347 $ export_dir = trailingslashit ( $ upload_dir ['basedir ' ] ) . $ export_dir_name ;
354348 $ export_url = trailingslashit ( $ upload_dir ['baseurl ' ] ) . $ export_dir_name ;
355-
349+
356350 wp_mkdir_p ( $ export_dir );
357351 sse_create_index_file ( $ export_dir );
358352
@@ -382,7 +376,7 @@ function sse_create_index_file( $export_dir ) {
382376 return ;
383377 }
384378 }
385-
379+
386380 if ( $ wp_filesystem && $ wp_filesystem ->is_writable ( $ export_dir ) ) {
387381 $ wp_filesystem ->put_contents (
388382 $ index_file_path ,
@@ -391,7 +385,7 @@ function sse_create_index_file( $export_dir ) {
391385 );
392386 return ;
393387 }
394-
388+
395389 sse_log ('Failed to write index.php file or directory not writable: ' . $ export_dir , 'error ' );
396390}
397391
@@ -423,10 +417,10 @@ function sse_export_database( $export_dir ) {
423417 escapeshellarg ($ db_filepath ), // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.escapeshellarg_escapeshellarg -- Required for shell command security
424418 escapeshellarg (ABSPATH ) // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.escapeshellarg_escapeshellarg -- Required for shell command security
425419 );
426-
420+
427421 $ output = shell_exec ($ command . ' 2>&1 ' ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.system_calls_shell_exec -- Required for WP-CLI database export: all parameters are validated and escaped with escapeshellarg()
428-
429-
422+
423+
430424 if ( ! file_exists ( $ db_filepath ) || filesize ( $ db_filepath ) <= 0 ) { // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.file_exists_file_exists -- Validating WP-CLI export success
431425 $ error_message = ! empty ($ output ) ? trim ($ output ) : 'WP-CLI command failed silently. ' ;
432426 return new WP_Error ( 'db_export_failed ' , $ error_message );
@@ -458,12 +452,15 @@ function sse_create_site_archive( $export_paths, $database_file ) {
458452 $ zip_filepath = trailingslashit ( $ export_paths ['export_dir ' ] ) . $ zip_filename ;
459453
460454 $ zip = new ZipArchive ();
461- if ( $ zip ->open ( $ zip_filepath , ZipArchive::CREATE | ZipArchive::OVERWRITE ) !== TRUE ) {
462- return new WP_Error ( 'zip_create_failed ' , sprintf (
463- /* translators: %s: filename */
464- __ ( 'Could not create zip file at %s ' , 'Simple-WP-Site-Exporter ' ),
465- basename ($ zip_filepath ) // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.system_calls_basename -- Safe usage: $zip_filepath is constructed from controlled inputs (WordPress upload dir + sanitized filename), not user input
466- ) );
455+ if ( $ zip ->open ( $ zip_filepath , ZipArchive::CREATE | ZipArchive::OVERWRITE ) !== true ) {
456+ return new WP_Error (
457+ 'zip_create_failed ' ,
458+ sprintf (
459+ /* translators: %s: filename */
460+ __ ( 'Could not create zip file at %s ' , 'Simple-WP-Site-Exporter ' ),
461+ basename ( $ zip_filepath ) // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.system_calls_basename -- Safe usage: $zip_filepath is constructed from controlled inputs (WordPress upload dir + sanitized filename), not user input.
462+ )
463+ );
467464 }
468465
469466 // Add database dump to zip
@@ -479,7 +476,7 @@ function sse_create_site_archive( $export_paths, $database_file ) {
479476 }
480477
481478 $ zip_close_status = $ zip ->close ();
482-
479+
483480 if ( ! $ zip_close_status || ! file_exists ( $ zip_filepath ) ) {
484481 return new WP_Error ( 'zip_finalize_failed ' , __ ( 'Failed to finalize or save the zip archive after processing files. ' , 'Simple-WP-Site-Exporter ' ) );
485482 }
@@ -515,11 +512,14 @@ function sse_add_wordpress_files_to_zip( $zip, $export_dir ) {
515512 sse_process_file_for_zip ( $ zip , $ file_info , $ source_path , $ export_dir );
516513 }
517514 } catch ( Exception $ e ) {
518- return new WP_Error ( 'file_iteration_failed ' , sprintf (
519- /* translators: %s: error message */
520- __ ( 'Error during file processing: %s ' , 'Simple-WP-Site-Exporter ' ),
521- $ e ->getMessage ()
522- ) );
515+ return new WP_Error (
516+ 'file_iteration_failed ' ,
517+ sprintf (
518+ /* translators: %s: error message */
519+ __ ( 'Error during file processing: %s ' , 'Simple-WP-Site-Exporter ' ),
520+ $ e ->getMessage ()
521+ )
522+ );
523523 }
524524
525525 return true ;
@@ -581,21 +581,21 @@ function sse_add_file_to_zip( $zip, $file_info, $file, $pathname, $relative_path
581581 }
582582 return true ;
583583 }
584-
584+
585585 if ( $ file_info ->isFile () ) {
586586 // Use real path (getRealPath() must succeed for security)
587587 if ( false === $ file ) {
588588 sse_log ( "Skipping file with unresolvable real path: " . $ pathname , 'warning ' );
589589 return true ; // Skip this file but continue processing
590590 }
591-
591+
592592 $ file_to_add = $ file ;
593-
593+
594594 if ( ! $ zip ->addFile ( $ file_to_add , $ relative_path ) ) {
595595 sse_log ( "Failed to add file to zip: " . $ relative_path . " (Source: " . $ file_to_add . ") " , 'error ' );
596596 }
597597 }
598-
598+
599599 return true ;
600600}
601601
@@ -656,17 +656,17 @@ function sse_show_success_notice( $zip_result ) {
656656 ),
657657 admin_url ()
658658 );
659-
659+
660660 $ delete_url = add_query_arg (
661661 array (
662662 'sse_delete_export ' => $ zip_result ['filename ' ],
663663 'sse_delete_nonce ' => wp_create_nonce ('sse_delete_export ' )
664664 ),
665665 admin_url ()
666666 );
667-
667+
668668 $ display_zip_path = str_replace ( ABSPATH , '[wp-root]/ ' , $ zip_result ['filepath ' ] );
669- $ display_zip_path = preg_replace ('|/+| ' , '/ ' , $ display_zip_path );
669+ $ display_zip_path = preg_replace ( '|/+| ' , '/ ' , $ display_zip_path );
670670 ?>
671671 <div class="notice notice-success is-dismissible">
672672 <p>
@@ -681,11 +681,12 @@ function sse_show_success_notice( $zip_result ) {
681681 <p><small><?php
682682 printf (
683683 /* translators: %s: file path */
684- esc_html__ ('File location: %s ' , 'Simple-WP-Site-Exporter ' ),
685- '<code title=" ' . esc_attr__ ('Path is relative to WordPress root directory ' , 'Simple-WP-Site-Exporter ' ) . '"> ' .
686- esc_html ($ display_zip_path ) . '</code> '
684+ esc_html__ ( 'File location: %s ' , 'Simple-WP-Site-Exporter ' ),
685+ '<code title=" ' . esc_attr__ ( 'Path is relative to WordPress root directory ' , 'Simple-WP-Site-Exporter ' ) . '"> ' .
686+ esc_html ( $ display_zip_path ) . '</code> '
687687 );
688- ?> </small></p>
688+ ?>
689+ </small></p>
689690 </div>
690691 <?php
691692 });
@@ -722,14 +723,14 @@ function sse_schedule_export_cleanup( $zip_filepath ) {
722723function sse_delete_export_file_handler ( $ file ) {
723724 // Validate that this is actually an export file before deletion
724725 $ filename = basename ( $ file );
725-
726+
726727 // Use the same validation as manual deletion for consistency
727728 $ validation = sse_validate_export_file_for_deletion ( $ filename );
728729 if ( is_wp_error ( $ validation ) ) {
729730 sse_log ( 'Scheduled deletion blocked - invalid file: ' . $ file . ' - ' . $ validation ->get_error_message (), 'warning ' );
730731 return ;
731732 }
732-
733+
733734 if ( file_exists ( $ file ) ) {
734735 if ( sse_safely_delete_file ( $ file ) ) {
735736 sse_log ( 'Scheduled deletion successful: ' . $ file , 'info ' );
@@ -748,7 +749,7 @@ function sse_delete_export_file_handler( $file ) {
748749 */
749750function sse_safely_delete_file ( $ filepath ) {
750751 global $ wp_filesystem ;
751-
752+
752753 // Initialize the WordPress filesystem
753754 if (empty ($ wp_filesystem )) {
754755 require_once ABSPATH . 'wp-admin/includes/file.php ' ;
@@ -757,18 +758,18 @@ function sse_safely_delete_file( $filepath ) {
757758 return false ;
758759 }
759760 }
760-
761+
761762 if ( ! $ wp_filesystem ) {
762763 sse_log ('WordPress filesystem API not available ' , 'error ' );
763764 return false ;
764765 }
765-
766+
766767 // Check if the file exists using WP Filesystem
767768 if ($ wp_filesystem ->exists ($ filepath )) {
768769 // Delete the file using WordPress Filesystem API
769770 return $ wp_filesystem ->delete ($ filepath , false , 'f ' );
770771 }
771-
772+
772773 return false ;
773774}
774775
@@ -779,8 +780,8 @@ function sse_safely_delete_file( $filepath ) {
779780 * @return bool True if path is safe, false if contains traversal patterns.
780781 */
781782function sse_check_path_traversal ( $ normalized_file_path ) {
782- // Block obvious directory traversal attempts
783- if ( strpos ( $ normalized_file_path , '.. ' ) !== false ||
783+ // Block obvious directory traversal attempts.
784+ if ( strpos ( $ normalized_file_path , '.. ' ) !== false ||
784785 strpos ( $ normalized_file_path , '/./ ' ) !== false ||
785786 strpos ( $ normalized_file_path , '\\' ) !== false ) {
786787 return false ;
0 commit comments