?AlkantarClanX12
| Current Path : /home/rankinh/mariage-reunion-refonte/wp-content/plugins/backwpup/src/Backups/API/ |
| Current File : /home/rankinh/mariage-reunion-refonte/wp-content/plugins/backwpup/src/Backups/API/Rest.php |
<?php
namespace WPMedia\BackWPup\Backups\API;
use WPMedia\BackWPup\Adapters\BackWPupAdapter;
use WPMedia\BackWPup\Adapters\OptionAdapter;
use WPMedia\BackWPup\Adapters\JobAdapter;
use WPMedia\BackWPup\Adapters\FileAdapter;
use WPMedia\BackWPup\Adapters\JobTypesAdapter;
use WPMedia\BackWPup\Backup\Database as BackupDatabase;
use WP_HTTP_Response;
use WP_REST_Request;
use WP_REST_Response;
use Exception;
use WPMedia\BackWPup\API\Rest as RestInterface;
class Rest implements RestInterface {
/**
* BackWPupAdapter instance.
*
* @var BackWPupAdapter
*/
private $backwpup_adapter;
/**
* Option adapter instance.
*
* @var OptionAdapter
*/
private $option_adapter;
/**
* JobAdapter instance.
*
* @var JobAdapter
*/
private $job_adapter;
/**
* FileAdapter instance.
*
* @var FileAdapter
*/
private $file_adapter;
/**
* JobTypesAdapter instance.
*
* @var JobTypesAdapter
*/
private $job_types_adapter;
/**
* Backup database instance.
*
* @var BackupDatabase
*/
private $backup_database;
/**
* Constructor.
*
* @param BackWPupAdapter $backups_adapter
* @param OptionAdapter $option_adapter
* @param JobAdapter $job_adapter
* @param FileAdapter $file_adapter
* @param JobTypesAdapter $job_types_adapter
* @param BackupDatabase $backup_database
*/
public function __construct(
BackWPupAdapter $backups_adapter,
OptionAdapter $option_adapter,
JobAdapter $job_adapter,
FileAdapter $file_adapter,
JobTypesAdapter $job_types_adapter,
BackupDatabase $backup_database
) {
$this->backwpup_adapter = $backups_adapter;
$this->option_adapter = $option_adapter;
$this->job_adapter = $job_adapter;
$this->file_adapter = $file_adapter;
$this->job_types_adapter = $job_types_adapter;
$this->backup_database = $backup_database;
}
/**
* Checks if the current user has the necessary permissions to perform the action.
*
* @return bool True if the user has permission, false otherwise.
*/
public function has_permission(): bool {
return current_user_can( 'backwpup' );
}
/**
* Registers the REST API routes for the Backups API.
*
* This method is responsible for defining the endpoints and their
* corresponding callbacks for the Backups API within the BackWPup Pro plugin.
*
* @return void
*/
public function register_routes(): void {
register_rest_route(
'backwpup/v1',
'/job-abort-status',
[
'methods' => 'GET',
'callback' => [ $this, 'get_job_abort_status' ],
'permission_callback' => [ $this, 'has_permission' ],
'args' => [
'job_id' => [
'required' => true,
'validate_callback' => function ( $param ) {
return is_numeric( $param ) && $param > 0;
},
'sanitize_callback' => 'absint',
],
],
]
);
register_rest_route(
'backwpup/v1',
'/startbackup',
[
'methods' => 'POST',
'callback' => [ $this, 'start_backup' ],
'permission_callback' => [ $this, 'has_permission' ],
'args' => [
'job_id' => [
'required' => false,
'validate_callback' => function ( $param ) {
return is_numeric( $param ) && $param > 0;
},
'sanitize_callback' => 'absint',
],
'first_backup' => [
'required' => false,
'validate_callback' => function ( $param ) {
return filter_var( $param, FILTER_VALIDATE_BOOLEAN );
},
'sanitize_callback' => function ( $param ) {
return filter_var( $param, FILTER_VALIDATE_BOOLEAN );
},
],
],
]
);
register_rest_route(
'backwpup/v1',
'/process_bulk_actions',
[
'methods' => 'POST',
'callback' => [ $this, 'process_bulk_actions' ],
'permission_callback' => [ $this, 'has_permission' ],
'args' => [
'action' => [
'required' => true,
'validate_callback' => function ( $param ) {
return in_array( $param, [ 'delete' ], true );
},
'sanitize_callback' => 'sanitize_text_field',
],
'backups' => [
'required' => true,
'validate_callback' => function ( $param ) {
return is_array( $param ) && ! empty( $param );
},
],
],
]
);
}
/**
* Starts the backup process.
*
* This function is triggered by a WP REST API request to initiate a backup.
* If a job ID is provided, it will start the backup for that job. Otherwise, it will start the backup for the file job.
*
* @param WP_REST_Request $request
* @return WP_REST_Response|WP_Error|WP_HTTP_Response The response object on success, or WP_Error on failure.
*/
public function start_backup( WP_REST_Request $request ) {
$params = $request->get_params();
if ( ! empty( $params['first_backup'] ) ) {
if ( false === get_site_transient( 'backwpup_first_backup' ) ) {
set_site_transient( 'backwpup_first_backup', true, HOUR_IN_SECONDS );
} else {
return new WP_REST_Response(
[
'status' => 301,
'url' => network_admin_url( 'admin.php?page=backwpup' ),
],
200,
[ 'Content-Type' => 'text/json' ]
);
}
}
$create_backup_now_job = true;
$jobs = $this->job_adapter->get_jobs();
foreach ( $jobs as $job ) {
if (
isset( $job['backup_now'] ) && true === $job['backup_now']
&& ! empty( $job['activetype'] )
) {
$create_backup_now_job = false;
}
}
$jobid = $params['job_id'] ?? false;
if ( $create_backup_now_job && false === $jobid ) {
$jobid = $this->get_job_id_when_no_available_job();
}
// check temp folder.
$temp_folder_message = $this->file_adapter->check_folder( $this->backwpup_adapter->get_plugin_data( 'TEMP' ), true );
if ( ! empty( $temp_folder_message ) ) {
return new WP_HTTP_Response(
[
'status' => 500,
'message' => $temp_folder_message,
],
200,
[ 'Content-Type' => 'text/json' ]
);
}
// check log folder.
$log_folder = get_site_option( 'backwpup_cfg_logfolder' );
$log_folder = $this->file_adapter->get_absolute_path( $log_folder );
$log_folder_message = $this->file_adapter->check_folder( $log_folder );
if ( ! empty( $log_folder_message ) ) {
return new WP_HTTP_Response(
[
'status' => 500,
'message' => $log_folder_message,
],
200,
[ 'Content-Type' => 'text/json' ]
);
}
// check backup destinations.
$job_types = $this->backwpup_adapter->get_job_types();
$job_data = $this->option_adapter->get_job( $jobid );
$creates_file = false;
foreach ( $job_types as $id => $job_type_class ) {
if ( in_array( $id, $job_data['type'], true ) && $job_type_class->creates_file() ) {
$creates_file = true;
break;
}
}
if ( $creates_file ) {
$destinations = 0;
foreach ( $job_data['destinations'] as $dest_key ) {
$dest = $this->backwpup_adapter->get_destination( $dest_key );
if ( ! $dest || ! $dest->can_run( $job_data, false ) ) {
continue;
}
++$destinations;
}
if ( ! $destinations ) {
return new WP_HTTP_Response(
[
'status' => 500,
// translators: %s: Job name.
'message' => sprintf( __( 'No backup destination available for "%s" or properly configured!', 'backwpup' ), esc_attr( $job_data['name'] ) ),
],
200,
[ 'Content-Type' => 'text/json' ]
);
}
}
// check if wp cron is working.
$current_time = time() - HOUR_IN_SECONDS;
$next_cron_times = array_keys( _get_cron_array() );
sort( $next_cron_times );
if ( isset( $next_cron_times[0] ) && $next_cron_times[0] < $current_time ) {
\BackWPup_Admin::message( __( 'WP-Cron does not seem to be working properly, because it has not run in the last hour. Please check your server settings.', 'backwpup' ), true );
}
$this->job_adapter->get_jobrun_url( 'runnow', $jobid );
sleep( 1 ); // Wait for the job to start.
$start_file_name = trailingslashit( $this->backwpup_adapter->get_plugin_data( 'TEMP' ) ) . '.backwpup_job_started';
if ( file_exists( $start_file_name ) ) {
wp_delete_file( $start_file_name );
} else {
return new WP_HTTP_Response(
[
'status' => 500,
'message' => __( 'The backup process could not be started! Please try to contact our support.', 'backwpup' ),
],
200,
[ 'Content-Type' => 'text/json' ]
);
}
if ( file_exists( $this->backwpup_adapter->get_plugin_data( 'running_file' ) ) ) {
return new WP_HTTP_Response(
[
'status' => 200,
// translators: %s: Job name.
'message' => sprintf( __( 'Job "%s" started.', 'backwpup' ), esc_attr( $job_data['name'] ) ),
],
200,
[ 'Content-Type' => 'text/json' ]
);
}
return new WP_HTTP_Response(
[
'status' => 201,
// translators: %s: Job name.
'message' => sprintf( __( 'Job "%s" executed.', 'backwpup' ), esc_attr( $job_data['name'] ) ),
],
200,
[ 'Content-Type' => 'text/json' ]
);
}
/**
* Start backup process for when jobs are deleted
*
* @return int
*/
private function get_job_id_when_no_available_job(): int {
$job_id = get_site_option( 'backwpup_backup_now_job_id', 0 );
if ( $job_id < 1 ) {
$job_id = $this->option_adapter->next_job_id();
update_site_option( 'backwpup_backup_now_job_id', $job_id );
$this->option_adapter->update( $job_id, 'type', $this->job_types_adapter->get_type_job_both() );
}
$this->backup_now_default_values( $job_id, true );
return $job_id;
}
/**
* Generate backup default values
*
* @param int $next_job_id The next job id.
* @param bool $backup_now Check if it's a temp job or back up now.
*
* @return void
*/
private function backup_now_default_values( int $next_job_id, bool $backup_now = false ): void {
$backup_now_job_values = [
'jobid' => $next_job_id,
'name' => 'Backup Now',
'destinations' => [ 'FOLDER' ],
'activetype' => '',
'backupsyncnodelete' => false,
'tempjob' => true,
];
if ( $backup_now ) {
$backup_now_job_values['tempjob'] = false;
$backup_now_job_values['backup_now'] = true;
}
$default_values = $this->option_adapter->defaults_job();
$default_destination_folder_values = $this->backwpup_adapter->get_destination( 'FOLDER' )->option_defaults();
$bwp_job_values = array_merge( $default_values, $default_destination_folder_values, $backup_now_job_values );
foreach ( $bwp_job_values as $key => $value ) {
$this->option_adapter->update( $next_job_id, $key, $value );
}
}
/**
* Process bulk actions.
*
* @param WP_REST_Request $request The REST request object containing the parameters.
*
* @return WP_REST_Response The response object containing the status and message.
*
* @throws Exception If an error occurs during the process.
*/
public function process_bulk_actions( WP_REST_Request $request ): \WP_REST_Response {
$params = $request->get_params();
$response = [
'success' => [],
'errors' => [],
];
$action = $params['action'];
switch ( $action ) {
case 'delete':
$backups = $params['backups'];
foreach ( $backups as $backup ) {
try {
$backup_id = 0;
if ( ! empty( $backup['dataset'] ) && is_array( $backup['dataset'] ) && ! empty( $backup['dataset']['backup_id'] ) ) {
$backup_id = (int) $backup['dataset']['backup_id'];
}
if ( $backup_id > 0 ) {
$this->delete_failed_backup_entry( $backup_id );
} else {
$this->delete_backup( $backup );
}
$response['success'][] = $backup;
} catch ( Exception $e ) {
$response['errors'][] = [
'backup' => $backup,
'error' => $e->getMessage(),
];
}
}
$response['message'] = __( 'Bulk action processed.', 'backwpup' );
break;
}
return rest_ensure_response( $response );
}
/**
* Delete a single backup.
*
* @param array $backup The backup data.
*
* @throws Exception If an error occurs during deletion.
*/
private function delete_backup( $backup ) {
if ( empty( $backup['dataset']['data-url'] ) ) {
throw new Exception( __( 'Invalid backup data.', 'backwpup' ) ); // @phpcs:ignore
}
// Parse the dataset URL.
$query = wp_parse_url( $backup['dataset']['data-url'], PHP_URL_QUERY );
parse_str( $query, $params );
// Validate required params.
if ( empty( $params['jobdest-top'] ) || empty( $params['backupfiles'] ) ) {
throw new Exception( __( 'Missing required parameters.', 'backwpup' ) ); // @phpcs:ignore
}
$jobdest = sanitize_text_field( $params['jobdest-top'] );
$file = esc_attr( $params['backupfiles'][0] ); // Handle array format.
[$jobid, $dest] = explode( '_', $jobdest );
$dest_class = $this->backwpup_adapter->get_destination( $dest );
if ( ! $dest_class ) {
throw new Exception( __( 'Invalid destination class.', 'backwpup' ) ); // @phpcs:ignore
}
// Perform the deletion.
$dest_class->file_delete( $jobdest, $file );
/**
* Fires after deleting backups.
*
* @param array $params['backupfiles'] Backup file deleted.
* @param string $dest Destination.
*/
do_action( 'backwpup_after_delete_backups', $params['backupfiles'], $dest );
}
/**
* Get the abort status of a running or recently-aborted backup job.
*
* Polls the database to determine whether the background PHP process has
* finished its cleanup after the user triggered an abort. The JS abort
* handler calls this endpoint repeatedly until `is_done` is true before
* redirecting or reloading the page, ensuring the DB row status is already
* updated from `created` to `aborted` when the history table is rendered.
*
* @param WP_REST_Request $request The REST request containing `job_id`.
*
* @return WP_REST_Response Response with `is_done` boolean flag.
*/
public function get_job_abort_status( WP_REST_Request $request ): WP_REST_Response {
$job_id = (int) $request->get_param( 'job_id' );
return rest_ensure_response( [ 'is_done' => $this->backup_database->is_abort_complete( $job_id ) ] );
}
/**
* Delete a failed backup entry from the database.
*
* @param int $backup_id Backup entry ID.
*
* @throws Exception When deletion fails.
*/
private function delete_failed_backup_entry( int $backup_id ): void {
if ( $backup_id <= 0 ) {
throw new Exception( __( 'Invalid failed backup entry.', 'backwpup' ) ); // @phpcs:ignore
}
$row = $this->backup_database->get_backup_row_by_id( $backup_id );
if ( ! $this->backup_database->delete_failed_backup( $backup_id ) ) {
throw new Exception( __( 'Failed to delete backup entry.', 'backwpup' ) ); // @phpcs:ignore
}
// For aborted backups, physically delete any partial file from the destination
// so it cannot re-appear as a "completed" backup after the DB row is removed.
if (
$row
&& 'aborted' === ( $row->status ?? '' )
&& ! empty( $row->filename )
&& ! empty( $row->destination )
) {
$destination = (string) $row->destination;
$filename = (string) $row->filename;
$job_id = isset( $row->job_id ) ? (int) $row->job_id : 0;
$dest_class = $this->backwpup_adapter->get_destination( $destination );
if ( $dest_class && $job_id > 0 ) {
$jobdest = $job_id . '_' . $destination;
$file_path = $this->find_remote_file_path( $dest_class, $jobdest, $filename );
if ( '' !== $file_path ) {
try {
$dest_class->file_delete( $jobdest, $file_path );
} catch ( \Throwable $e ) {
// Remote file deletion is best-effort; the DB row is already removed.
// Log so operators can identify orphaned partial files.
error_log( sprintf( '[BackWPup] Failed to delete partial aborted file "%s": %s', $filename, $e->getMessage() ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
}
}
}
}
}
/**
* Find the destination-specific file path or ID for a given base filename.
*
* Different destinations store files with different keys in their file lists:
* FTP stores full remote paths, Google Drive stores file IDs, Azure stores blob names.
* This method looks up the entry matching the base filename and returns the value
* expected by file_delete(). If the file is not found (e.g. early abort with no
* upload), an empty string is returned so deletion can be skipped entirely, avoiding
* spurious admin error notices.
*
* @param \BackWPup_Destinations $dest_class Destination handler instance.
* @param string $jobdest Job-destination key (e.g. '123_FTP').
* @param string $filename Base archive filename to look for.
*
* @return string The file path or ID to pass to file_delete(), or '' if not found.
*/
private function find_remote_file_path( $dest_class, string $jobdest, string $filename ): string {
$file_list = $dest_class->file_get_list( $jobdest );
foreach ( $file_list as $file ) {
if ( ! is_array( $file ) ) {
continue;
}
$file_key = (string) ( $file['file'] ?? '' );
$file_name = (string) ( $file['filename'] ?? basename( $file_key ) );
if ( $file_name === $filename || $file_key === $filename ) {
return $file_key;
}
}
return '';
}
}