Orderadmin/css/boldgrid-backup-premium-admin-historical.css000064400000000603147600362700017074 0ustar00.dashicons.dashicons-archive { color: #c4963d; } .dashicons.dashicons-media-default { color: #0073aa; } .dashicons.dashicons-admin-page { color: #0073aa; } /* Fix the padding for the "#" heading. */ table.widefat thead td.check-column { padding: 8px 10px; } /* Add a border between each of the version #'s found. */ tr.top:not(:first-of-type) td { border-top: 1px solid #ddd; }admin/js/boldgrid-backup-premium-admin-zip-browser.js000064400000002471147600362700016673 0ustar00/** * Browser. * * @summary JS for all admin backup pages. * * @since 1.5.3 */ /* global ajaxurl,jQuery,boldgrid_backup_premium_zip_browser */ var BoldGrid = BoldGrid || {}; BoldGrid.PremiumZipBrowser = function( $ ) { var self = this; self.onClickRestore = function() { var $a = $( this ), $tr = $a.closest( 'tr' ), $fileTr = $tr.prev(), data = { action: 'boldgrid_backup_restore_single_file', filename: $( '#filename' ).val(), // Total Upkeep provides a general nonce for usage on the Archive Details page. security: $( '#bgbkup_archive_details_nonce' ).val(), file: $fileTr.attr( 'data-dir' ) }, $restoring = $( ' ' + boldgrid_backup_premium_zip_browser.restoring + '...' ); $a.after( $restoring ).remove(); $.post( ajaxurl, data, function( response ) { if ( response.data !== undefined ) { $restoring.html( response.data ); } else { $restoring.html( boldgrid_backup_premium_zip_browser.unknownError ); } } ).error( function() { $restoring.html( boldgrid_backup_premium_zip_browser.unknownError ); } ); }; /** * Init. */ $( function() { $( 'body' ).on( 'click', '.file-actions a.restore', self.onClickRestore ); } ); }; BoldGrid.PremiumZipBrowser( jQuery ); admin/js/boldgrid-backup-premium-admin-timely-updates.js000064400000003175147600362700017360 0ustar00/** * File: boldgrid-backup-premium-admin-timely-updates.js * * @summary JS for the admin themes page. * * @since 1.4.0 */ /* global ajaxurl,jQuery,settingsData*/ var BoldGrid = BoldGrid || {}; /** * Class: TimelyUpdates. * * @since 1.4.0 */ class TimelyUpdates { /** * Constructor * * @since 1.4.0 */ constructor() { $ = jQuery; this.themes = window.BgbckTheme || {}; $(document).on('ready', this._onReady()); } /** * _onReady * * @since 1.4.0 */ _onReady() { var themeSlugs = []; for (var themeSlug in this.themes) { themeSlugs.push(themeSlug); } themeSlugs.forEach(function(themeSlug, index) { var updateMessage = this.themes[themeSlug].message; var contents = $(updateMessage); this.prependDiv(themeSlug, contents); }, this); } /** * Prepend Div with provided contents. * * @since 1.4.0 * * @param {string} themeSlug Theme Slug. * @param {string} contents Contents of update message. */ async prependDiv(themeSlug, contents) { var totalCheckTime = 0, checkExist = setInterval(function() { /* * Every 250ms we will check for the themes to have been rendered. For performance * reasons, we will stop checking after 5 seconds. */ totalCheckTime += 250; if (totalCheckTime > 5000) { clearInterval(checkExist); } // When the themes have been rendered, add our upgrade notice. if ($('.theme-browser.rendered').length) { $(".theme[aria-describedby*='" + themeSlug + "'] .update-message p").after(contents); clearInterval(checkExist); } }, 250); // check every 250ms } } BoldGrid.TimelyUpdates = new TimelyUpdates(); admin/js/boldgrid-backup-premium-admin-settings.js000064400000006757147600362700016263 0ustar00/** * File: boldgrid-backup-premium-admin-settings.js * * @summary JS for the admin settings page. * * @since 1.3.0 */ /* global ajaxurl,jQuery,settingsData */ var BoldGrid = BoldGrid || {}; BoldGrid.BGBPSettings = function( $ ) { var self = this; /** * Add settings markup to the Backup Security section. * * @since 1.3.0 */ self.addSettingsMarkup = function() { var markup = '' + settingsData.lang.encryptionToken + ' ' + '

' + settingsData.lang.addTokenText + '

'; $( 'div#section_security' ) .find( '.form-table tr:last' ) .after( markup ); self.toggleToken( $( 'input[name="encrypt_db"]:checked' ) ); $( 'p.help[data-id="backup_security"]' ).append( settingsData.lang.addHelpText ); }; /** * Toggle the disable attribute for the encyption token input field. * * @since 1.3.0 */ self.toggleToken = function( $this ) { var $tokenInputs = $( 'input#crypt-token' ) .parent() .children(); $tokenInputs.prop( 'disabled', '0' === $this.val() ); }; /** * Reveal the encyption token. * * @since 1.3.0 */ self.revealToken = function() { var $this = $( this ), markup = ' '; $this .parent() .find( 'input#crypt-token' ) .remove(); $this.replaceWith( markup ); new ClipboardJS( '#token-copy-button' ); }; /** * @summary Update the download link copy button after clicking, and then reset after 3 seconds. * * @since 1.3.0 */ self.updateCopyText = function( e ) { var $this = $( this ), oldHtml = $this.html(); e.preventDefault(); $this.prop( 'disabled', true ); $this.html( settingsData.lang.copiedText ); setTimeout( function() { $this.html( oldHtml ); $this.prop( 'disabled', false ); }, 3000 ); }; /** * Delete the encyption token. * * @since 1.3.0 */ self.deleteToken = function( e ) { var confirmResponse, $this = $( this ), $parent = $this.parent(); confirmResponse = confirm( settingsData.lang.deleteConfirmText ); if ( ! confirmResponse ) { return false; } e.preventDefault(); $parent.find( 'input[name=delete_crypt_token]' ).val( '1' ); $parent.find( '#crypt-token-reveal' ).remove(); $parent.find( '#crypt-token' ).remove(); $parent.find( '#token-copy-button' ).remove(); $this.html( 'Click "Save Changes" to complete deletion' ).prop( 'disabled', true ); }; /** * Init. * * @since 1.3.0 */ $( function() { self.addSettingsMarkup(); $body = $( 'body' ); $body.on( 'click', 'input[name="encrypt_db"]:checked', function() { self.toggleToken( $( this ) ); } ); $body.on( 'click', 'button#crypt-token-reveal', self.revealToken ); $body.on( 'click', 'button#token-copy-button', self.updateCopyText ); $body.on( 'click', 'button#delete-crypt-token', self.deleteToken ); } ); }; BoldGrid.BGBPSettings( jQuery ); admin/js/boldgrid-backup-premium-admin-plugin-editor.js000064400000005446147600362700017177 0ustar00/** * Plugin Editor. * * @summary JS for all admin plugin editor. * * @since 1.5.3 */ /* global ajaxurl,jQuery,boldgrid_backup_premium_admin_plugin_editor */ var BoldGrid = BoldGrid || {}; BoldGrid.PluginEditor = function( $ ) { var self = this, $spinner, filepath, relFilepath, $editorNotices, noticeSuccess = '
', noticeError = '
', closeButton = ''; /** * @summary Load our content into the page. * * @since 1.5.3 */ self.loadContent = function() { var link = 'admin.php?page=boldgrid-backup-historical&file=' + relFilepath; $spinner .before( ' ' ) .before( ' ' + boldgrid_backup_premium_admin_plugin_editor.find_a_version + '' ); $editorNotices.after( boldgrid_backup_premium_admin_plugin_editor.help ); }; /** * @summary Take action when the user clicks copy. * * @since 1.5.3 */ self.onClickCopy = function() { var data = { action: 'boldgrid_backup_save_copy', file: relFilepath, pluginFile: filepath, nonce: $( '#nonce' ).val() }, errorCallback, successCallback; errorCallback = function() { $editorNotices.append( noticeError + '

' + boldgrid_backup_premium_admin_plugin_editor.error_saving + '

' + closeButton + '
' ); $spinner.removeClass( 'is-active' ); }; successCallback = function() { $editorNotices.append( noticeSuccess + '

' + boldgrid_backup_premium_admin_plugin_editor.success_saving + '

' + closeButton + '
' ); $spinner.removeClass( 'is-active' ); }; $spinner.addClass( 'is-active' ); $.post( ajaxurl, data, function( response ) { var success = response.success !== undefined && true === response.success; if ( success ) { successCallback(); } else { errorCallback(); } } ).error( errorCallback ); return false; }; /** * Init. * * @since 1.5.3 */ $( function() { $spinner = $( '.spinner' ); filepath = $( 'input[name="file"]' ).val(); relFilepath = boldgrid_backup_premium_admin_plugin_editor.rel_plugin_path + filepath; $editorNotices = $( '.editor-notices' ); self.loadContent(); $( 'body' ).on( 'click', '.save-copy', self.onClickCopy ); $( 'body' ).on( 'click', '.boldgrid-backup.notice-dismiss', function() { $( this ) .closest( '.notice' ) .slideUp(); } ); } ); }; BoldGrid.PluginEditor( jQuery ); admin/js/boldgrid-backup-premium-admin-historical.js000064400000007470147600362700016555 0ustar00/** * Historical. * * @summary JS for all admin historical pages. * * @since 1.5.3 */ /* global ajaxurl,jQuery,boldgrid_backup_premium_admin_historical */ var BoldGrid = BoldGrid || {}; BoldGrid.Historical = function( $ ) { var self = this, reloadingTable = '
' + boldgrid_backup_premium_admin_historical.reloading_table + '...', iconWarning = boldgrid_backup_premium_admin_historical.icon_warning + ''; /** * @summary Ajax and load the table of file versions. * * @since 1.5.3 */ self.loadVersions = function() { var data = { action: 'boldgrid_backup_get_historical_versions', security: $( '#bgbkup_historical_version_nonce' ).val(), file: $( '[name="file"]' ).val() }, $table = $( '#versions_container' ); $.post( ajaxurl, data, function( response ) { if ( response.data !== undefined ) { $table.html( response.data ); } else { $table.html( iconWarning + boldgrid_backup_premium_admin_historical.unknown_error_load ); } } ).error( function() { $table.html( iconWarning + boldgrid_backup_premium_admin_historical.unknown_error_load ); } ); }; /** * @summary Action to take when restoring from a zip. * * @since 1.5.3 */ self.onClickRestore = function() { var $a = $( this ), $tr = $a.closest( 'tr' ), data = { action: 'boldgrid_backup_restore_single_file', filename: $tr.attr( 'data-filename' ), security: $( '#bgbkup_historical_version_nonce' ).val(), file: $( '[name="file"]' ).val() }, $restoring = $( ' ' + boldgrid_backup_premium_admin_historical.restoring + '...' ); $a.parent( 'div' ) .removeClass( 'row-actions' ) .end() .after( $restoring ) .remove(); $.post( ajaxurl, data, function( response ) { if ( response.data !== undefined ) { $restoring.html( response.data + reloadingTable ); self.loadVersions(); } else { $restoring.html( iconWarning + boldgrid_backup_premium_admin_historical.unknown_error_restore ); } } ).error( function() { $restoring.html( iconWarning + boldgrid_backup_premium_admin_historical.unknown_error_restore ); } ); return false; }; /** * @summary Action to take when user clicks to restore a historic version. * * A historical version is a single copy of the file. It can be generated, for example, when the * user is in the Plugin Editor and clicks the, "Save a copy before updating" button (added by * Total Upkeep Premium). * * @since 1.5.3 */ self.onClickRestoreHistorical = function() { var $a = $( this ), fileVersion = $a.attr( 'data-file-version' ), data = { action: 'boldgrid_backup_restore_historical', file_version: fileVersion, security: $( '#bgbkup_historical_version_nonce' ).val(), file: $( '[name="file"]' ).val() }, $restoring = $( ' ' + boldgrid_backup_premium_admin_historical.restoring + '...' ); $a.parent( 'div' ) .removeClass( 'row-actions' ) .end() .after( $restoring ) .remove(); $.post( ajaxurl, data, function( response ) { if ( response.data !== undefined ) { $restoring.html( response.data + reloadingTable ); self.loadVersions(); } else { $restoring.html( iconWarning + boldgrid_backup_premium_admin_historical.unknown_error_restore ); } } ).error( function() { $restoring.html( iconWarning + boldgrid_backup_premium_admin_historical.unknown_error_restore ); } ); return false; }; /** * Init. */ $( function() { $( 'body' ).on( 'click', 'a.restore', self.onClickRestore ); $( 'body' ).on( 'click', 'a.restore-historical', self.onClickRestoreHistorical ); self.loadVersions(); } ); }; BoldGrid.Historical( jQuery ); admin/js/boldgrid-backup-premium-admin-amazon-s3.js000064400000001705147600362700016217 0ustar00/** * Amazon S3. * * @summary JS for Amazon S3. * * @since 1.5.4 */ /* global ajaxurl,jQuery,boldgrid_backup_premium_admin_amazon_s3 */ var BoldGrid = BoldGrid || {}; BoldGrid.AmazonS3 = function( $ ) { var self = this; /** * @summary Download an Amazon S3 file. * * @since 1.5.4 */ self.download = function() { var $a = $( this ), data = { nonce: $a.attr( 'data-nonce' ), key: $a.attr( 'data-key' ), action: 'boldgrid_backup_amazon_s3_download' }, spinner = ''; $a.after( spinner + ' ' + boldgrid_backup_premium_admin_amazon_s3.downloading + '...' ).remove(); $.post( ajaxurl, data, function( response ) { location.reload(); } ).error( function() { location.reload(); } ); return false; }; /** * @summary Init. * * @since 1.5.4 */ $( function() { $( 'body' ).on( 'click', '.amazon-s3-download', self.download ); } ); }; BoldGrid.AmazonS3( jQuery ); admin/partials/archives/backup_tr.php000064400000001604147600362700013750 0ustar00 for a backup on the backups page. * * @since 1.5.4 * * @package Boldgrid_Backup * @subpackage Boldgrid_Backup/admin/partials/archives * @copyright BoldGrid * @version $Id$ * @author BoldGrid */ return sprintf( ' %3$s %1$s: %2$s
Download to server ', /* 1 */ __( 'Backup', 'boldgrid-backup' ), /* 2 */ gmdate( 'n/j/Y g:i A', $this->core->utility->time( $backup['Headers']['Metadata']['last_modified'] ) ), /* 3 */ __( 'Amazon S3', 'boldgrid-backup' ), /* 4 */ $backup['Headers']['Metadata']['last_modified'], /* 5 */ esc_attr( $backup['Key'] ), /* 6 */ $nonce ); admin/partials/historical/entry.php000064400000006107147600362700013477 0ustar00 of the "all versions of this file" table. * * @since 1.5.3 * * @package Boldgrid_Backup * @subpackage Boldgrid_Backup/admin/partials/historical * @copyright BoldGrid * @version $Id$ * @author BoldGrid */ // Reset values and force them to be created below. $last_modified_span = ''; $last_modified_utc = ''; $archive_created_span = ''; $this->core->time->init( $version['lastmodunix'] ); $last_modified_span = $this->core->time->get_span( 'M j @ h:i a' ); $last_modified_utc = $this->core->time->utc_time; switch ( $version['type'] ) { case 'current': $type = $this->lang['current_file']; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited $row_actions = ''; break; case 'historical': $type = $this->lang['historical_file']; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited $row_actions = sprintf( '%1$s', __( 'Restore previous version', 'boldgrid-backup' ), $version['name'] ); break; case 'in_archives': $type = $this->lang['archive_file']; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited $row_actions = sprintf( '%1$s | %3$s', __( 'Restore from archive', 'boldgrid-backup' ), $this->core->archive_details->get_url( basename( $version['archive_filepath'] ) ), __( 'View details', 'boldgrid-backup' ) ); $this->core->time->init( $version['created'] ); $archive_created_span = $this->core->time->get_span( 'M j @ h:i a' ); break; default: $type = __( 'Unknown', 'boldgrid-backup' ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited $row_actions = ''; } $version_td = ''; $tr_class = ''; if ( $last_modified_utc !== $last_modified ) { $version_number++; $last_modified = $last_modified_utc; $tr_class = 'top'; $version_td = sprintf( '%1$s.', $version_number ); } return sprintf( ' %6$s %1$s %7$s
%4$s
%2$s %3$s ', // 1. Type. $type, // 2. Last modified -- date( 'M j, Y h:i:s a', $version['lastmodunix'] ). sprintf( '%1$s %3$s (%2$s)', human_time_diff( $last_modified_utc, time() ), $last_modified_span, __( 'ago', 'boldgrid-backup' ) ), // 3. Size. Boldgrid_Backup_Admin_Utility::bytes_to_human( $version['size'] ), // 4. Row actions (restore/etc). $row_actions, // 5. Path to zip file. ! empty( $version['archive_filepath'] ) ? basename( $version['archive_filepath'] ) : '', // 6. The version number. $version_td, // 7. Created. 'in_archives' === $version['type'] ? sprintf( '
%4$s %1$s %3$s (%2$s)', human_time_diff( $version['created'], time() ), $archive_created_span, __( 'ago', 'boldgrid-backup' ), __( 'Archive created', 'boldgrid-backukp' ) ) : '', // 8. Class applied to the tr. $tr_class ); admin/partials/remote/google_drive.php000064400000007375147600362700014145 0ustar00 * * @param string $folder_name * @param int $retention_count * @param string $nickname * * phpcs:disable WordPress.XSS.EscapeOutput.OutputNotEscaped */ defined( 'WPINC' ) || die; // Determine whether "name" or "id" folder type needs to be selected. $id_selected = 'id' === $folder_type ? 'selected' : ''; $name_selected = 'name' === $folder_type ? 'selected' : ''; // Used with wp_kses. $allowed_html = array( 'strong' => array(), 'em' => array(), 'br' => array(), ); ?>

Folder Name, your backups will be stored on Google Drive in:
/Total Upkeep/Folder Name/backup.zip
This folder will be created if it doesn\'t exist.', 'boldgrid-backup' ), $allowed_html ); ?>

Folder Id if you have a specific folder you want to upload to. For example, in your browser go to the Google Drive folder. If the url is https://drive.google.com/drive/u/0/folders/abc123, then your backup id is abc123. If this is a shared folder, your user must have permission to delete the file, otherwise retention / deleting backups will not work.', 'boldgrid-backup' ), $allowed_html ); ?>

admin/partials/remote/dreamobjects.php000064400000005045147600362700014132 0ustar00 * * @uses string $key Access Key ID. * @uses string $secret Secret Access Key. * @uses string $bucket_id Bucket ID. */ // phpcs:disable WordPress.XSS.EscapeOutput.OutputNotEscaped defined( 'WPINC' ) || die; ?>

provider->get_key() ); ?>
admin/partials/remote/amazon-s3.php000064400000004170147600362700013276 0ustar00 * * @param string $key Access Key ID. * @param string $secret Secret Access Key. * @param string $bucket_id Bucket ID. */ // phpcs:disable WordPress.XSS.EscapeOutput.OutputNotEscaped defined( 'WPINC' ) || die; ?>

admin/partials/recent.php000064400000004633147600362700011457 0ustar00 %1$s %2$s %3$s ', /* 1 */ __( 'File', 'boldgrid-backup' ), /* 2 */ __( 'Last Modified', 'boldgrid-backup' ), /* 3 */ __( 'Actions', 'boldgrid-backup' ) ); foreach ( $this->list as $item ) { $relative_path = str_replace( ABSPATH, '', $item['path'] ); $href = 'admin.php?page=boldgrid-backup-historical&file=' . $relative_path; $link = sprintf( // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited '%2$s', $href, __( 'Find versions to restore', 'boldgrid-backup' ) ); $table .= sprintf( ' %1$s %2$s %3$s ', /* 1 */ $relative_path, /* 2 */ sprintf( '%1$s %2$s (%3$s)', human_time_diff( $item['lastmodunix'], time() ), __( 'ago', 'boldgrid-backup' ), gmdate( 'M j @ h:i a', $this->core->utility->time( $item['lastmodunix'] ) ) ), /* 3 */ $link ); } $table .= ''; if ( ! isset( $_GET['mins'] ) ) { // phpcs:ignore $table = ''; } elseif ( empty( $this->list ) ) { $table = '

' . __( 'There are no files last modified within the given time frame. Please try again.', 'boldgrid-backup' ) . '

'; } printf( '

%1$s

%2$s

', __( 'Recently Modified Files', 'boldgrid-backup' ), // phpcs:ignore __( 'Use this tool to find a list of all files modified recently. Enter a number of minutes below and we will find all files modified within that time frame.', 'boldgrid-backup' ) // phpcs:ignore ); printf( '

%4$s:

', /* 1 */ __( 'Search', 'boldgrid-backup' ), /* 2 */ ! empty( $minutes ) ? esc_attr( $minutes ) : '', /* 3 */ '60', /* 4 */ __( 'Minutes', 'boldgrid-backup' ) // phpcs:ignore ); echo $table; // phpcs:ignore $output = ob_get_contents(); ob_end_clean(); return $output; admin/partials/history.php000064400000003536147600362700011701 0ustar00 */ // phpcs:disable Squiz.PHP.NonExecutableCode if ( ! defined( 'WPINC' ) ) { die; } ob_start(); printf( '

%1$s

', __( 'History', 'boldgrid-backup' ) ); // phpcs:ignore echo '

' . sprintf( // translators: 1: Plugin title encapsulated with HTML strong tags. esc_html__( '%1$s keeps a running history of changes to your site (such as plugin updates, backups created, etc). This page shows your history log.', 'boldgrid-backup' ), '' . esc_html( BOLDGRID_BACKUP_PREMIUM_TITLE ) . '' ) . '

'; ?> core->utility->time( $time ); $timestamp = $this->core->utility->time( $item['timestamp'] ); $user = is_numeric( $item['user_id'] ) ? get_userdata( $item['user_id'] ) : null; printf( ' ', gmdate( 'Y-m-d h:i:s a', $timestamp ), human_time_diff( $timestamp, $time ), is_object( $user ) && 'WP_User' === get_class( $user ) ? $user->display_name : $item['user_id'], // phpcs:ignore esc_html( $item['message'] ) ); } ?>
Date User Action
%1$s
%2$s ago
%3$s %4$s
%1$s

', __( 'No history to display.', 'boldgrid-backup' ) ); // phpcs:ignore } ?> */ // phpcs:disable WordPress.XSS.EscapeOutput.OutputNotEscaped $glossary = sprintf( ' ', $this->lang['current_file'], $this->lang['current_file_description'], $this->lang['historical_file'], $this->lang['historical_file_description'], $this->lang['archive_file'], $this->lang['archive_file_description'] ); wp_nonce_field( 'bgbkup_historical_version_page', 'bgbkup_historical_version_nonce' ); ?>


File:

admin/partials/plugin-editor.php000064400000001432147600362700012753 0ustar00 */ return '

' . sprintf( // translators: 1: HTML opening strong tag, 2: HTML closing strong tag, 3: Plugin title. __( 'The %1$s%3$s%2$s plugin offers two additional tools below, %1$sSave a copy before updating%2$s and %1$sFind a version to restore%2$s. If you want to make a backup of this file before saving any changes, click the %1$sSave a copy%2$s button. If you want to find or restore any copies previously saved or included in a backup, click %1$sFind a version%2$s.', 'boldgrid-backup' ), '', '', BOLDGRID_BACKUP_PREMIUM_TITLE ) . '

'; admin/remote/s3_uploader.php000064400000007466147600362700012102 0ustar00 */ use Aws\S3\Model\MultipartUpload\UploadBuilder; /** * S3 Uploader class. * * @since 1.2.0 */ class Boldgrid_Backup_Premium_Admin_Remote_S3_Uploader { /** * An array of error messages. * * @since 1.2.0 * @var array $errors * @access private */ private $errors = array(); /** * Get errors. * * @since 1.2.0 * * @return array */ public function get_errors() { return $this->errors; } /** * Whether or not we have errors. * * @since 1.2.0 * * @return bool */ public function has_error() { return ! empty( $this->errors ); } /** * Upload a backup file. * * @since 1.2.0 * * @param Boldgrid_Backup_Premium_Admin_Remote_S3_Bucket $bucket Our bucket. * @param string $filepath File path. * @return bool */ public function upload( Boldgrid_Backup_Premium_Admin_Remote_S3_Bucket $bucket, $filepath ) { $success = false; $core = apply_filters( 'boldgrid_backup_get_core', null ); $client = $bucket->get_client(); if ( ! $core->wp_filesystem->exists( $filepath ) ) { // Translators: 1: File path. $this->errors[] = sprintf( __( 'Failed to upload, filepath does not exist: %1$s', 'boldgrid-backup' ), $filepath ); return $success; } /* * When files are uploaded to an S3 host S3, the LastModified is the time the file was uploaded, * not the time the file was last modified. When we enforce retention, we'll need to know when * the backup archive was created, not when it was uploaded. */ $archive_data = $core->archive_log->get_by_zip( $filepath ); $last_modified = ! empty( $archive_data['lastmodunix'] ) ? $archive_data['lastmodunix'] : $core->wp_filesystem->mtime( $filepath ); try { $uploader = new \Aws\S3\MultipartUploader( $client->get_client(), fopen( $filepath, 'rb' ), // phpcs:ignore WordPress.WP.AlternativeFunctions array( 'bucket' => $bucket->get_id(), 'key' => basename( $filepath ), /* * Before Initiate. * * Set our custom metadata. * * Originally, our Amazon S3 class used is_boldgrid_backup and last_modified. These * don't work with DreamObjects. Appears only lowercase letters are allowed. * * @link https://docs.aws.amazon.com/aws-sdk-php/v3/api/api-s3-2006-03-01.html#createmultipartupload * @link https://docs.aws.amazon.com/sdk-for-php/v3/developer-guide/s3-multipart-upload.html * * @var $command Aws\Command A CreateMultipartUpload operation. */ 'before_initiate' => function( $command ) use ( $last_modified ) { $command['Metadata'] = array( 'isboldgridbackup' => 'true', 'lastmodified' => $last_modified, ); }, ) ); } catch ( Exception $e ) { $this->errors[] = sprintf( // Translators: 1, the status code (such as 403), 2 the error code (such as "SignatureDoesNotMatch"). __( '%1$s error: %2$s.', 'boldgrid-backup' ), $e->getStatusCode(), $e->getAwsErrorCode() ); return $success; } try { $uploader->upload(); // We've uploaded a new file. Delete this bucket's objects transient. $bucket->get_client()->get_provider()->get_transient()->delete_objects( $bucket->get_id() ); // Upload was a success if our bucket has our backup file. $bucket->set_objects( true ); $success = $bucket->has_object_key( basename( $filepath ) ); } catch ( MultipartUploadException $e ) { $uploader->abort(); $this->errors[] = __( 'Failed to upload.', 'boldgrid-inspirations' ); return $success; } return $success; } } admin/remote/s3_provider.php000064400000011466147600362700012114 0ustar00 */ /** * Generic S3 class. * * @since 1.2.0 */ class Boldgrid_Backup_Premium_Admin_Remote_S3_Provider extends Boldgrid_Backup_Premium_Admin_Remote_Provider { /** * An instance of Boldgrid_Backup_Premium_Admin_Remote_S3_Client. * * @since 1.2.0 * @var Boldgrid_Backup_Premium_Admin_Remote_S3_Client * @access private */ private $client; /** * Our transient class. * * @since 1.2.0 * @var Boldgrid_Backup_Premium_Admin_Remote_S3_Transient * @access private */ private $transient; /** * Our uploader. * * @since 1.2.0 * @var Boldgrid_Backup_Premium_Admin_Remote_S3_Uploader */ private $uploader; /** * An instance of Boldgrid_Backup_Premium_Admin_Remote_S3_Bucket * * @since 1.2.0 * @var Boldgrid_Backup_Premium_Admin_Remote_S3_Client * @access protected */ protected $bucket; /** * Constructor. * * @since 1.2.0 */ public function __construct() { // phpcs:ignore parent::__construct(); } /** * Enforce retention. * * @since 1.2.0 */ public function enforce_retention() { $retention_count = $this->get_setting( 'retention_count' ); $bucket = $this->get_bucket(); $bucket->enforce_retention( $retention_count, $this->title ); } /** * Get our bucket. * * @since 1.2.0 * * @return Boldgrid_Backup_Premium_Admin_Remote_S3_Bucket */ public function get_bucket() { if ( is_null( $this->bucket ) ) { $client = $this->get_client(); $bucket_id = $this->get_setting( 'bucket_id' ); if ( ! empty( $client ) && ! empty( $bucket_id ) ) { $this->bucket = new Boldgrid_Backup_Premium_Admin_Remote_S3_Bucket( $client, $bucket_id ); } } return $this->bucket; } /** * Get our client. * * @since 1.2.0 * * @return Boldgrid_Backup_Premium_Admin_Remote_S3_Client */ public function get_client() { if ( is_null( $this->client ) ) { $settings = $this->remote_settings->get_settings(); // Only try to initialize the client if we have the needed settings. if ( $this->remote_settings->has_setting_keys( array( 'key', 'secret', 'host' ) ) ) { $this->client = new Boldgrid_Backup_Premium_Admin_Remote_S3_Client( array( 'key' => $settings['key'], 'secret' => $settings['secret'], 'endpoint' => $settings['host'], ) ); $this->client->set_provider( $this ); } } return $this->client; } /** * Get details * * @since 1.2.0 * * @param bool $try_cache Whether or not to use last_login to validate the Dreamobjects * account. Please see param definition in $this->is_setup(). * @return array */ public function get_details( $try_cache = false ) { $client = $this->get_client(); $is_setup = ! empty( $client ) && $client->is_valid(); $enabled = $this->get_setting( 'enabled' ); $details = array( 'title' => $this->title, 'key' => $this->key, 'configure' => 'admin.php?page=boldgrid-backup-' . $this->key, 'is_setup' => $is_setup, 'enabled' => $enabled && $is_setup, ); return $details; } /** * Get our transient class. * * @since 1.2.0 * * @return Boldgrid_Backup_Premium_Admin_Remote_S3_Transient */ public function get_transient() { if ( is_null( $this->transient ) ) { $this->transient = new Boldgrid_Backup_Premium_Admin_Remote_S3_Transient( $this ); } return $this->transient; } /** * Get our uploader. * * @since 1.2.0 * * @return Boldgrid_Backup_Premium_Admin_Remote_S3_Uploader */ public function get_uploader() { return $this->uploader; } /** * Determine whether or not this provider has a bucket. * * @since 1.2.1 * * @return bool */ public function has_bucket() { $bucket = $this->get_bucket(); return ! empty( $bucket ); } /** * Determine whether or not this provider has a client. * * @since 1.2.1 * * @return bool */ public function has_client() { $client = $this->get_client(); return ! empty( $client ); } /** * Upload a file. * * @since 1.2.0 * * @param string $filepath Path to file to upload. * @return bool True on success. */ public function upload( $filepath ) { $bucket = $this->get_bucket(); $this->uploader = new Boldgrid_Backup_Premium_Admin_Remote_S3_Uploader(); $success = $this->uploader->upload( $bucket, $filepath ); if ( $success ) { $this->enforce_retention(); /** * File uploaded to remote storage location. * * @since 1.2.0 * * @param string DreamObjects * @param string $filepath */ do_action( 'boldgrid_backup_remote_uploaded', $this->title, $filepath ); } return $success; } } admin/remote/s3_page.php000064400000015266147600362700011200 0ustar00 */ /** * S3_Page class. * * @since 1.2.0 */ class Boldgrid_Backup_Premium_Admin_Remote_S3_Page { /** * Errors. * * @since 1.2.0 * @var array * @access private */ private $errors = array(); /** * The provider this page is for. * * @since 1.2.0 * @var Boldgrid_Backup_Premium_Admin_Remote_S3_Provider * @access private */ private $provider; /** * Constructor. * * @since 1.2.0 * * @param Boldgrid_Backup_Premium_Admin_Remote_S3_Provider $provider */ public function __construct( Boldgrid_Backup_Premium_Admin_Remote_S3_Provider $provider ) { $this->provider = $provider; } /** * Add menu items. * * @since 1.2.0 */ public function add_submenu_page() { $capability = 'administrator'; $title = sprintf( // Translators: 1 the name of the s3 provider, such as DreamObjects. __( '%1$s Settings', 'boldgrid-backup' ), $this->provider->get_title() ); add_submenu_page( 'boldgrid-backup-settings', $title, $title, $capability, 'boldgrid-backup-' . $this->provider->get_key(), array( $this, 'submenu_page', ) ); } /** * Ensure the host has a protocol. * * @since 1.2.0 * * @return string */ public function sanitize_host( $host ) { if ( false === strpos( $host, '://' ) ) { $host = 'https://' . $host; } return stripslashes( $host ); } /** * Whether or not the page was submitted with a valid nonce. * * @since 1.2.0 * * @return bool */ public function is_valid_nonce() { $nonce = ! empty( $_POST['_wpnonce'] ) ? $_POST['_wpnonce'] : null; // phpcs:ignore return wp_verify_nonce( $nonce, 'save-provider-settings_' . $this->provider->get_key() ); } /** * Generate the submenu page for our Provider's Settings page. * * @since 1.2.0 */ public function submenu_page() { wp_enqueue_style( 'boldgrid-backup-admin-hide-all' ); $this->submenu_page_save(); if ( ! empty( $this->errors ) ) { do_action( 'boldgrid_backup_notice', implode( '

', $this->errors ) ); } /* * Determine the values to use in the form. * * If we're posting data, use that, otherwise use the values already in the settings. */ if ( ! empty( $_POST['save'] ) && $this->is_valid_nonce() ) { // phpcs:ignore // phpcs:disable WordPress.Security.NonceVerification.Missing $key = sanitize_text_field( $_POST['key'] ); $secret = sanitize_text_field( $_POST['secret'] ); $bucket_id = sanitize_text_field( $_POST['bucket_id'] ); $retention_count = (int) $_POST['retention_count']; $nickname = sanitize_text_field( $_POST['nickname'] ); $host = esc_url( $this->sanitize_host( $_POST['host'] ) ); // phpcs:enable } else { $settings = $this->provider->get_remote_settings()->get_settings(); $key = ! empty( $settings['key'] ) ? $settings['key'] : null; $secret = ! empty( $settings['secret'] ) ? $settings['secret'] : null; $bucket_id = ! empty( $settings['bucket_id'] ) ? $settings['bucket_id'] : Boldgrid_Backup_Premium_Admin_Remote_S3_Bucket::create_unique_bucket(); $retention_count = ! empty( $settings['retention_count'] ) ? $settings['retention_count'] : $this->provider->get_default_retention(); $nickname = ! empty( $settings['nickname'] ) ? $settings['nickname'] : ''; $host = ! empty( $settings['host'] ) ? $settings['host'] : null; } // @todo Next implementation of a generic s3 provider, may need to rework the settings page. include BOLDGRID_BACKUP_PREMIUM_PATH . '/admin/partials/remote/' . sanitize_file_name( $this->provider->get_key() ) . '.php'; } /** * Process the user's request to update their Provider's settings. * * @since 1.2.0 */ public function submenu_page_save() { if ( ! current_user_can( 'update_plugins' ) ) { return false; } if ( empty( $_POST ) ) { // phpcs:ignore return false; } if ( ! $this->is_valid_nonce() ) { $this->errors[] = __( 'Access denied: invalid nonce.', 'boldgrid-backup' ); return false; } $this->provider->get_transient()->delete_all(); $provider_settings = array(); if ( ! $this->provider->has_settings() ) { $provider_settings = array(); } // If the user has requested to delete all their settings, do that now and return. if ( ! empty( $_POST['delete'] ) ) { // phpcs:ignore $this->provider->delete_settings(); do_action( 'boldgrid_backup_notice', __( 'Settings saved.', 'boldgrid-backup' ), 'notice updated is-dismissible' ); return; } $key = ! empty( $_POST['key'] ) ? sanitize_text_field( $_POST['key'] ) : null; // phpcs:ignore $secret = ! empty( $_POST['secret'] ) ? sanitize_text_field( $_POST['secret'] ) : null; // phpcs:ignore $bucket_id = ! empty( $_POST['bucket_id'] ) ? sanitize_text_field( $_POST['bucket_id'] ) : Boldgrid_Backup_Premium_Admin_Remote_S3_Bucket::create_unique_bucket(); // phpcs:ignore $retention_count = ! empty( $_POST['retention_count'] ) && is_numeric( $_POST['retention_count'] ) ? (int) $_POST['retention_count'] : $this->provider->get_default_retention(); // phpcs:ignore $nickname = ! empty( $_POST['nickname'] ) ? sanitize_text_field( stripslashes( $_POST['nickname'] ) ) : null; // phpcs:ignore $host = ! empty( $_POST['host'] ) ? esc_url( $this->sanitize_host( $_POST['host'] ) ) : null; // phpcs:ignore echo $this->provider->get_core()->elements['long_checking_creds']; // phpcs:ignore if ( ob_get_level() > 0 ) { ob_flush(); } flush(); $client_args = array( 'key' => $key, 'secret' => $secret, 'endpoint' => $host, ); $client = new Boldgrid_Backup_Premium_Admin_Remote_S3_Client( $client_args ); if ( $client->is_valid() ) { $provider_settings = array( 'key' => $key, 'secret' => $secret, 'retention_count' => $retention_count, 'nickname' => $nickname, 'host' => $host, 'bucket_id' => $bucket_id, ); // Create the bucket if it does not already exist. $bucket = new Boldgrid_Backup_Premium_Admin_Remote_S3_Bucket( $client, $bucket_id ); $bucket->maybe_create(); } else { $this->errors[] = __( 'Invalid Access Key Id and / or Secret Access Key.', 'boldgrid-backup' ); } if ( empty( $this->errors ) ) { $this->provider->get_remote_settings()->save_settings( $provider_settings ); do_action( 'boldgrid_backup_notice', __( 'Settings saved.', 'boldgrid-backup' ), 'notice updated is-dismissible' ); } } } admin/remote/s3_hooks.php000064400000022213147600362700011375 0ustar00 */ /** * Class: Boldgrid_Backup_Premium_Admin_Remote_S3_Hooks * * @since 1.2.0 */ class Boldgrid_Backup_Premium_Admin_Remote_S3_Hooks { /** * The provider this page is for. * * @since 1.2.0 * @var Boldgrid_Backup_Premium_Admin_Remote_S3_Provider * @access private */ private $provider; /** * Constructor. * * @since 1.2.0 * * @param Boldgrid_Backup_Premium_Admin_Remote_S3_Provider $provider */ public function __construct( Boldgrid_Backup_Premium_Admin_Remote_S3_Provider $provider ) { $this->provider = $provider; } /** * Upload a backup via an ajax request. * * This is done via the archive details of a single archive. * * @since 1.2.0 */ public function ajax_upload() { $core = $this->provider->get_core(); if ( ! current_user_can( 'update_plugins' ) ) { wp_send_json_error( __( 'Permission denied.', 'boldgrid-backup' ) ); } if ( ! $core->archive_details->validate_nonce() ) { wp_send_json_error( __( 'Invalid nonce.', 'boldgrid-backup' ) ); } // Get our filepath based on filename. $filename = ! empty( $_POST['filename'] ) ? sanitize_file_name( $_POST['filename'] ) : false; // phpcs:ignore $filepath = $core->backup_dir->get_path_to( $filename ); if ( empty( $filename ) || ! $core->wp_filesystem->exists( $filepath ) ) { wp_send_json_error( __( 'Invalid archive filename.', 'boldgrid-backup' ) ); } $success = $this->provider->upload( $filepath ); if ( $success ) { wp_send_json_success(); } else { $error = $this->provider->get_uploader()->has_error() ? implode( '
', $this->provider->get_uploader()->get_errors() ) : esc_html__( 'Unknown error.', 'boldgrid-backup' ); wp_send_json_error( $error ); } } /** * Hook into the filter to add all of our provider's backups to the full list of backups. * * @since 1.2.0 */ public function filter_get_all() { $core = $this->provider->get_core(); $bucket = $this->provider->get_bucket(); if ( empty( $bucket ) ) { return; } $bucket->set_objects(); if ( $bucket->has_errors() ) { $core->notice->boldgrid_backup_notice( implode( '
', $bucket->get_errors() ), 'notice notice-error is-dismissible' ); return; } $bucket->set_backups(); foreach ( $bucket->get_backups() as $object ) { $backup = array( 'filename' => $object['Key'], 'last_modified' => ! empty( $object['Metadata']['lastmodified'] ) ? $object['Metadata']['lastmodified'] : strtotime( $object['LastModified'] ), 'size' => $object['Size'], 'locations' => array( array( 'title' => $this->provider->get_nickname(), 'on_remote_server' => true, 'title_attr' => $this->provider->get_title(), ), ), ); $core->archives_all->add( $backup ); } } /** * Determine if provider is setup. * * @since 1.2.0 */ public function is_setup_ajax() { if ( ! current_user_can( 'update_plugins' ) ) { wp_send_json_error( __( 'Permission denied.', 'boldgrid-backup' ) ); } if ( ! check_ajax_referer( 'boldgrid_backup_settings', 'security', false ) ) { wp_send_json_error( __( 'Invalid nonce.', 'boldgrid-backup' ) ); } // Settings and location needed within the storage-location.php file. $settings = $this->provider->get_core()->settings->get_settings(); $location = $this->provider->get_details(); $tr = include BOLDGRID_BACKUP_PATH . '/admin/partials/settings/storage-location.php'; $client = $this->provider->get_client(); ! empty( $client ) && $client->is_valid() ? wp_send_json_success( $tr ) : wp_send_json_error( $tr ); } /** * Actions to take after a backup file has been generated. * * @since 1.2.0 * * @param array $info An array of info about our backup. */ public function post_archive_files( array $info ) { $core = $this->provider->get_core(); /* * We only want to add this to the jobs queue if we're in the middle of an automatic backup. * If the user simply clicked on "Backup site now", we don't want to automatically send the * backup to Google, there's a button for that. */ if ( ! $core->doing_cron ) { return; } $enabled = $this->provider->get_setting( 'enabled' ); if ( ! $enabled || $info['dryrun'] || ! $info['save'] ) { return; } $args = array( 'filepath' => $info['filepath'], 'action' => 'boldgrid_backup_' . $this->provider->get_key() . '_upload_post_archive', 'action_data' => $info['filepath'], 'action_title' => sprintf( // Translators: 1 the name of our provider, such as DreamObjects. __( 'Upload backup file to %1$s', 'boldgrid-backup' ), $this->provider->get_title() ), ); $core->jobs->add( $args ); } /** * Register our Provider as a storage location. * * When you go to the settings page and see a list of storage providers, each of those storage providers needs to * hook into the "boldgrid_backup_register_storage_location" filter and add themselves. * * @since 1.2.0 * * @param array $storage_locations An array of storage locations. * @return array An updated array of storage locations. */ public function register_storage_location( array $storage_locations ) { $storage_locations[] = $this->provider->get_details(); return $storage_locations; } /** * Add the one click upload from an archive's details page. * * @since 1.2.0 * * @param string $filepath Path to our archive. */ public function single_archive_remote_option( $filepath ) { $filename = basename( $filepath ); // Determine if the remote service is setup. $is_setup = $this->provider->has_client() && $this->provider->get_client()->is_valid(); // Determine if a backup file exists on the remote server. $uploaded = $this->provider->has_bucket() && $this->provider->get_bucket()->has_object_key( $filename ); $storage = array( 'id' => $this->provider->get_key(), 'title' => $this->provider->get_nickname(), 'title_attr' => $this->provider->get_title(), 'uploaded' => $uploaded, 'allow_upload' => $is_setup, 'is_setup' => $is_setup, ); $this->provider->get_core()->archive_details->remote_storage_li[] = $storage; } /** * Upload a file. * * The jobs queue will call this method to upload a file. * * @since 1.2.0 * * @param string $filepath File path. * @return bool */ public function upload_post_archiving( $filepath ) { return $this->provider->upload( $filepath ); } /** * Download a backup file. * * @since 1.2.0 */ public function wp_ajax_download() { $core = $this->provider->get_core(); // translators: 1: A generic error from our provider. $error = __( 'Unable to download backup from %1$s: %2$s', 'bolgrid-bakcup' ); // Validation, user role. if ( ! current_user_can( 'update_plugins' ) ) { $message = sprintf( $error, $this->provider->get_title(), __( 'Permission denied.', 'boldgrid-backup' ) ); $core->notice->add_user_notice( $message, 'notice notice-error' ); wp_send_json_error(); } // Validation, nonce. if ( ! $core->archive_details->validate_nonce() ) { $message = sprintf( $error, $this->provider->get_title(), __( 'Invalid nonce.', 'boldgrid-backup' ) ); $core->notice->add_user_notice( $message, 'notice notice-error' ); wp_send_json_error(); } // Validation, $_POST data. $filename = ! empty( $_POST['filename'] ) ? sanitize_file_name( $_POST['filename'] ) : false; // phpcs:ignore if ( empty( $filename ) ) { $message = sprintf( $error, $this->provider->get_title(), __( 'Invalid filename.', 'boldgrid-backup' ) ); $core->notice->add_user_notice( $message, 'notice notice-error' ); wp_send_json_error(); } $bucket = $this->provider->get_bucket(); $path = $core->backup_dir->get_path_to( $filename ); $success = $bucket->download_key( $filename, $path ); if ( $success ) { $notice = '

' . wp_kses( sprintf( // Translators: 1 The name of our provider title, such as DreamObjects. BOLDGRID_BACKUP_PREMIUM_TITLE . ' - ' . __( '%1$s Download', 'boldgrid-backup' ), $this->provider->get_title() ), array() ) . '

'; $notice .= '

' . wp_kses( sprintf( // Translators: 1 an opening strong tag, 2 its closing strong tag, 3 the filename of the backup just downloaded, 4 the name of our provider (such as DreamObjects). __( 'Backup file %1$s%3$s%2$s successfully downloaded from %4$s.', 'boldgrid-backup' ), '', '', $filename, $this->provider->get_title() ), array( 'strong' => array() ) ) . '

'; $core->notice->add_user_notice( $notice, 'notice notice-success' ); wp_send_json_success(); } else { $bucket_errors = $bucket->has_errors() ? implode( '
', $bucket->get_errors() ) : __( 'Unknown error', 'boldgrid-backup' ); $message = sprintf( $error, $this->provider->get_title(), $bucket_errors ); $core->notice->add_user_notice( $message, 'notice notice-error' ); wp_send_json_error(); } } } admin/remote/s3_client.php000064400000005432147600362700011534 0ustar00 */ use Aws\S3\S3Client; /** * S3 Client class. * * @since 1.1.0 */ class Boldgrid_Backup_Premium_Admin_Remote_S3_Client { /** * Our s3 client. * * @since 1.2.0 * @var Aws\S3\S3Client * @access protected */ protected $client; /** * Our provider. * * Not required to use this class. If set, allows a bucket to figure out which provider it * belongs to. * * @since 1.2.0 * @var Boldgrid_Backup_Premium_Admin_Remote_S3_Provider * @access protected */ protected $provider; /** * Constructor. * * @since 1.2.0 * * @param array $args An array of args. */ public function __construct( array $args ) { $credentials = new \Aws\Credentials\Credentials( $args['key'], $args['secret'] ); $this->client = new \Aws\S3\S3Client( array( /* * A "version" configuration value is required. Specifying a version constraint ensures * that your code will not be affected by a breaking change made to the service. * @link https://docs.aws.amazon.com/sdk-for-php/v3/developer-guide/guide_configuration.html#cfg-version */ 'version' => '2006-03-01', 'endpoint' => $args['endpoint'], 'credentials' => $credentials, /* * Configure our region. While we're using a custom endpoint, if this variable is missing * we'll get a fatal. */ 'region' => '', ) ); } /** * Get the client. * * @since 1.2.0 * * @return Aws\S3\S3Client */ public function get_client() { return $this->client; } /** * Get our provider. * * @since 1.2.0 * * @return Boldgrid_Backup_Premium_Admin_Remote_S3_Provider */ public function get_provider() { return $this->provider; } /** * Whether or not this client has a provider. * * A client can be instantied by itself and not belong to a client. * * @since 1.2.0 * * @return bool */ public function has_provider() { return ! is_null( $this->provider ); } /** * Whether or not this client is valid. * * @since 1.2.0 * * @return bool */ public function is_valid() { // @todo Probably need better logic here. try { $buckets = new Boldgrid_Backup_Premium_Admin_Remote_S3_Buckets( $this ); $buckets->set_buckets(); $buckets->get_buckets(); return true; } catch ( Exception $e ) { return false; } } /** * Set our provider. * * @since 1.2.0 * * @param Boldgrid_Backup_Premium_Admin_Remote_S3_Provider $provider */ public function set_provider( Boldgrid_Backup_Premium_Admin_Remote_S3_Provider $provider ) { $this->provider = $provider; } } admin/remote/s3_bucket.php000064400000031740147600362700011534 0ustar00 */ /** * S3 Bucket class. * * @since 1.2.0 */ class Boldgrid_Backup_Premium_Admin_Remote_S3_Bucket { /** * An array of backups. * * @since 1.2.0 * @var array * @access private */ private $backups; /** * An instance of Boldgrid_Backup_Premium_Admin_Remote_S3_Client. * * @since 1.2.0 * @var Boldgrid_Backup_Premium_Admin_Remote_S3_Client * @access private */ private $client; /** * An array of error messages. * * @since 1.2.0 * @var array * @access private */ private $errors = array(); /** * Our bucket id. * * @since 1.2.0 * @var string * @access private */ private $id; /** * An array of objects in a bucket. * * @since 1.2.0 * @var array * @access private */ private $objects; /** * Constructor. * * @since 1.2.0 * * @param Boldgrid_Backup_Premium_Admin_Remote_S3_Client $client Our S3 client. * @param string $id Our bucket id. */ public function __construct( Boldgrid_Backup_Premium_Admin_Remote_S3_Client $client, $id ) { $this->client = $client; $this->id = $id; } /** * Get our bucket id. * * @since 1.2.0 * * @return string */ public function get_id() { return $this->id; } /** * Get an array of our backups within the bucket. * * @since 1.2.0 * * @return array */ public function get_backups() { return $this->backups; } /** * Get an array of our backups, ordered by lastmodified desc. * * The first items in the array will have the largest lastmodified values, IE the newest files. * * This method is mainly used when enforcing retention. * * @since 1.2.0 * * @return array */ public function get_backups_desc() { $this->set_backups(); // Sort by timestamp desc. usort( $this->backups, function( $a, $b ) { return $a['Metadata']['lastmodified'] < $b['Metadata']['lastmodified'] ? 1 : -1; } ); return $this->backups; } /** * Get our bucket's client. * * @since 1.2.0 * * @return Boldgrid_Backup_Premium_Admin_Remote_S3_Client */ public function get_client() { return $this->client; } /** * Get our errors. * * @since 1.2.0 * * @return mixed An array if we have errors, null if we don't. */ public function get_errors() { return $this->errors; } /** * Get an object's headers. * * @since 1.2.0 * * @var string $key The object key. * @return Guzzle\Service\Resource\Model Raw response from s3 server. */ public function get_object_headers( $key ) { $params = array( 'Bucket' => $this->id, 'Key' => $key, ); return $this->client->get_client()->headObject( $params ); } /** * Get one set of data from the headers. * * @since 1.2.0 * * @param string $object_key The object key to get the headers for. * @param string $header_key The key of the header to retrieve. * @return mixed */ public function get_object_header( $object_key, $header_key ) { $headers = $this->get_object_headers( $object_key ); return $headers->get( $header_key ); } /** * Get an object by key. * * @since 1.2.0 * * @param string $key The key of the object to get. * @return array The object. */ public function get_object( $key ) { $my_object = array(); foreach ( $this->objects as $object ) { if ( $object['Key'] === $key ) { $my_object = $object; break; } } return $my_object; } /** * Get this bucket's objects. * * Be sure to set them first. * * @since 1.2.0 * * @return array */ public function get_objects() { return $this->objects; } /** * Whether or not this bucket has errors. * * @since 1.2.0 * * @return bool */ public function has_errors() { return ! empty( $this->errors ); } /** * Whether or not a bucket has an object by key. * * @since 1.2.0 * * @param string $key The object key to search for. * @return bool */ public function has_object_key( $key ) { $has_object_key = false; $this->set_objects(); foreach ( $this->objects as $object ) { if ( $object['Key'] === $key ) { $has_object_key = true; } } return $has_object_key; } /** * Create a bucket if it does not already exist. * * @since 1.2.0 */ public function maybe_create() { $buckets = new Boldgrid_Backup_Premium_Admin_Remote_S3_Buckets( $this->client ); if ( ! $buckets->has_bucket( $this->id ) ) { $this->create(); } } /** * Enforce retention. * * @since 1.2.0 * * @param int $retention_count The number of backups to keep. * @param string $service_name The name of the service. */ public function enforce_retention( $retention_count, $service_name ) { $found = 0; foreach ( $this->get_backups_desc() as $backup ) { $found++; if ( $found <= $retention_count ) { continue; } $this->client->get_client()->deleteObject( array( 'Bucket' => $this->id, 'Key' => $backup['Key'], ) ); /** * Remote file deleted due to remote retention settings. * * @since 1.2.0 */ do_action( 'boldgrid_backup_remote_retention_deleted', $service_name, // Translators: 1: Bucket id, 2: Key. sprintf( __( 'Bucket: %1$s, Key: %2$s', 'boldgrid-backup' ), $this->id, $backup['Key'] ) ); } // Clear the objects transient. Enforce retention is the only place objects are deleted. $this->get_client()->get_provider()->get_transient()->delete_objects( $this->id ); } /** * Create a bucket. * * @since 1.2.0 * * @return bool True if the bucket is created or the user previously created the bucket. */ public function create() { // Validate bucket name. if ( ! $this->client->get_client()->isBucketDnsCompatible( $this->id ) ) { $this->errors[] = __( 'Invalid Bucket ID. Bucket name must be between 3 and 63 characters long, must not end with a dash or period, and must not use any special characters.', 'boldgrid-backup' ); return false; } // Clear the buckets transient. if ( $this->client->has_provider() ) { $this->client->get_provider()->get_transient()->delete_buckets(); } try { $this->client->get_client()->createBucket( array( 'Bucket' => $this->id, ) ); } catch ( Aws\S3\Exception\BucketAlreadyOwnedByYouException $e ) { return true; } catch ( Aws\S3\Exception\BucketAlreadyExistsException $e ) { $this->errors[] = sprintf( // Translators: 1: Bucket id. __( 'Bucket ID %1$s already exist. Please try another Bucket ID.', 'boldgrid-backup' ), '' . esc_html( $this->id ) . '' ); return false; } catch ( Exception $e ) { $this->errors[] = __( 'Unknown error when attempting to create bucket.', 'boldgrid-backup' ); return false; } return true; } /** * Create a unique bucket id. * * When you delete a bucket, Amazon gives you the following message: * Amazon S3 buckets are unique. If you delete this bucket, you may lose the * bucket name to another AWS user. * * @since 1.2.0 * * @return string */ public static function create_unique_bucket() { $url = parse_url( get_site_url() ); // phpcs:ignore WordPress.WP.AlternativeFunctions $bucket_parts = array( 'boldgrid-backup', $url['host'], ); $bucket_id = implode( '-', $bucket_parts ); return $bucket_id; } /** * Download a file by key. * * @since 1.2.0 * * @param string $key The object key to download. * @param string $path The filepath to save the file to locally. * @return bool True if the file was downloaded successfully. */ public function download_key( $key, $path ) { $core = apply_filters( 'boldgrid_backup_get_core', null ); $this->set_objects( true ); $object = $this->get_object( $key ); /* * Make sure the object exists before trying to download it. * * The set_objects( true ) call above bypasses transient data and gets fresh data. If the backup * is not found by chance, the objects transient will be cleared, avoid future issues with this * backup. */ if ( empty( $object ) ) { $this->errors[] = __( 'Backup does not exist on the host.', 'boldgrid-backup' ); return false; } // Example $result: https://pastebin.com/thyw9jrh. $result = $this->client->get_client()->getObject( array( 'Bucket' => $this->id, 'Key' => $key, 'SaveAs' => $path, ) ); if ( empty( $result['ContentLength'] ) ) { $this->errors[] = __( 'File was empty, no ContentLength.', 'boldgrid-backup' ); return false; } // Make sure the size of the backup we downloaded matches what we should have downloaded. $filesize_match = ! empty( $object['Size'] ) && (int) $object['Size'] === (int) $result['ContentLength']; if ( $filesize_match ) { /* * Change the timestamp of the backup file. * * Normally you have to make a separate call to get Headers / Metadata, however it's included * in our $result. */ $last_modified = ! empty( $result['Metadata']['lastmodified'] ) ? $result['Metadata']['lastmodified'] : null; if ( ! empty( $last_modified ) ) { $core->wp_filesystem->touch( $path, $last_modified ); } $core->remote->post_download( $path ); return true; } else { $this->errors[] = esc_html( sprintf( // translators: 1 the filesize of the backup we downloaded, 2 the filesize we expected to download. __( 'Expected to download %1$s bytes, only downloaded %2$s.', 'boldgrid-backup' ), $object['Size'], $core->wp_filesystem->size( $path ) ) ); // Delete the file we just downloaded, it's not valid. $core->wp_filesystem->delete( $path ); return false; } } /** * Get an array of our backups within the bucket. * * @since 1.2.0 * * @return array */ public function set_backups() { // First, try to get backups from transient. if ( is_null( $this->backups ) ) { if ( $from_transient = $this->client->get_provider()->get_transient()->get_backups( $this->id ) ) { // phpcs:ignore $this->backups = $from_transient; } } /* * If we could not get backups from transient, get fresh and save transient. * * Transient is cleared whenever the objects transient is cleared. */ if ( is_null( $this->backups ) ) { $this->backups = array(); // The list of backups is built from our list of objects, so build objects first. $this->set_objects(); foreach ( $this->objects as &$object ) { if ( ! isset( $object['Metadata'] ) ) { $object['Metadata'] = $this->get_object_header( $object['Key'], 'Metadata' ); // There could be any number of files in the bucket. Only include backup files. if ( ! empty( $object['Metadata']['isboldgridbackup'] ) ) { $this->backups[] = $object; } } } $this->client->get_provider()->get_transient()->set_backups( $this->backups, $this->id ); } } /** * Initilize our bucket's objects. * * @since 1.2.0 * * @param bool $force Pass as true to wipe existing objects and get fresh. */ public function set_objects( $force = false ) { $success = false; // Validate our bucket id before continuing. if ( empty( $this->id ) ) { $this->errors[] = __( 'S3 Bucket error: Attempting to set objects on an empty bucket id.', 'boldgrid-backup' ); return $success; } // First, try to get objects from transient. if ( is_null( $this->objects ) ) { if ( $from_transient = $this->client->get_provider()->get_transient()->get_objects( $this->id ) ) { // phpcs:ignore $this->objects = $from_transient; } } if ( $force ) { $this->objects = null; } /* * If we could not get objects from transient, get them fresh and set transient. * * Transient is cleared whenever an object is uploaded or deleted. */ if ( is_null( $this->objects ) ) { $this->objects = array(); $objects = $this->client->get_client()->getIterator( 'ListObjects', array( 'Bucket' => $this->id, ) ); try { foreach ( $objects as $object ) { $this->objects[] = $object; } $this->client->get_provider()->get_transient()->set_objects( $this->objects, $this->id ); } catch ( Exception $e ) { $error = method_exists( $e, 'getStatusCode' ) && method_exists( $e, 'getAwsErrorCode' ) ? $e->getStatusCode() . ' ' . $e->getAwsErrorCode() : __( 'Unknown error', 'boldgrid-backup' ); $this->errors[] = wp_kses( sprintf( // Translators: 1 This s3 bucket id, 2 the error message, 3 an opening em tag, 4 its closing em tag, 5 an opening strong tag, 6 its closing strong tag. __( '%5$sError%6$s: Unable to retrieve a list of backups from %3$sS3 bucket%4$s %5$s%1$s%6$s. %2$s', 'boldgrid-backup' ), $this->id, $error, '', '', '', '' ), array( 'em' => array(), 'strong' => array(), ) ); return false; } } $success = is_array( $this->objects ); return $success; } } admin/remote/google_drive_page.php000064400000015164147600362700013315 0ustar00 */ // phpcs:disable WordPress.VIP /** * Build the settings page for Google Drive. * * @since 1.1.0 */ class Boldgrid_Backup_Premium_Admin_Remote_Google_Drive_Page { /** * The core class object. * * @since 1.1.0 * @access private * @var Boldgrid_Backup_Admin_Core */ private $core; /** * Default folder name. * * @since 1.1.0 * @access private * @var string */ private $default_folder_name; /** * Default folder type. * * A full explanation of this property can be found in * Boldgrid_Backup_Premium_Admin_Remote_Google_Drive_Folder::get_backup_id * * @since 1.4.0 * @access private * @var string */ private $default_folder_type = 'name'; /** * Default nickname. * * @since 1.1.0 * @access private * @var string */ private $default_nickname = 'Google Drive'; /** * Default retention count. * * @since 1.1.0 * @access private * @var int */ private $default_retention_count = 5; /** * An instance of Boldgrid_Backup_Premium_Admin_Core. * * @since 1.1.0 * @var Boldgrid_Backup_Premium_Admin_Core */ private $premium_core; /** * Constructor. * * @since 1.1.0 * * @param Boldgrid_Backup_Admin_Core $core Core class object. * @param Boldgrid_Backup_Premium_Admin_Core $premium_core Premium Core class object. */ public function __construct( Boldgrid_Backup_Admin_Core $core, Boldgrid_Backup_Premium_Admin_Core $premium_core ) { $this->core = $core; $this->premium_core = $premium_core; $this->default_folder_name = esc_html( site_url() ); } /** * Add submenu page. * * @since 1.1.0 */ public function add_submenu_page() { $capability = 'administrator'; add_submenu_page( 'boldgrid-backup-settings', __( 'Google Drive Settings', 'boldgrid-backup' ), __( 'Google Drive Settings', 'boldgrid-backup' ), $capability, 'boldgrid-backup-google-drive', array( $this, 'page', ) ); } /** * Get the default folder name. * * @since 1.1.0 * * @return string */ public function get_default_folder_name() { return $this->default_folder_name; } /** * Get the default retention count. * * @since 1.1.0 * * @return int */ public function get_default_retention_count() { return $this->default_retention_count; } /** * Get our nickname. * * If we don't have a nickname set, it will be our default nickname, 'Google Drive'. * * @since 1.1.0 * * @return string */ public function get_nickname() { return $this->premium_core->google_drive->settings->get_setting( 'nickname', $this->default_nickname ); } /** * Determine whether or not the request to save / delete settings is authorized. * * This logic in this method originally lived in self::maybe_save(), but was moved here when the * self::maybe_delete() method was added and the code needed to be reused. * * @since 1.3.2 * * @return bool True if user is authorized. */ private function is_authorized() { if ( ! current_user_can( 'update_plugins' ) ) { do_action( 'boldgrid_backup_notice', __( 'Unauthorized request.', 'boldgrid-backup' ), 'notice error is-dismissible' ); return false; } if ( ! check_ajax_referer( 'bgbkup-gd-settings', 'gd_auth', false ) ) { do_action( 'boldgrid_backup_notice', __( 'Unauthorized request, expired nonce.', 'boldgrid-backup' ), 'notice error is-dismissible' ); return false; } return true; } /** * Render our settings page. * * @since 1.1.0 */ public function page() { wp_enqueue_style( 'boldgrid-backup-admin-hide-all' ); $this->maybe_save(); $this->maybe_delete(); // Vars needed for the google_drive.php page. $folder_type = $this->premium_core->google_drive->settings->get_setting( 'folder_type', 'name' ); $folder_name = $this->premium_core->google_drive->settings->get_setting( 'folder_name', $this->default_folder_name ); $retention_count = $this->premium_core->google_drive->settings->get_setting( 'retention_count', 5 ); $nickname = $this->get_nickname(); include BOLDGRID_BACKUP_PREMIUM_PATH . '/admin/partials/remote/google_drive.php'; } /** * If need be, delete the user's Google Drive settings. * * This means they will need to reauthorize. * * @since 1.3.2 * * @return bool True on success. */ private function maybe_delete() { // If we're not deleting settings, abort. if ( empty( $_POST['delete_settings'] ) ) { // phpcs:ignore return false; } if ( ! $this->is_authorized() ) { return false; } $this->premium_core->google_drive->logs->get_connect_log()->add( __METHOD__ . ' Deleting Google Drive settings.' ); $this->premium_core->google_drive->settings->delete_settings(); do_action( 'boldgrid_backup_notice', __( 'Settings deleted.', 'boldgrid-backup' ), 'notice updated is-dismissible' ); return true; } /** * Process the user's request to update their Google Drive settings. * * @since 1.1.0 * * @return bool Whether or not we saved settings. */ public function maybe_save() { if ( empty( $_POST['save_settings'] ) ) { // phpcs:ignore return false; } if ( ! $this->is_authorized() ) { return false; } // Nonce verification done in the is_authorized() call above. $folder_name = ! empty( $_POST['folder_name'] ) ? sanitize_text_field( $_POST['folder_name'] ) : $this->default_folder_name; // phpcs:ignore $folder_type = ! empty( $_POST['folder_type'] ) ? sanitize_text_field( $_POST['folder_type'] ) : $this->default_folder_type; // phpcs:ignore $retention_count = ! empty( $_POST['retention_count'] ) ? intval( $_POST['retention_count'] ) : $this->default_retention_count; // phpcs:ignore $nickname = ! empty( $_POST['nickname'] ) ? sanitize_text_field( $_POST['nickname'] ) : $this->default_nickname; // phpcs:ignore $this->premium_core->google_drive->logs->get_connect_log()->add( __METHOD__ . ' Updating Google Drive settings.' ); $this->premium_core->google_drive->settings->save_setting( 'folder_name', $folder_name ); $this->premium_core->google_drive->settings->save_setting( 'folder_type', $folder_type ); $this->premium_core->google_drive->settings->save_setting( 'retention_count', $retention_count ); $this->premium_core->google_drive->settings->save_setting( 'nickname', $nickname ); do_action( 'boldgrid_backup_notice', __( 'Settings saved.', 'boldgrid-backup' ), 'notice updated is-dismissible' ); return true; } } admin/remote/google_drive_hooks.php000064400000021600147600362700013514 0ustar00 */ /** * Class: Boldgrid_Backup_Premium_Admin_Remote_Google_Drive_Hooks * * @since 1.1.0 */ class Boldgrid_Backup_Premium_Admin_Remote_Google_Drive_Hooks { /** * The core class object. * * @since 1.1.0 * @access private * @var Boldgrid_Backup_Admin_Core */ private $core; /** * An instance of Boldgrid_Backup_Premium_Admin_Core. * * @since 1.1.0 * @access private * @var Boldgrid_Backup_Premium_Admin_Core */ private $premium_core; /** * Constructor. * * @since 1.1.0 * * @param Boldgrid_Backup_Admin_Core $core Boldgrid_Backup_Admin_Core object. * @param Boldgrid_Backup_Premium_Admin_Core $premium_core Boldgrid_Backup_Premium_Admin_Core object. */ public function __construct( Boldgrid_Backup_Admin_Core $core, Boldgrid_Backup_Premium_Admin_Core $premium_core ) { $this->core = $core; $this->premium_core = $premium_core; } /** * Upload a backup via an ajax request. * * This is done via the archive details of a single archive. * * @since 1.1.0 */ public function ajax_upload() { if ( ! current_user_can( 'update_plugins' ) ) { wp_send_json_error( __( 'Permission denied.', 'boldgrid-backup' ) ); } if ( ! $this->core->archive_details->validate_nonce() ) { wp_send_json_error( __( 'Invalid nonce.', 'boldgrid-backup' ) ); } $filename = ! empty( $_POST['filename'] ) ? sanitize_file_name( $_POST['filename'] ) : false; // phpcs:ignore $filepath = $this->core->backup_dir->get_path_to( $filename ); if ( empty( $filename ) || ! $this->core->wp_filesystem->exists( $filepath ) ) { wp_send_json_error( __( 'Invalid archive filename.', 'boldgrid-backup' ) ); } $archive = new Boldgrid_Backup_Premium_Admin_Remote_Google_Drive_Archive( $this->core, $this->premium_core, $filename ); $success = $archive->upload(); if ( $success ) { wp_send_json_success(); } else { wp_send_json_error( $archive->last_error ); } } /** * Check for an auth code and scope coming in via the query params. * * This method is called duruing admin_init, early enough so that we can do a redirect within * the init() method if need be. * * @since 1.1.0 */ public function check_for_auth() { if ( ! empty( $_GET['code'] ) && ! empty( $_GET['scope'] ) ) { // phpcs:ignore $this->premium_core->google_drive->client->init(); } } /** * Hook into the filter to add all Google Drive backups to the full list of backups. * * @since 1.1.0 */ public function filter_get_all() { $files = $this->premium_core->google_drive->folder->get_files(); $files = ! empty( $files['files'] ) ? $files['files'] : array(); foreach ( $files as $file ) { $backup = array( 'filename' => $file['name'], 'last_modified' => ! empty( $file['properties']['createdTime'] ) ? $file['properties']['createdTime'] : strtotime( $file['createdTime'] ), 'size' => $file['size'], 'locations' => array( array( 'title' => $this->premium_core->google_drive->page->get_nickname(), 'on_remote_server' => true, 'title_attr' => $this->premium_core->google_drive->get_title(), ), ), ); $this->core->archives_all->add( $backup ); } } /** * Determine if Google Drive is setup. * * @since 1.1.0 */ public function is_setup_ajax() { if ( ! current_user_can( 'update_plugins' ) ) { wp_send_json_error( __( 'Permission denied.', 'boldgrid-backup' ) ); } if ( ! check_ajax_referer( 'boldgrid_backup_settings', 'security', false ) ) { wp_send_json_error( __( 'Invalid nonce.', 'boldgrid-backup' ) ); } // Settings and location needed within the storage-location.php file. $settings = $this->core->settings->get_settings(); $location = $this->premium_core->google_drive->get_details(); $tr = include BOLDGRID_BACKUP_PATH . '/admin/partials/settings/storage-location.php'; $this->core->ftp->is_setup() ? wp_send_json_success( $tr ) : wp_send_json_error( $tr ); } /** * Actions to take after a backup file has been generated. * * @since 1.1.0 * * @param array $info An array of info about our backup. */ public function post_archive_files( array $info ) { /* * We only want to add this to the jobs queue if we're in the middle of an automatic backup. * If the user simply clicked on "Backup site now", we don't want to automatically send the * backup to Google, there's a button for that. */ if ( ! $this->core->doing_cron ) { return; } if ( ! $this->premium_core->google_drive->settings->get_setting( 'enabled', false ) || $info['dryrun'] || ! $info['save'] ) { return; } $args = array( 'filepath' => $info['filepath'], 'action' => 'boldgrid_backup_google_drive_upload_post_archive', 'action_data' => $info['filepath'], 'action_title' => __( 'Upload backup file to Google Drive', 'boldgrid-backup' ), ); $this->core->jobs->add( $args ); } /** * Register Google Drive as a storage location. * * When you go to the settings page and see a list of storage providers, each of those storage providers needs to * hook into the "boldgrid_backup_register_storage_location" filter and add themselves. * * @since 1.1.0 * * @param array $storage_locations An array of storage locations. * @return array An updated array of storage locations. */ public function register_storage_location( array $storage_locations ) { $storage_locations[] = $this->premium_core->google_drive->get_details(); return $storage_locations; } /** * Add the one click upload from an archive's details page. * * @since 1.1.0 * * @param string $filepath Path to our archive. */ public function single_archive_remote_option( $filepath ) { $filename = basename( $filepath ); $archive = new Boldgrid_Backup_Premium_Admin_Remote_Google_Drive_Archive( $this->core, $this->premium_core, $filename ); $uploaded = $archive->is_uploaded(); $is_setup = $this->premium_core->google_drive->is_setup(); $storage = array( 'id' => $this->premium_core->google_drive->get_key(), 'title' => $this->premium_core->google_drive->page->get_nickname(), 'title_attr' => $this->premium_core->google_drive->get_title(), 'uploaded' => $uploaded, 'allow_upload' => $is_setup, 'is_setup' => $is_setup, ); $this->core->archive_details->remote_storage_li[] = $storage; } /** * Upload a file. * * The jobs queue will call this method to upload a file. * * @since 1.1.0 * * @param string $filepath File path. * @return bool */ public function upload_post_archiving( $filepath ) { $filename = basename( $filepath ); $archive = new Boldgrid_Backup_Premium_Admin_Remote_Google_Drive_Archive( $this->core, $this->premium_core, $filename ); $success = $archive->upload(); return $success; } /** * Download a backup file. * * @since 1.1.0 */ public function wp_ajax_download() { // translators: 1: A Google Drive error message. $error = __( 'Unable to download backup from Google Drive: %1$s', 'bolgrid-bakcup' ); $allowed_html = array( 'h2' => array(), 'p' => array(), 'strong' => array(), ); // Validation, user role. if ( ! current_user_can( 'update_plugins' ) ) { $this->core->notice->add_user_notice( sprintf( $error, __( 'Permission denied.', 'boldgrid-backup' ) ), 'notice notice-error' ); wp_send_json_error(); } // Validation, nonce. if ( ! $this->core->archive_details->validate_nonce() ) { $this->core->notice->add_user_notice( sprintf( $error, __( 'Invalid nonce.', 'boldgrid-backup' ) ), 'notice notice-error' ); wp_send_json_error(); } // Validation, $_POST data. $filename = ! empty( $_POST['filename'] ) ? sanitize_file_name( $_POST['filename'] ) : false; // phpcs:ignore if ( empty( $filename ) ) { $this->core->notice->add_user_notice( sprintf( $error, __( 'Invalid filename.', 'boldgrid-backup' ) ), 'notice notice-error' ); wp_send_json_error(); } $archive = new Boldgrid_Backup_Premium_Admin_Remote_Google_Drive_Archive( $this->core, $this->premium_core, $filename ); $result = $archive->download(); if ( $result ) { $this->core->notice->add_user_notice( wp_kses( sprintf( // translators: 1: Filename, 2: Premium plugin title. __( '

%2$s - Google Drive Download

Backup file %1$s successfully downloaded from Google Drive.

', 'boldgrid-backup' ), $filename, BOLDGRID_BACKUP_PREMIUM_TITLE ), $allowed_html ), 'notice notice-success' ); wp_send_json_success(); } else { $this->core->notice->add_user_notice( sprintf( $error, $archive->last_error ), 'notice notice-error' ); wp_send_json_error(); } } } admin/remote/google_drive_folder.php000064400000034721147600362700013654 0ustar00 */ /** * Class: Boldgrid_Backup_Premium_Admin_Remote_Google_Drive_Folder * * @since 1.1.0 */ class Boldgrid_Backup_Premium_Admin_Remote_Google_Drive_Folder { /** * Our last error message. * * @since 1.1.0 * @var string */ public $last_error; /** * The core class object. * * @since 1.1.0 * @access private * @var Boldgrid_Backup_Admin_Core */ private $core; /** * Our parent folder name. * * All backups will be stored off the root in a folder named after the parent plugin, such as: * /BOLDGRID_BACKUP_TITLE/DomainA.com/backup1.zip * /BOLDGRID_BACKUP_TITLE/DomainB.com/backup1.zip * * @since 1.1.0 * @access private * @var string */ private $parent_folder_name = 'BoldGrid Backup'; /** * An instance of Boldgrid_Backup_Premium_Admin_Core. * * @since 1.1.0 * @access private * @var Boldgrid_Backup_Premium_Admin_Core */ private $premium_core; /** * Constructor. * * @since 1.1.0 * * @param Boldgrid_Backup_Admin_Core $core Boldgrid_Backup_Admin_Core object. * @param Boldgrid_Backup_Premium_Admin_Core $premium_core Boldgrid_Backup_Premium_Admin_Core object. */ public function __construct( Boldgrid_Backup_Admin_Core $core, Boldgrid_Backup_Premium_Admin_Core $premium_core ) { $this->core = $core; $this->premium_core = $premium_core; } /** * Create a folder. * * @since 1.1.0 * * @link https://github.com/google/google-api-php-client/issues/860 * * @param string $name The name of the folder to create. * @param string $parent_id Optional, a parent folder id. * @return mixed Google_Service_Drive_DriveFile on Success, false on failure. */ public function create( $name, $parent_id = '' ) { $service = $this->premium_core->google_drive->client->get_service(); $args = array( 'name' => $name, 'mimeType' => 'application/vnd.google-apps.folder', ); // By default, folders will be created in the root directory, unless otherwise stated. if ( ! empty( $parent_id ) ) { $args['parents'] = array( $parent_id ); } $folder = new Google_Service_Drive_DriveFile( $args ); // Catch any possible exceptions thrown by Google Drive classes. try { $req = $service->files->create( $folder, array( 'fields' => 'id', ) ); return $req; } catch ( Google_Exception $e ) { $this->last_error = sprintf( // translators: 1 The name of a folder we're trying to create, 2 that folder's parent id, 3 the error code, 4 the error message. __( 'Unable to create folder "%1$s" (with a parent id of "%2$s"). Error %3$s: %4$s.', 'boldgrid-backup' ), $name, $parent_id, $e->getCode(), $this->get_exception_message( $e ) ); return false; } } /** * Create our parent folder. * * @see self::parent_folder_name * * @return mixed Google_Service_Drive_DriveFile on Success, false on failure. */ private function create_parent() { return $this->create( $this->parent_folder_name ); } /** * Enforce retention. * * @since 1.1.0 * * @param string $folder_id The id of the folder to enforce retention on. */ public function enforce_retention( $folder_id = '' ) { // If we have an invalid retention count, abort. $retention_count = $this->premium_core->google_drive->settings->get_setting( 'retention_count', $this->premium_core->google_drive->page->get_default_retention_count() ); if ( empty( $retention_count ) ) { return; } $files = $this->get_files_asc( $folder_id ); if ( count( $files ) <= $retention_count ) { return; } $service = $this->premium_core->google_drive->client->get_service(); $count_to_delete = count( $files ) - $retention_count; foreach ( $files as $file ) { if ( 0 === $count_to_delete ) { break; } try { $service->files->delete( $file['id'], array( 'supportsAllDrives' => true, 'supportsTeamDrives' => true, ) ); } catch ( Exception $e ) { $this->last_error = __( 'An error occurred deleting a backup during retention:', 'boldgrid-backup' ) . ' ' . $e->getMessage(); } $count_to_delete--; } } /** * Get the id of our folder. * * @since 1.1.0 * * @param string $name The name of the folder to create. * @param string $parent_id Optional, a parent folder id. * @return string Our folder id, or false on failure. */ public function get_id( $name, $parent_id = '' ) { if ( empty( $name ) ) { return false; } /* * Try to get our folder. * * If we get false, self::last_error has been set and we can abort. */ $folder = $this->get_folder( $name, $parent_id ); if ( false === $folder ) { return false; } if ( empty( $folder ) ) { $folder = $this->create( $name, $parent_id ); } return empty( $folder->id ) ? false : $folder->id; } /** * Get a folder based on key / value search. * * Typical method is to get folder by name. * * @since 1.1.0 * * @link https://developers.google.com/drive/api/v3/search-parameters * * @param string $name The name of the folder to create. * @param string $parent_id Optional, a parent folder id. * @return mixed Google_Service_Drive_DriveFile Object on success, false on failure. */ public function get_folder( $name, $parent_id = '' ) { $service = $this->premium_core->google_drive->client->get_service(); $this->premium_core->google_drive->client->set_defer( false ); $query = array( 'name = "' . $name . '"', 'mimeType = "application/vnd.google-apps.folder"', 'trashed = false', ); // By default, we do not specify a parent folder id. if ( ! empty( $parent_id ) ) { $query[] = '"' . $parent_id . '" in parents'; } try { /* * Build our query to search for a folder. * * It is assumed that we will be using this method to search by folder name. If we later * accept other parameters besides $key = 'name', we will need to build the additional * logic below. * * Example $files: https://pastebin.com/54rRb6ue. */ $files = $service->files->listFiles( array( 'q' => implode( ' and ', $query ), 'pageSize' => 1, ) ); return empty( $files->files ) ? array() : $files->files[0]; } catch ( Google_Exception $e ) { $this->last_error = sprintf( // translators: 1 The name of a folder we're trying to get, 2 that folder's parent id, 3 the error code, 4 the error message. __( 'Unable to get backup folder "%1$s" (with a parent id of "%2$s"). Error %3$s: %4$s.', 'boldgrid-backup' ), $name, $parent_id, $e->getCode(), $this->get_exception_message( $e ) ); return false; } } /** * Get the id of our backup folder. * * Do not confuse this with self::get_parent_id(). * * /parent folder/backup folder/backup.zip * * @since 1.1.0 * * @see self::get_parent_id() * @see self::parent_folder_name * * @return string Our folder id, or false on failure. */ public function get_backup_id() { $backup_folder_id = false; /* * Prior to 1.4.0, backups on Google Drive were stored in a parent / child folder, such as * PARENT/BACKUP/backup.zip. The user was only able to configure the "BACKUP" folder. * * As of 1.4.0, the user can now specify a Google Drive ID as the folder. So, if the user wanted * to upload a file here: https://drive.google.com/drive/u/0/folders/abc123 - the user only * needs to specify "abc123" * * Our Google Drive settings now have two settings: "folder_type" and "folder_name". Examples: * * Original method, specifying a folder name to upload backups to: * # folder_type: name * # folder_name: domain.com * * New method, specifying a folder id to upload backups to: * # folder_type: id * # folder_name: abc123 */ switch ( $this->premium_core->google_drive->settings->get_setting( 'folder_type', 'name' ) ) { case 'name': $folder_name = $this->get_folder_name(); if ( empty( $folder_name ) ) { $this->last_error = __( 'Cannot get id of backup folder: Empty folder name. Please try to resave your Google Drive settings.', 'boldgrid-backup' ); } else { $backup_folder_id = $this->get_id( $folder_name, $this->get_parent_id() ); } break; case 'id': $backup_folder_id = $this->premium_core->google_drive->settings->get_setting( 'folder_name' ); break; } return $backup_folder_id; } /** * Get our error message from an exception. * * Sometimes we're dealing with a string, and others an array. * * @since 1.3.2 * * @param Google_Exception $e * @return string */ private function get_exception_message( $e ) { $message = $e->getMessage(); $unknown = __( 'Unknown error', 'boldgrid-backup' ); $is_json = '{' === substr( trim( $message ), 0, 1 ); if ( $is_json ) { $message = json_decode( $message ); $message = ! empty( $message->error->errors[0]->message ) ? $message->error->errors[0]->message : $unknown; } else { $message = is_string( $message ) && ! empty( $message ) ? $message : $unknown; } return $message; } /** * Get a specific file. * * @since 1.1.0 * * @param string $filename The name of our file. * @param string $folder_id The id of the file's folder. * @return array The first file found. */ public function get_file( $filename, $folder_id = '' ) { $service = $this->premium_core->google_drive->client->get_service(); $this->premium_core->google_drive->client->set_defer( false ); if ( empty( $folder_id ) ) { $folder_id = $this->get_backup_id(); if ( false === $folder_id ) { $this->last_error = $this->last_error; return false; } } try { $query = array( '"' . $folder_id . '" in parents', 'name = "' . $filename . '"', 'mimeType != "application/vnd.google-apps.folder"', 'trashed = false', ); $files = $service->files->listFiles( array( 'q' => implode( ' AND ', $query ), 'pageSize' => 1, 'fields' => 'files(id,size)', // Shared drive support. 'includeItemsFromAllDrives' => true, 'supportsAllDrives' => true, ) ); // Return the first file in the set of results. return empty( $files->files ) ? array() : $files->files[0]; } catch ( Google_Exception $e ) { // Translators: 1: Error message. $this->last_error = sprintf( __( 'Unable to determine if archive exists in Google Drive. %1$s.', 'boldgrid-backup' ), $e->getMessage() ); return false; } } /** * Get a list of files in our backup folder. * * @since 1.1.0 * * @param string $folder_id The folder id to get the contents of. * @return mixed False on failure, Google_Service_Drive_FileList on success. * Example: https://pastebin.com/Ui0BSrwz */ public function get_files( $folder_id = '' ) { $folder_id = empty( $folder_id ) ? $this->get_backup_id() : $folder_id; if ( empty( $folder_id ) ) { $this->last_error = __( 'Unable to get Google Drive folder id.', 'boldgrid-backup' ); return false; } $service = $this->premium_core->google_drive->client->get_service(); $this->premium_core->google_drive->client->set_defer( false ); $q = array( '"' . $folder_id . '" in parents', 'mimeType != "application/vnd.google-apps.folder"', 'trashed = false', ); try { $files = $service->files->listFiles( array( 'q' => implode( ' and ', $q ), 'pageSize' => 100, 'fields' => 'files(id,size,name,createdTime,properties)', // Shared drive support. 'includeItemsFromAllDrives' => true, 'supportsAllDrives' => true, ) ); $site_backup_id = get_site_option( 'boldgrid_backup_id' ); $file_count = count( $files->files ); $filtered_files = array(); for ( $i = 0; $i < $file_count; $i++ ) { if ( false !== strpos( $files[ $i ]['name'], $site_backup_id ) ) { $filtered_files[] = $files[ $i ]; } } $files->files = $filtered_files; return $files; } catch ( Google_Exception $e ) { $message = __( 'Unable to retrieve file listing.', 'boldgrid-backup' ); if ( 401 === $e->getCode() ) { $message .= ' ' . __( 'Invalid Credentials.', 'boldgrid-backup' ); } $this->last_error = $message; $this->premium_core->google_drive->logs->get_connect_log()->add( $message ); return false; } } /** * Get a list of files in asc order (based on date created). * * Used in the retention process. * * @param string $folder_id The folder id to get the contents of. * @return array */ public function get_files_asc( $folder_id = '' ) { $folder_id = empty( $folder_id ) ? $this->get_backup_id() : $folder_id; $files = $this->get_files( $folder_id ); $files = $files instanceof Google_Service_Drive_FileList && ! empty( $files['files'] ) ? $files['files'] : array(); // Sort by created time. usort( $files, function( $a, $b ) { $a_time = ! empty( $a['properties']['createdTime'] ) ? $a['properties']['createdTime'] : strtotime( $a['createdTime'] ); // phpcs:ignore $b_time = ! empty( $b['properties']['createdTime'] ) ? $b['properties']['createdTime'] : strtotime( $b['createdTime'] ); // phpcs:ignore // Sorts low (oldest) to high (newest). return $a_time > $b_time ? 1 : -1; } ); return $files; } /** * Get the name of our folder on Google Drive where we are storing backups. * * @since 1.1.0 * * @return string */ public function get_folder_name() { return $this->premium_core->google_drive->settings->get_setting( 'folder_name', $this->premium_core->google_drive->page->get_default_folder_name() ); } /** * Get our parent folder. * * @since 1.1.0 * * @see self::parent_folder_name * * @return mixed Google_Service_Drive_DriveFile Object on success, false on failure. */ private function get_parent_folder() { return $this->get_folder( $this->parent_folder_name ); } /** * Get our parent folder id. * * Do not confuse this with self::get_backup_id(). * * /parent folder/backup folder/backup.zip * * @since 1.1.0 * * @see self::get_backup_id() * @see self::parent_folder_name * * @return string Our folder id, or false on failure. */ public function get_parent_id() { return $this->get_id( $this->parent_folder_name ); } } admin/remote/google_drive_client.php000064400000024013147600362700013650 0ustar00 */ /** * Google Drive Client class. * * @since 1.1.0 */ class Boldgrid_Backup_Premium_Admin_Remote_Google_Drive_Client { /** * Our Google Drive client. * * @since 1.1.0 * @var Google_Client */ public $client; /** * The last error message, if any, received. * * @since 1.1.0 * @var string */ public $last_error; /** * Access token key. * * @since 1.1.0 * @access private * @var string */ private $access_token_key = 'access_token'; /** * Code key. * * @since 1.1.0 * @access private * @var string */ private $code_key = 'code'; /** * The core class object. * * @since 1.1.0 * @access private * @var Boldgrid_Backup_Admin_Core */ private $core; /** * An instance of Boldgrid_Backup_Premium_Admin_Core. * * @since 1.1.0 * @access private * @var Boldgrid_Backup_Premium_Admin_Core */ private $premium_core; /** * An instance of Google_Service_Drive. * * @since 1.1.0 * @access private * @var Google_Service_Drive */ private $service; /** * Constructor. * * @since 1.1.0 * * @param Boldgrid_Backup_Admin_Core $core Boldgrid_Backup_Admin_Core object. * @param Boldgrid_Backup_Premium_Admin_Core $premium_core Boldgrid_Backup_Premium_Admin_Core object. */ public function __construct( Boldgrid_Backup_Admin_Core $core, Boldgrid_Backup_Premium_Admin_Core $premium_core ) { $this->core = $core; $this->premium_core = $premium_core; } /** * Init our client. * * @since 1.1.0 * * @link https://github.com/googleapis/google-api-php-client/blob/master/examples/large-file-upload.php * * @return mixed Google_Client on success, false on failure. */ public function init() { if ( ! empty( $this->client ) ) { return $this->client; } $configs = $this->premium_core->get_configs(); $this->client = new Google_Client(); $this->client->setAuthConfig( $configs['google_drive_config'] ); /* * Refreshing an access token (offline access) * * @link https://developers.google.com/identity/protocols/OAuth2WebServer#offline * * Access tokens periodically expire. You can refresh an access token without prompting the * user for permission (including when the user is not present) if you requested offline * access to the scopes associated with the token. */ $this->client->setAccessType( 'offline' ); $this->client->setIncludeGrantedScopes( true ); // View and manage Google Drive files and folders that you have opened or created with this app. $this->client->addScope( Google_Service_Drive::DRIVE_FILE ); /* * Required to get a refresh token. * * @link https://stackoverflow.com/questions/8942340/get-refresh-token-google-api */ $this->client->setApprovalPrompt( 'force' ); if ( empty( $_GET['code'] ) ) { // phpcs:ignore $code = $this->get_code(); $access_token = $this->get_access_token(); } else { $code = $_GET['code']; // phpcs:ignore $this->update_code( $code ); // Exchange an authorization code for an access token. $access_token = $this->client->authenticate( $code ); $this->update_access_token( $access_token ); if ( ! empty( $access_token['error'] ) ) { $message = __( 'Unable to authorize Google Drive:', 'boldgrid-backup' ) . ' ' . esc_html( $access_token['error'] ) . ' - ' . esc_html( $access_token['error_description'] ); $this->core->notice->add_user_notice( $message, $this->core->notice->lang['dis_error'] ); } else { $message = __( 'Google Drive successfully authorized!', 'boldgrid-backup' ); $this->core->notice->add_user_notice( $message, $this->core->notice->lang['dis_success'] ); } $this->premium_core->google_drive->logs->get_connect_log()->add( $message ); wp_safe_redirect( admin_url( 'admin.php?page=boldgrid-backup-settings§ion=section_storage' ) ); // phpcs:ignore WordPress.VIP exit; } // Catch any possible exceptions thrown by the Google Drive classes. try { $this->client->setAccessToken( $access_token ); $this->maybe_refresh_token(); if ( $this->client->isAccessTokenExpired() ) { $this->last_error = __( 'Unable to connect to Google Drive. Access token expired.', 'boldgrid-backup' ); $this->premium_core->google_drive->logs->get_connect_log()->add( $this->last_error ); return false; } } catch ( InvalidArgumentException $e ) { // Translators: 1: Error message. $this->last_error = sprintf( __( 'Unable to connect to Google Drive. %1$s.', 'boldgrid-backup' ), $e->getMessage() ); $this->premium_core->google_drive->logs->get_connect_log()->add( $this->last_error ); return false; } return $this->client; } /** * Maybe refresh our access token. * * Since 1.1.0 * * @return bool Whether or not the access token was updated. */ public function maybe_refresh_token() { $refreshed = false; if ( $this->client->isAccessTokenExpired() ) { $this->premium_core->google_drive->logs->get_connect_log()->add( __METHOD__ . ' Access token is expired.' ); $refresh_token = $this->get_refresh_token(); if ( ! empty( $refresh_token ) ) { $this->premium_core->google_drive->logs->get_connect_log()->add( __METHOD__ . ' Fetching access token with refresh token...' ); $access_token = $this->client->fetchAccessTokenWithRefreshToken( $refresh_token ); $refreshed = $this->update_access_token( $access_token ); } else { $this->premium_core->google_drive->logs->get_connect_log()->add( __METHOD__ . ' Unable to fetch access token. Missing refresh token.' ); } } return $refreshed; } /** * Set our client's defer status. * * Declare whether making API calls should make the call immediately, or return a request which * can be called with ->execute(); * * This is a wrapper method, made primarily to give the developer the following comments: * * On a case by case basis, you may need to set the defer to false so that calls are executed * right away. This was discovered because calls to "get files" were returning different results * when called in the same manner. For example, sometimes we would get a * Google_Service_Drive_FileList object in return, and other times we would get a * GuzzleHttp\Psr7\Request object instead. Most likely this was caused by the calls being made * immediately before the calls to "get files", which probably changed the defer type. So, if * you experience an issue as described above, try setting the defer type to false. * * @since 1.1.0 * * @link https://github.com/googleapis/google-api-php-client/blob/v2.2.2/src/Google/Service/Resource.php#L222-L230 * @link https://github.com/googleapis/google-api-php-client/blob/v2.2.2/src/Google/Client.php#L906-L915 * * @param bool $defer True if calls should not be executed right away. */ public function set_defer( $defer ) { $this->client->setDefer( $defer ); } /** * Get our access token. * * @since 1.1.0 * * @return array Example: https://pastebin.com/ur1Jh9YM */ public function get_access_token() { $access_token = $this->premium_core->google_drive->settings->get_setting( $this->access_token_key, array() ); if ( empty( $access_token ) ) { $this->premium_core->google_drive->logs->get_connect_log()->add( __METHOD__ . ' No access token.' ); } return $access_token; } /** * Get our authentication code. * * @since 1.1.0 * * @return string */ public function get_code() { return $this->premium_core->google_drive->settings->get_setting( $this->code_key ); } /** * Get our refresh token. * * Access tokens have limited lifetimes. If your application needs access to a Google API beyond * the lifetime of a single access token, it can obtain a refresh token. A refresh token allows * your application to obtain new access tokens. Save refresh tokens in secure long-term storage * and continue to use them as long as they remain valid. Limits apply to the number of refresh * tokens that are issued per client-user combination, and per user across all clients, and these * limits are different. If your application requests enough refresh tokens to go over one of the * limits, older refresh tokens stop working. * * @since 1.1.0 * * @return string */ public function get_refresh_token() { $refresh_token = ''; $option_value = $this->get_access_token(); if ( ! empty( $option_value['refresh_token'] ) ) { $refresh_token = $option_value['refresh_token']; } else { $this->premium_core->google_drive->logs->get_connection_log()->add( 'No refresh token found.' ); } return $refresh_token; } /** * Determine whether or not we have an access token. * * @since SINCEVERSION * * @return bool */ public function has_access_token() { $access_token = $this->get_access_token(); return ! empty( $access_token ); } /** * Get our service object. * * @since 1.1.0 * * @return Google_Service_Drive object. */ public function get_service() { if ( ! is_null( $this->service ) ) { return $this->service; } $this->init(); $this->service = new Google_Service_Drive( $this->client ); return $this->service; } /** * Save our access token. * * @since 1.1.0 * * @param array $access_token An array of access token info. * @return bool */ public function update_access_token( array $access_token ) { if ( empty( $access_token['access_token'] ) || empty( $access_token['refresh_token'] ) ) { return false; } if ( isset( $access_token['error'] ) ) { return false; } return $this->premium_core->google_drive->settings->save_setting( $this->access_token_key, $access_token ); } /** * Update our authentication code. * * @since 1.1.0 * * @param string $code Authentication code. * @return bool */ public function update_code( $code ) { return $this->premium_core->google_drive->settings->save_setting( $this->code_key, $code ); } } admin/remote/google_drive_archive.php000064400000026105147600362700014017 0ustar00 */ /** * Google Drive Archive class. * * @since 1.1.0 */ class Boldgrid_Backup_Premium_Admin_Remote_Google_Drive_Archive { /** * The last error message received, if any. * * @since 1.1.0 * @var string */ public $last_error; /** * The core class object. * * @since 1.1.0 * @access private * @var Boldgrid_Backup_Admin_Core */ private $core; /** * An archive filename. * * @since 1.1.0 * @access private * @var string */ private $filename; /** * An instance of Boldgrid_Backup_Premium_Admin_Core. * * @since 1.1.0 * @access private * @var Boldgrid_Backup_Premium_Admin_Core */ private $premium_core; /** * Constructor. * * @since 1.1.0 * * @param Boldgrid_Backup_Admin_Core $core Boldgrid_Backup_Admin_Core object. * @param Boldgrid_Backup_Premium_Admin_Core $premium_core Boldgrid_Backup_Premium_Admin_Core object. * @param string $filename An archive filename. */ public function __construct( Boldgrid_Backup_Admin_Core $core, Boldgrid_Backup_Premium_Admin_Core $premium_core, $filename ) { $this->core = $core; $this->premium_core = $premium_core; $this->filename = $filename; $this->last_error = __( 'Unknown Error.', 'boldgrid-backup' ); } /** * Download a backup file. * * Had many issues following example code in the 1st @link. This method modifies that example * code based on official documentation in the 2nd @link. For example, we're manually adding the * Authorization header rather than having the google-api-php-client handle it. * * @since 1.1.0 * * @link https://github.com/googleapis/google-api-php-client/blob/master/examples/large-file-download.php * @link https://developers.google.com/drive/api/v3/manage-downloads * * @return bool True if file was downloaded successfully. */ public function download() { $log = $this->premium_core->google_drive->logs->get_download_log(); $log->add_separator(); $log->add( 'Downloading ' . $this->filename ); $client = $this->premium_core->google_drive->client->init(); if ( false === $client ) { $this->last_error = $this->premium_core->google_drive->client->last_error; $log->add( $this->last_error ); return false; } $file = $this->premium_core->google_drive->folder->get_file( $this->filename ); if ( ! $file instanceof Google_Service_Drive_DriveFile || empty( $file->id ) || empty( $file->size ) ) { $this->last_error = __( 'Unable to find backup file on Google Drive.', 'boldgrid-backup' ); $log->add( $this->last_error ); return false; } $file_size = intval( $file->size ); $local_filepath = $this->core->backup_dir->get_path_to( $this->filename ); $http = $client->authorize(); $fp = fopen( $local_filepath, 'w' ); // phpcs:ignore WordPress.WP.AlternativeFunctions $chunk_size_bytes = 1 * 1024 * 1024; // Download in 1 MB chunks $chunk_start = 0; $time_start = microtime( true ); while ( $chunk_start < $file_size ) { $chunk_end = $chunk_start + $chunk_size_bytes; try { $response = $http->get( sprintf( 'https://www.googleapis.com/drive/v3/files/%s', $file->id ), array( 'query' => array( 'alt' => 'media', ), 'headers' => array( 'Range' => sprintf( 'bytes=%s-%s', $chunk_start, $chunk_end ), ), ) ); } catch ( Exception $e ) { $this->core->archive->delete( $local_filepath ); $this->last_error = esc_html( $e->getMessage() ); $log->add( $e->getMessage() ); return false; } $chunk_start = $chunk_end + 1; /* * Example api code used getBody()->getContents() instead of getBody. The difference * between the two approaches is that getContents returns the remaining contents, so * that a second call returns nothing unless you seek the position of the stream with * rewind or seek . * * @link https://stackoverflow.com/questions/30549226/guzzlehttp-how-get-the-body-of-a-response-from-guzzle-6 */ fwrite( $fp, $response->getBody( true ) ); // phpcs:ignore WordPress.WP.AlternativeFunctions } $time_end = microtime( true ); $duration = $time_end - $time_start; $success = fclose( $fp ); // phpcs:ignore WordPress.WP.AlternativeFunctions if ( $success ) { $log->add( 'Download completed successfully.' ); $size = $this->core->wp_filesystem->size( $local_filepath ); $rate = $size / $duration; $log->add( size_format( $size, 2 ) . ' downloaded in ' . human_time_diff( $time_start, $time_end ) . '(' . size_format( $rate, 2 ) . '/s)' ); $this->core->remote->post_download( $local_filepath ); } else { $log->add( 'Download failed.' ); } return $success; } /** * Determine whether or not a backup is uploaded. * * @since 1.1.0 * * @return bool */ public function is_uploaded() { $file = $this->premium_core->google_drive->folder->get_file( $this->filename ); if ( empty( $file ) ) { $this->last_error = $this->premium_core->google_drive->folder->last_error; return false; } else { return true; } } /** * Upload an archive. * * @since 1.1.0 */ public function upload() { // Add info to the upload log. $log = $this->premium_core->google_drive->logs->get_upload_log(); $log->add_separator(); $log->add( 'Filename: ' . $this->filename ); // Add info to the connect log. $this->premium_core->google_drive->logs->get_connect_log()->add_separator(); $this->premium_core->google_drive->logs->get_connect_log()->add( __METHOD__ ); $backup_folder_id = $this->premium_core->google_drive->folder->get_backup_id(); if ( false === $backup_folder_id ) { $this->last_error = $this->premium_core->google_drive->folder->last_error; $log->add( $this->last_error ); return false; } // Setup our client and service, needed to upload. $client = $this->premium_core->google_drive->client; $client->init(); $service = $client->get_service(); // Init our archive so we can get the timestamp and filepath later below. $this->core->archive->init_by_filename( $this->filename ); $archive_size = $this->core->wp_filesystem->size( $this->core->archive->filepath ); $log->add( $this->core->archive->filepath . ' / ' . $archive_size . ' (' . size_format( $archive_size, 2 ) . ')' ); // Make sure our backup file exists. if ( ! $this->core->wp_filesystem->exists( $this->core->archive->filepath ) ) { $this->last_error = sprintf( // translators: 1 The filepath to a backup file. __( 'Archive does not exist: $1$s', 'boldgrid-backup' ), $this->core->archive->filepath ); $log->add( $this->last_error ); return false; } /* * Insert file into folder. * * @link https://developers.google.com/drive/api/v3/folder */ $file = new Google_Service_Drive_DriveFile( array( 'name' => $this->core->archive->filename, 'parents' => array( $backup_folder_id ), 'createdTime' => gmdate( 'c', $this->core->archive->timestamp ), 'properties' => array( 'createdTime' => $this->core->archive->timestamp, ), ) ); $chunk_size_bytes = 1 * 1024 * 1024; // Call the API with the media upload, defer so it doesn't immediately return. $client->client->setDefer( true ); /* * The supportsAllDrives parameter will be valid until June 1, 2020. After June 1, 2020, all * applications will be assumed to support shared drives. * * @link https://developers.google.com/drive/api/v3/enable-shareddrives */ $request = $service->files->create( $file, array( 'supportsAllDrives' => true ) ); // Create a media file upload to represent our upload process. $media = new Google_Http_MediaFileUpload( $client->client, $request, 'application/zip', null, true, $chunk_size_bytes ); $media->setFileSize( $archive_size ); // Upload the various chunks. $status will be false until the process is complete. $status = false; // Make sure we can open our backup file before we try to upload it. $handle = fopen( $this->core->archive->filepath, 'rb' ); // phpcs:ignore WordPress.WP.AlternativeFunctions if ( false === $handle ) { $this->last_error = sprintf( // translators: 1 The filepath to a backup file. __( 'Unable to open archive: $1$s', 'boldgrid-backup' ), $this->core->archive->filepath ); $log->add( $this->last_error ); return false; } $log->add( 'Beginning to upload file...' ); $start_time = microtime( true ); while ( ! $status && ! feof( $handle ) ) { /* * Read until you get $chunk_size_bytes from TESTFILE. fread will never return more than * 8192 bytes if the stream is read buffered and it does not represent a plain file. An * example of a read buffered file is when reading from a URL */ $chunk = $this->read_big_chunk( $handle, $chunk_size_bytes ); try { $status = $media->nextChunk( $chunk ); } catch ( Google_Service_Exception $e ) { $this->last_error = __( 'Unable to upload file to Google Drive', 'boldgrid-backup' ) . ': ' . $e->getCode(); $log->add( $this->last_error ); } } fclose( $handle ); // phpcs:ignore WordPress.WP.AlternativeFunctions // Calculate how long the upload took and the bytes per second average. $end_time = microtime( true ); $upload_speed = $archive_size / ( $end_time - $start_time ); $log->add( 'File upload complete! Uploaded ' . size_format( $archive_size, 2 ) . ' in ' . human_time_diff( $start_time, $end_time ) . ' (' . size_format( $upload_speed, 2 ) . '/s)' ); /* * The final value of $status will be the data from the API for the object that's been uploaded. * * Example $status on success: https://pastebin.com/SZxwHwNC */ $result = false; if ( false !== $status ) { $result = $status; $log->add( 'Enforcing retention...' ); $this->premium_core->google_drive->folder->enforce_retention( $backup_folder_id ); $log->add( 'Retention enforcement complete.' ); } $success = false !== $result; $log->add( __METHOD__ . ' Method completed. Success? ' . ( $success ? 'Yes' : 'No' ) ); return $success; } /** * Read a big chunk. * * @since 1.1.0 * @access private * * @link https://github.com/googleapis/google-api-php-client/blob/f88a98dbaac0207e177419a15214ab4fcf30c47a/examples/large-file-upload.php#L133 * * @param resource $handle File handle. * @param int $chunk_size Chunk size. * @return string */ private function read_big_chunk( $handle, $chunk_size ) { $byte_count = 0; $giant_chunk = ''; while ( ! feof( $handle ) ) { /* * fread will never return more than 8192 bytes if the stream is read buffered and it * does not represent a plain file */ $chunk = fread( $handle, 8192 ); // phpcs:ignore WordPress.WP.AlternativeFunctions $byte_count += strlen( $chunk ); $giant_chunk .= $chunk; if ( $byte_count >= $chunk_size ) { return $giant_chunk; } } return $giant_chunk; } } admin/remote/amazon_s3_bucket.php000064400000014067147600362700013104 0ustar00 */ /** * Amazon S3 Bucket class. * * @since 1.0.0 */ class Boldgrid_Backup_Premium_Admin_Remote_Amazon_S3_Bucket { /** * Errors. * * @since 1.0.0 * @var array */ public $errors = array(); /** * The core class object. * * @since 1.0.0 * @access private * @var Boldgrid_Backup_Admin_Core */ private $core; /** * An instance of Boldgrid_Backup_Premium_Admin_Core. * * @since 1.0.0 * @access private * @var Boldgrid_Backup_Premium_Admin_Core */ private $premium_core; /** * Constructor. * * @since 1.0.0 * * @param Boldgrid_Backup_Admin_Core $core Boldgrid_Backup_Admin_Core object. * @param Boldgrid_Backup_Premium_Admin_Core $premium_core Boldgrid_Backup_Premium_Admin_Core object. */ public function __construct( Boldgrid_Backup_Admin_Core $core, Boldgrid_Backup_Premium_Admin_Core $premium_core ) { $this->core = $core; $this->premium_core = $premium_core; } /** * Create a bucket. * * If the bucket already exists and this user owns it, we'll return true. * * @since 1.0.0 * * @param string $bucket_id Bucket id. * @return bool */ public function create( $bucket_id ) { // Validate bucket name. $valid_name = $this->premium_core->amazon_s3->client->isBucketDnsCompatible( $bucket_id ); if ( ! $valid_name ) { $this->premium_core->amazon_s3->errors[] = __( 'Invalid Bucket ID. Bucket name must be between 3 and 63 characters long, must not end with a dash or period, and must not use any special characters.', 'boldgrid-backup' ); return false; } try { $this->premium_core->amazon_s3->client->createBucket( array( 'Bucket' => $bucket_id, ) ); } catch ( Aws\S3\Exception\BucketAlreadyOwnedByYouException $e ) { return true; } catch ( Aws\S3\Exception\BucketAlreadyExistsException $e ) { $this->premium_core->amazon_s3->errors[] = sprintf( // Translators: 1: Bucket id. __( 'Bucket ID %1$s already exist. Please try another Bucket ID.', 'boldgrid-backup' ), '' . esc_html( $bucket_id ) . '' ); return false; } catch ( Exception $e ) { $this->premium_core->amazon_s3->errors[] = __( 'Unknown error when attempting to create bucket:', 'boldgrid-backup' ) . ' - ' . $e->getMessage(); return false; } return true; } /** * Delete bucket transients. * * @since 1.5.4 */ public function delete_transients() { $bucket_id = $this->premium_core->amazon_s3->bucket_id; $transient = sprintf( 'boldgrid_backup_s3_bucket_w_headers_%1$s', $bucket_id ); delete_transient( $transient ); $transient = sprintf( 'boldgrid_backup_s3_bucket_%1$s', $bucket_id ); delete_transient( $transient ); } /** * Get the contents of a bucket. * * @since 1.0.0 * * @param string $bucket_id Bucket id. * @param bool $include_headers Include headers. * @param bool $use_transient Whether or not to first try to get our * bucket from the transient. In some situations * we need fresh data, and we can pass true * to get fresh data from Amazon. * @return array https://pastebin.com/uVkx8t5A */ public function get( $bucket_id = null, $include_headers = false, $use_transient = true ) { // Shorten for readability. $s3 = $this->premium_core->amazon_s3; $s3->set_client(); if ( empty( $s3->client ) ) { return array(); } if ( ! empty( $bucket_id ) ) { $s3->set_bucket_id( $bucket_id ); } if ( ! $s3->client->isBucketDnsCompatible( $s3->bucket_id ) ) { return array(); } $transient_name = sprintf( 'boldgrid_backup_s3_bucket_%1$s%2$s', $include_headers ? 'w_headers_' : '', $s3->bucket_id ); // Save resources and try to get bucket contents from transient. if ( $use_transient ) { $bucket_contents = get_transient( $transient_name ); if ( false !== $bucket_contents ) { return $bucket_contents; } } $bucket_contents = array(); // If the bucket does not exist, return an empty bucket. try { $iterator = $s3->client->getIterator( 'ListObjects', array( 'Bucket' => $s3->bucket_id, ) ); foreach ( $iterator as $object ) { if ( ! $this->core->archive->is_site_archive( $object['Key'] ) ) { continue; } if ( $include_headers ) { $object['Headers'] = $s3->get_headers( $object['Key'] ); } $bucket_contents[] = $object; } } catch ( Aws\S3\Exception\NoSuchBucketException $e ) { return array(); } set_transient( $transient_name, $bucket_contents, 5 * MINUTE_IN_SECONDS ); return $bucket_contents; } /** * Get an item from the bucket. * * @since 1.5.4 * * @param string $key Key. * @return mixed Array on success, false on failure. */ public function get_item( $key ) { $bucket_contents = $this->get(); foreach ( $bucket_contents as $item ) { if ( $item['Key'] === $key ) { return $item; } } return false; } /** * Validate a local backup matches the remove backup. * * @since 1.5.4 * * @param string $key Key. */ public function validate_backup( $key ) { $item = $this->get_item( $key ); $local_path = $this->core->backup_dir->get_path_to( $key ); $local = $this->core->wp_filesystem->dirlist( $local_path ); $remote_size = ! empty( $item['Size'] ) ? intval( $item['Size'] ) : null; $local_size = ! empty( $local[ $key ]['size'] ) ? intval( $local[ $key ]['size'] ) : null; if ( empty( $remote_size ) || empty( $local_size ) ) { return false; } $same_size = $remote_size === $local_size; if ( ! $same_size ) { $this->errors[] = sprintf( // Translators: 1: Local file size, 2: Remote file size. __( 'Downloaded filesize (%1$s) does not match remote filesize (%2$s).', 'boldgrid-backup' ), $local_size, $remote_size ); } return $same_size; } } admin/remote/amazon_s3_backups_page.php000064400000007771147600362700014257 0ustar00 */ /** * Amazon S3 Backups Page class. * * @since 1.0.0 */ class Boldgrid_Backup_Premium_Admin_Remote_Amazon_S3_Backups_Page { /** * An instance of Boldgrid_Backup_Admin_Core. * * @since 1.0.0 * @access private * @var Boldgrid_Backup_Premium_Admin_Core */ private $core; /** * An instance of Boldgrid_Backup_Premium_Admin_Core. * * @since 1.0.0 * @access private * @var Boldgrid_Backup_Premium_Admin_Core */ private $premium_core; /** * Constructor. * * @since 1.0.0 * * @param Boldgrid_Backup_Admin_Core $core Boldgrid_Backup_Admin_Core object. * @param Boldgrid_Backup_Premium_Admin_Core $premium_core Boldgrid_Backup_Premium_Admin_Core object. */ public function __construct( Boldgrid_Backup_Admin_Core $core, Boldgrid_Backup_Premium_Admin_Core $premium_core ) { $this->core = $core; $this->premium_core = $premium_core; } /** * Enqueue scripts. * * @since 1.5.4 * * @param string $hook Hook name. */ public function admin_enqueue_scripts( $hook ) { if ( 'total-upkeep_page_boldgrid-backup' !== $hook ) { return; } $handle = 'boldgrid-backup-premium-admin-amazon-s3'; wp_register_script( $handle, plugin_dir_url( dirname( __FILE__ ) ) . 'js/boldgrid-backup-premium-admin-amazon-s3.js', array( 'jquery' ), BOLDGRID_BACKUP_PREMIUM_VERSION, true ); $translation = array( 'downloading' => __( 'Downloading', 'boldgrid-backup' ), ); wp_localize_script( $handle, 'boldgrid_backup_premium_admin_amazon_s3', $translation ); wp_enqueue_script( $handle ); } /** * If a local backup is on Amazon S3 too, update verbiage to reflect. * * @since 1.5.4 * * @param array $locations Locations. * @param string $filepath File path. * @return array */ public function backup_locations( array $locations, $filepath ) { if ( $this->premium_core->amazon_s3->in_bucket( null, $filepath ) ) { $locations[] = __( 'Amazon S3', 'boldgrid-backup' ); } return $locations; } /** * Handle the ajax request to download an Amazon S3 backup locally. * * @since 1.5.4 */ public function wp_ajax_download() { $error = __( 'Unable to download backup from Amazon S3', 'bolgrid-bakcup' ); // Validation, user role. if ( ! current_user_can( 'update_plugins' ) ) { $this->core->notice->add_user_notice( sprintf( $error . ': ' . __( 'Permission denied.', 'boldgrid-backup' ) ), 'notice notice-error' ); wp_send_json_error(); } // Validation, nonce. if ( ! $this->core->archive_details->validate_nonce() ) { $this->core->notice->add_user_notice( sprintf( $error . ': ' . __( 'Invalid nonce.', 'boldgrid-backup' ) ), 'notice notice-error' ); wp_send_json_error(); } // Validation, $_POST data. $key = ! empty( $_POST['filename'] ) ? $_POST['filename'] : false; // phpcs:ignore if ( empty( $key ) ) { $this->core->notice->add_user_notice( sprintf( $error . ': ' . __( 'Invalid key.', 'boldgrid-backup' ) ), 'notice notice-error' ); wp_send_json_error(); } $result = $this->premium_core->amazon_s3->download( $key ); if ( $result ) { $this->core->notice->add_user_notice( sprintf( // Translators: 1: Key, 2: Title. __( '

%2$s

Backup file %1$s successfully downloaded from Amazon S3.

', 'boldgrid-backup' ), /* 1 */ $key, /* 2 */ BOLDGRID_BACKUP_PREMIUM_TITLE . ' - ' . __( 'Amazon S3 Download', 'boldgrid-backup' ) ), 'notice notice-success' ); wp_send_json_success(); } if ( ! empty( $this->premium_core->amazon_s3->errors ) ) { $this->core->notice->add_user_notice( implode( '
', $this->premium_core->amazon_s3->errors ), 'notice notice-error' ); wp_send_json_error(); } wp_send_json_error(); } } admin/remote/amazon_s3.php000064400000057772147600362700011561 0ustar00 */ use Aws\S3\S3Client; use Aws\S3\Model\MultipartUpload\UploadBuilder; use Aws\Common\Exception\MultipartUploadException; use Aws\Common\Model\MultipartUpload\AbstractTransfer; /** * Amazon S3 class. * * @since 1.0.0 */ class Boldgrid_Backup_Premium_Admin_Remote_Amazon_S3 { /** * Backups page. * * @since 1.0.0 * @var Boldgrid_Backup_Premium_Admin_Remote_Amazon_S3_Backups_Page. */ public $backups_page; /** * Bucket object. * * @since 1.0.0 * @var Boldgrid_Backup_Premium_Admin_Remote_Amazon_S3_Bucket. */ public $bucket; /** * Bucket id. * * @since 1.0.0 * @var string $bucket_id */ public $bucket_id = null; /** * Our S3 client. * * @since 1.0.0 * @var object $client */ public $client = null; /** * Errors. * * @since 1.5.4 * @var array */ public $errors = array(); /** * Nickname. * * @since 1.0.0 * @var string */ public $nickname; /** * Title. * * @since 1.0.0 * @var string */ public $title = 'Amazon S3'; /** * Title attribute. * * Used as a title tag attribute. * * @since 1.0.0 * @var string */ public $title_attr; /** * The core class object. * * @since 1.0.0 * @access private * @var Boldgrid_Backup_Admin_Core */ private $core; /** * An instance of Boldgrid_Backup_Premium_Admin_Core. * * @since 1.0.0 * @access private * @var Boldgrid_Backup_Premium_Admin_Core */ private $premium_core; /** * Retention count. * * @since 1.0.0 * @access private * @var int $retention_count */ private $retention_count = 5; /** * Key. * * @since 1.0.0 * @access private * @var string $key */ private $key = null; /** * Secret, I'm not telling. * * @since 1.0.0 * @access private * @var string $secret */ private $secret = null; /** * Constructor. * * @since 1.0.0 * * @param Boldgrid_Backup_Admin_Core $core Boldgrid_Backup_Admin_Core object. * @param Boldgrid_Backup_Premium_Admin_Core $premium_core Boldgrid_Backup_Premium_Admin_Core object. */ public function __construct( Boldgrid_Backup_Admin_Core $core, Boldgrid_Backup_Premium_Admin_Core $premium_core ) { $this->core = $core; $this->premium_core = $premium_core; $settings = $this->core->settings->get_settings(); $this->key = ! empty( $settings['remote']['amazon_s3']['key'] ) ? $settings['remote']['amazon_s3']['key'] : $this->key; $this->secret = ! empty( $settings['remote']['amazon_s3']['secret'] ) ? $settings['remote']['amazon_s3']['secret'] : $this->secret; $this->bucket_id = ! empty( $settings['remote']['amazon_s3']['bucket_id'] ) ? $settings['remote']['amazon_s3']['bucket_id'] : $this->create_unique_bucket(); $this->retention_count = ! empty( $settings['remote']['amazon_s3']['retention_count'] ) ? $settings['remote']['amazon_s3']['retention_count'] : $this->retention_count; $this->nickname = ! empty( $settings['remote']['amazon_s3']['nickname'] ) ? $settings['remote']['amazon_s3']['nickname'] : $this->nickname; $this->title_attr = $this->title . ': ' . $this->bucket_id; if ( ! empty( $this->nickname ) ) { $this->title = $this->nickname; } $this->backups_page = new Boldgrid_Backup_Premium_Admin_Remote_Amazon_S3_Backups_Page( $this->core, $this->premium_core ); $this->bucket = new Boldgrid_Backup_Premium_Admin_Remote_Amazon_S3_Bucket( $this->core, $this->premium_core ); } /** * Add menu items. * * @since 1.0 */ public function add_menu_items() { $capability = 'administrator'; add_submenu_page( 'boldgrid-backup-settings', __( 'Amazon S3 Settings', 'boldgrid-backup' ), __( 'Amazon S3 Settings', 'boldgrid-backup' ), $capability, 'boldgrid-backup-amazon-s3', array( $this, 'submenu_page', ) ); } /** * Upload a backup via an ajax request. * * This is done via the archive details of a single archive. * * @since 1.0.0 */ public function ajax_upload() { if ( ! current_user_can( 'update_plugins' ) ) { wp_send_json_error( __( 'Permission denied.', 'boldgrid-backup' ) ); } if ( ! $this->core->archive_details->validate_nonce() ) { wp_send_json_error( __( 'Invalid nonce.', 'boldgrid-backup' ) ); } $filename = ! empty( $_POST['filename'] ) ? $_POST['filename'] : false; // phpcs:ignore $filepath = $this->core->backup_dir->get_path_to( $filename ); if ( empty( $filename ) || ! $this->core->wp_filesystem->exists( $filepath ) ) { wp_send_json_error( __( 'Invalid archive filename.', 'boldgrid-backup' ) ); } // @todo Temp code to get more details about any errors. add_action( 'shutdown', function() { $last_error = error_get_last(); // If there's no error or this is not fatal, abort. if ( empty( $last_error ) || 1 !== $last_error['type'] ) { return; } $message = sprintf( '%1$s: %2$s in %3$s on line %4$s', __( 'Fatal error', 'boldgrid-backup' ), $last_error['message'], $last_error['file'], $last_error['line'] ); wp_send_json_error( $message ); } ); $upload_result = $this->upload( $filepath ); if ( true === $upload_result ) { wp_send_json_success( $upload_result ); } else { wp_send_json_error( $upload_result ); } } /** * Create a unique bucket id. * * When you delete a bucket, Amazon gives you the following message: * Amazon S3 buckets are unique. If you delete this bucket, you may lose the * bucket name to another AWS user. * * @since 1.0.0 */ public function create_unique_bucket() { $url = parse_url( get_site_url() ); // phpcs:ignore WordPress.WP.AlternativeFunctions $bucket_parts[] = 'boldgrid-backup'; $bucket_parts[] = $url['host']; $bucket_id = implode( '-', $bucket_parts ); return $bucket_id; } /** * Download a backup from Amazon S3 and put it in backups folder. * * @since 1.5.4 * * @param string $key Key. * @param string $bucket_id Bucket id. * @return bool */ public function download( $key, $bucket_id = null ) { $bucket_id = is_null( $bucket_id ) ? $this->bucket_id : $bucket_id; if ( empty( $bucket_id ) || empty( $key ) ) { return false; } $this->set_client(); $path = $this->core->backup_dir->get_path_to( $key ); // Example $result: https://pastebin.com/thyw9jrh . $result = $this->client->getObject( array( 'Bucket' => $bucket_id, 'Key' => $key, 'SaveAs' => $path, ) ); if ( empty( $result['ContentLength'] ) ) { return false; } // Change the timestamp of the backup file. $last_modified = ! empty( $result['Metadata']['last_modified'] ) ? $result['Metadata']['last_modified'] : null; if ( ! empty( $last_modified ) ) { $this->core->wp_filesystem->touch( $path, $last_modified ); } // If the backup file is not valid, delete it. $valid = $this->bucket->validate_backup( $key ); if ( ! $valid && ! empty( $this->bucket->errors ) ) { $this->errors = array_merge( $this->errors, $this->bucket->errors ); $this->core->wp_filesystem->delete( $path ); } $this->core->remote->post_download( $path ); return $valid; } /** * Enforce retention. * * @since 1.0.0 */ public function enforce_retention() { if ( empty( $this->retention_count ) ) { return; } $bucket_contents = $this->bucket->get( $this->bucket_id, true, false ); if ( empty( $bucket_contents ) ) { return; } // Remove files from bucket list that are not our backups. foreach ( $bucket_contents as $key => $item ) { if ( empty( $item['Headers']['Metadata']['is_boldgrid_backup'] ) || 'true' !== $item['Headers']['Metadata']['is_boldgrid_backup'] ) { unset( $bucket_contents[ $key ] ); } } // Sort by timestamp desc. usort( $bucket_contents, function( $a, $b ) { return $a['Headers']['Metadata']['last_modified'] < $b['Headers']['Metadata']['last_modified'] ? 1 : -1; } ); // Do the deleting. $count = 0; foreach ( $bucket_contents as $item ) { $count++; if ( $count <= $this->retention_count ) { continue; } $this->client->deleteObject( array( 'Bucket' => $this->bucket_id, 'Key' => $item['Key'], ) ); /** * Remote file deleted due to remote retention settings. * * @since 1.5.3 */ do_action( 'boldgrid_backup_remote_retention_deleted', 'Amazon S3', // Translators: 1: Bucket id, 2: Key. sprintf( __( 'Bucket: %1$s, Key: %2$s', 'boldgrid-backup' ), $this->bucket_id, $item['Key'] ) ); } $this->bucket->delete_transients(); } /** * Add Amazon S3 backups to the list of all backups. * * @since 1.0.0 */ public function filter_get_all() { $bucket_contents = $this->bucket->get( $this->bucket_id, true ); if ( empty( $bucket_contents ) ) { return; } foreach ( $bucket_contents as $item ) { $filename = $item['Key']; $is_this_site = false !== strpos( $item['Key'], get_site_option( 'boldgrid_backup_id' ) ); $is_backup = ! empty( $item['Headers']['Metadata']['is_boldgrid_backup'] ) && 'true' === $item['Headers']['Metadata']['is_boldgrid_backup']; if ( ! $is_backup || ! $is_this_site ) { continue; } $backup = array( 'filename' => $filename, 'last_modified' => $item['Headers']['Metadata']['last_modified'], 'size' => $item['Size'], 'locations' => array( array( 'title' => $this->title, 'title_attr' => $this->title_attr, 'on_remote_server' => true, ), ), ); $this->core->archives_all->add( $backup ); } } /** * Get the contents of a bucket. * * @since 1.0.0 * * @param string $bucket_id Bucket id. * @param bool $include_headers Include headers. * @return array https://pastebin.com/uVkx8t5A */ public function get_bucket( $bucket_id, $include_headers = false ) { $this->set_client(); $this->set_bucket_id( $bucket_id ); $bucket_contents = array(); $transient_name = sprintf( 'boldgrid_backup_s3_bucket_%1$s%2$s', $include_headers ? 'w_headers_' : '', $this->bucket_id ); $bucket_contents = get_transient( $transient_name ); if ( false !== $bucket_contents ) { return $bucket_contents; } // If the bucket does not exist, return an empty bucket. try { $iterator = $this->client->getIterator( 'ListObjects', array( 'Bucket' => $this->bucket_id, ) ); foreach ( $iterator as $object ) { if ( $include_headers ) { $object['Headers'] = $this->get_headers( $object['Key'] ); } $bucket_contents[] = $object; } } catch ( Aws\S3\Exception\NoSuchBucketException $e ) { return array(); } set_transient( $transient_name, $bucket_contents, 5 * MINUTE_IN_SECONDS ); return $bucket_contents; } /** * Get settings. * * @since 1.0.0 */ public function get_details() { $settings = $this->core->settings->get_settings(); $details = array( 'title' => __( 'Amazon S3', 'boldgrid-backup' ), 'key' => 'amazon_s3', 'configure' => 'admin.php?page=boldgrid-backup-amazon-s3', 'is_setup' => $this->is_setup(), 'enabled' => ! empty( $settings['remote']['amazon_s3']['enabled'] ) && $settings['remote']['amazon_s3']['enabled'] && $this->is_setup(), ); return $details; } /** * Get the headers of an object. * * @since 1.0.0 * * @param string $key Key. */ public function get_headers( $key ) { $params = array( 'Bucket' => $this->bucket_id, 'Key' => $key, ); $headers = $this->client->headObject( $params ); return $headers->toArray(); } /** * Determine if a file exists in a bucket. * * @since 1.0.0 * * @param string $bucket_id Bucket id. * @param string $filepath File path. * @return bool */ public function in_bucket( $bucket_id, $filepath ) { $this->set_client(); if ( empty( $this->client ) ) { return false; } $bucket_contents = $this->bucket->get( $bucket_id ); $filename = basename( $filepath ); foreach ( $bucket_contents as $item ) { if ( $item['Key'] === $filename ) { return true; } } return false; } /** * Set our client. * * @since 1.0.0 * * @param string $key Key. * @param string $secret Secret. */ public function set_client( $key = null, $secret = null ) { $key = empty( $key ) ? $this->key : $key; $secret = empty( $secret ) ? $this->secret : $secret; if ( empty( $key ) || empty( $secret ) ) { return false; } $credentials = new \Aws\Credentials\Credentials( $key, $secret ); /* * Define our region. * * When using v2 of the aws-sdk-php, no region was defined. As such, all backups should have * defaulted to us-east-1. * @link https://github.com/aws/aws-sdk-php/blob/2.8.31/src/Aws/Common/HostNameUtils.php#L26 * * @todo Give users the option to choose. */ $region = 'us-east-1'; $this->client = new \Aws\S3\S3Client( array( /* * A "version" configuration value is required. Specifying a version constraint ensures * that your code will not be affected by a breaking change made to the service. * @link https://docs.aws.amazon.com/sdk-for-php/v3/developer-guide/guide_configuration.html#cfg-version */ 'version' => '2006-03-01', 'region' => $region, 'credentials' => $credentials, ) ); } /** * Return data about a particular archive in Amazon S3. * * For example, if you're looking at a single backup, we need to know if it * already exists in our Amazon S3 account. * * This method will return an array of information useful to the single * archive page. * * @since 1.0.0 * * @param string $filepath File path. */ public function single_archive_remote_option( $filepath ) { $allow_upload = $this->is_setup(); $uploaded = $allow_upload && $this->in_bucket( null, $filepath ); $storage = array( 'id' => 'amazon_s3', 'title' => $this->title, 'title_attr' => $this->title_attr, 'uploaded' => $uploaded, 'allow_upload' => $allow_upload, 'is_setup' => $allow_upload, ); $this->core->archive_details->remote_storage_li[] = $storage; } /** * Generate the submenu page for our Amazon S3 Settings page. * * @since 1.0.0 */ public function submenu_page() { wp_enqueue_style( 'boldgrid-backup-admin-hide-all' ); $this->submenu_page_save(); $settings = $this->core->settings->get_settings(); $key = ! empty( $settings['remote']['amazon_s3']['key'] ) ? $settings['remote']['amazon_s3']['key'] : null; $secret = ! empty( $settings['remote']['amazon_s3']['secret'] ) ? $settings['remote']['amazon_s3']['secret'] : null; $bucket_id = ! empty( $settings['remote']['amazon_s3']['bucket_id'] ) ? $settings['remote']['amazon_s3']['bucket_id'] : $this->bucket_id; $retention_count = ! empty( $settings['remote']['amazon_s3']['retention_count'] ) ? $settings['remote']['amazon_s3']['retention_count'] : $this->retention_count; $nickname = ! empty( $settings['remote']['amazon_s3']['nickname'] ) ? $settings['remote']['amazon_s3']['nickname'] : ''; include BOLDGRID_BACKUP_PREMIUM_PATH . '/admin/partials/remote/amazon-s3.php'; } /** * Process the user's request to update their Amazon S3 settings. * * @since 1.0.0 */ public function submenu_page_save() { if ( ! current_user_can( 'update_plugins' ) ) { return false; } if ( empty( $_POST ) ) { // phpcs:ignore return false; } $this->bucket->delete_transients(); $settings = $this->core->settings->get_settings(); if ( ! isset( $settings['remote']['amazon_s3'] ) || ! is_array( $settings['remote']['amazon_s3'] ) ) { $settings['remote']['amazon_s3'] = array(); } /* * If the user has requested to delete all their settings, do that now * and return. */ if ( __( 'Delete settings', 'boldgrid-backup' ) === $_POST['submit'] ) { // phpcs:ignore $settings['remote']['amazon_s3'] = array(); update_site_option( 'boldgrid_backup_settings', $settings ); $this->key = null; $this->secret = null; $this->bucket_id = null; $this->retention_count = null; $this->nickname = null; do_action( 'boldgrid_backup_notice', __( 'Settings saved.', 'boldgrid-backup' ), 'notice updated is-dismissible' ); return; } $key = ! empty( $_POST['key'] ) ? $_POST['key'] : null; // phpcs:ignore $secret = ! empty( $_POST['secret'] ) ? $_POST['secret'] : null; // phpcs:ignore // If no bucket_id submitted, we had a valid bucket_id created in the constructor. $bucket_id = ! empty( $_POST['bucket_id'] ) ? $_POST['bucket_id'] : $this->bucket_id; // phpcs:ignore $retention_count = ! empty( $_POST['retention_count'] ) && is_numeric( $_POST['retention_count'] ) ? $_POST['retention_count'] : $this->retention_count; // phpcs:ignore $nickname = ! empty( $_POST['nickname'] ) ? stripslashes( $_POST['nickname'] ) : null; // phpcs:ignore echo $this->core->elements['long_checking_creds']; // phpcs:ignore if ( ob_get_level() > 0 ) { ob_flush(); } flush(); $valid_credentials = $this->is_valid_credentials( $key, $secret ); if ( $valid_credentials ) { $settings['remote']['amazon_s3']['key'] = $key; $settings['remote']['amazon_s3']['secret'] = $secret; $this->key = $key; $this->secret = $secret; } else { $this->errors[] = __( 'Invalid Access Key Id and / or Secret Access Key.', 'boldgrid-backup' ); } if ( $this->bucket->create( $bucket_id ) ) { $settings['remote']['amazon_s3']['bucket_id'] = $bucket_id; $this->bucket_id = $bucket_id; } $settings['remote']['amazon_s3']['retention_count'] = $retention_count; $settings['remote']['amazon_s3']['nickname'] = $nickname; if ( ! empty( $this->errors ) ) { do_action( 'boldgrid_backup_notice', implode( '

', $this->errors ) ); } else { update_site_option( 'boldgrid_backup_settings', $settings ); do_action( 'boldgrid_backup_notice', __( 'Settings saved.', 'boldgrid-backup' ), 'notice updated is-dismissible' ); } } /** * Set our bucket id. * * @since 1.0.0 * * @param string $bucket_id Bucket id. */ public function set_bucket_id( $bucket_id ) { if ( empty( $bucket_id ) ) { return; } $this->bucket_id = $bucket_id; } /** * Upload a backup file. * * @since 1.0.0 * * @param string $filepath File path. * @return mixed True on success, error message on failure. */ public function upload( $filepath ) { $this->set_client(); if ( ! $this->core->wp_filesystem->exists( $filepath ) ) { // Translators: 1: File path. return sprintf( __( 'Failed to upload, filepath does not exist: %1$s', 'boldgrid-backup' ), $filepath ); } $key = basename( $filepath ); /* * When files are uploaded to Amazon S3, the LastModified is the time * the file was uploaded, not the time the file was last modified. When * we enforce retention, we'll need to know when the backup archive was * created, not when it was uploaded to Amazon S3. */ $archive_data = $this->core->archive_log->get_by_zip( $filepath ); $last_modified = ! empty( $archive_data['lastmodunix'] ) ? $archive_data['lastmodunix'] : $this->core->wp_filesystem->mtime( $filepath ); // Make sure our bucket is created. if ( ! $this->bucket->create( $this->bucket_id ) ) { return sprintf( // Translators: 1: URL. __( 'Unable to create bucket! Please go to your settings page to configure Amazon S3.', 'boldgrid-backup' ), 'admin.php?page=boldgrid-backup-settings' ); } try { $uploader = new \Aws\S3\MultipartUploader( $this->client, fopen( $filepath, 'rb' ), // phpcs:ignore WordPress.WP.AlternativeFunctions array( 'bucket' => $this->bucket_id, 'key' => $key, /* * Before Initiate. * * Set our custom metadata. * * @link https://docs.aws.amazon.com/aws-sdk-php/v3/api/api-s3-2006-03-01.html#createmultipartupload * @link https://docs.aws.amazon.com/sdk-for-php/v3/developer-guide/s3-multipart-upload.html * * @var $command Aws\Command A CreateMultipartUpload operation. */ 'before_initiate' => function( $command ) use ( $last_modified ) { $command['Metadata'] = array( 'is_boldgrid_backup' => 'true', 'last_modified' => $last_modified, ); }, ) ); } catch ( Exception $e ) { return __( 'Failed to initialize', 'boldgrid-backup' ); } try { $uploader->upload(); $this->enforce_retention(); $this->bucket->delete_transients(); } catch ( MultipartUploadException $e ) { $uploader->abort(); return __( 'Failed to upload.', 'boldgrid-inspirations' ); } /** * File uploaded to remote storage location. * * @since 1.5.3 * * @param string Amazon S3 * @param string $filepath */ do_action( 'boldgrid_backup_remote_uploaded', 'Amazon S3', $filepath ); return true; } /** * Upload a file. * * The jobs queue will call this method to upload a file. * * @since 1.0.0 * * @param string $filepath File path. */ public function upload_post_archiving( $filepath ) { $success = $this->upload( $filepath ); return $success; } /** * Determine if Amazon S3 is setup properly. * * Hook into "boldgrid_backup_is_setup_amazon_s3". */ public function is_setup() { return $this->is_valid_credentials( $this->key, $this->secret ) && $this->client->isBucketDnsCompatible( $this->bucket_id ); } /** * Determine if Amazon S3 is setup properly. * * This method is ran within an ajax request. */ public function is_setup_ajax() { if ( ! current_user_can( 'update_plugins' ) ) { wp_send_json_error( __( 'Permission denied.', 'boldgrid-backup' ) ); } if ( ! check_ajax_referer( 'boldgrid_backup_settings', 'security', false ) ) { wp_send_json_error( __( 'Invalid nonce.', 'boldgrid-backup' ) ); } $settings = $this->core->settings->get_settings(); $location = $this->get_details(); $tr = include BOLDGRID_BACKUP_PATH . '/admin/partials/settings/storage-location.php'; if ( $this->is_setup() ) { wp_send_json_success( $tr ); } else { wp_send_json_error( $tr ); } } /** * Determine if credentials are valid. * * @since 1.0.0 * * @param string $key Key. * @param string $secret Secret. * @return bool */ public function is_valid_credentials( $key, $secret ) { if ( empty( $key ) || empty( $secret ) ) { return false; } $this->set_client( $key, $secret ); try { $this->client->listBuckets(); return true; } catch ( Exception $e ) { return false; } } /** * Actions to take after a backup file has been generated. * * @since 1.0.0 * * @param array $info Info. */ public function post_archive_files( array $info ) { /* * We only want to add this to the jobs queue if we're in the middle of * an automatic backup. If the user simply clicked on "Backup site now", * we don't want to automatically send the backup to Amazon, there's a * button for that. */ if ( ! $this->core->doing_cron ) { return; } if ( ! $this->core->remote->is_enabled( 'amazon_s3' ) || $info['dryrun'] || ! $info['save'] ) { return; } $args = array( 'filepath' => $info['filepath'], 'action' => 'boldgrid_backup_amazon_s3_upload_post_archive', 'action_data' => $info['filepath'], 'action_title' => __( 'Upload backup file to Amazon S3', 'boldgrid-backup' ), ); $this->core->jobs->add( $args ); } /** * Register Amazon S3 as a storage location. * * When you go to the settings page and see a list of storage providers, each of those storage providers needs to * hook into the "boldgrid_backup_register_storage_location" filter and add themselves. * * @since 1.0.0 * * @param array $storage_locations Storage locations. * @return array */ public function register_storage_location( array $storage_locations ) { $storage_locations[] = $this->get_details(); return $storage_locations; } } admin/remote/s3_transient.php000064400000011503147600362700012261 0ustar00 */ /** * S3 Transient class. * * @since 1.2.0 */ class Boldgrid_Backup_Premium_Admin_Remote_S3_Transient { /** * The name of the transient storing our buckets. * * @since 1.2.0 * @var string * @access private */ private $name_buckets; /** * Our provider. * * @since 1.2.0 * @var Boldgrid_Backup_Premium_Admin_Remote_S3_Provider * @access private */ private $provider; /** * Constructor. * * @since 1.2.0 * * @param Boldgrid_Backup_Premium_Admin_Remote_S3_Provider $provider */ public function __construct( Boldgrid_Backup_Premium_Admin_Remote_S3_Provider $provider ) { $this->provider = $provider; $this->name_buckets = 'boldgrid_backup_s3_' . $this->provider->get_key() . '_buckets'; } /** * Delete all of our provider's transients. * * @since 1.2.0 * * @global wpdb @wpdb */ public function delete_all() { global $wpdb; $transients = $wpdb->get_results( $wpdb->prepare( "SELECT `option_name` FROM $wpdb->options WHERE `option_name` LIKE %s;", '_transient_boldgrid_backup_s3_' . $this->provider->get_key() . '_%' ) ); foreach ( $transients as $transient ) { $transient_name = str_replace( '_transient_', '', $transient->option_name ); delete_transient( $transient_name ); } } /** * Delete our backups from transients. * * @since 1.2.0 * * @param string $bucket_id A bucket id. */ public function delete_backups( $bucket_id ) { $transient_name = $this->get_name_backups( $bucket_id ); delete_transient( $transient_name ); } /** * Delete our buckets from transients. * * @since 1.2.0 */ public function delete_buckets() { delete_transient( $this->name_buckets ); } /** * Delete our objects cache. * * @since 1.2.0 * * @param string $bucket_id A bucket id. */ public function delete_objects( $bucket_id ) { $transient_name = $this->get_name_objects( $bucket_id ); delete_transient( $transient_name ); // Backups are created from objects, so delete backups transient too. $this->delete_backups( $bucket_id ); } /** * Get our objects from cache. * * @since 1.2.0 * * @param string $bucket_id A bucket id. * @return mixed False if objects do not exist, array if they do. */ public function get_objects( $bucket_id ) { $transient_name = $this->get_name_objects( $bucket_id ); return get_transient( $transient_name ); } /** * Get the name of our backups transient. * * @since 1.2.0 * * @param string $bucket_id A bucket id. * @return string */ public function get_name_backups( $bucket_id ) { return 'boldgrid_backup_s3_' . $this->provider->get_key() . '_bucket_' . $bucket_id . '_backups'; } /** * Get the name of our objects transient. * * @since 1.2.0 * * @param string $bucket_id A bucket id. * @return string */ public function get_name_objects( $bucket_id ) { return 'boldgrid_backup_s3_' . $this->provider->get_key() . '_bucket_' . $bucket_id . '_objects'; } /** * Get backups from transient. * * @since 1.2.0 * * @param string $bucket_id A bucket id. * @return array */ public function get_backups( $bucket_id ) { $transient_name = $this->get_name_backups( $bucket_id ); return get_transient( $transient_name ); } /** * Get our buckets from transients. * * @since 1.2.0 * * @return mixed False if transient is missing, otherwise Guzzle\Service\Resource\Model. */ public function get_buckets() { return get_transient( $this->name_buckets ); } /** * Set our backups transient. * * @since 1.2.0 * * @param array $backups An array of backups. * @param string $bucket_id A bucket id. */ public function set_backups( array $backups, $bucket_id ) { $transient_name = $this->get_name_backups( $bucket_id ); set_transient( $transient_name, $backups, DAY_IN_SECONDS ); } /** * Set our buckets transients. * * @since 1.2.0 * * @param Guzzle\Service\Resource\Model $buckets */ public function set_buckets( \Aws\Result $buckets ) { set_transient( $this->name_buckets, $buckets, DAY_IN_SECONDS ); } /** * Set our objects transient. * * @since 1.2.0 * * @param array $objects An array of objects. * @param string $bucket_id A bucket id. */ public function set_objects( array $objects, $bucket_id ) { $transient_name = $this->get_name_objects( $bucket_id ); set_transient( $transient_name, $objects, DAY_IN_SECONDS ); /* * Because the backups transient is based off of the objects transient, any change to the * objects transient should reset the backups transient. */ $this->delete_backups( $bucket_id ); } } admin/remote/s3_buckets.php000064400000004311147600362700011711 0ustar00 */ /** * S3 Buckets class. * * @since 1.2.0 */ class Boldgrid_Backup_Premium_Admin_Remote_S3_Buckets { /** * A listing of our buckets. * * This list is raw data. * * @since 1.2.0 * @var Guzzle\Service\Resource\Model * @access private */ private $buckets; /** * Our client. * * @since 1.2.0 * @var Boldgrid_Backup_Premium_Admin_Remote_S3_Client * @access private */ private $client; /** * Constructor. * * @since 1.2.0 * * @param Boldgrid_Backup_Premium_Admin_Remote_S3_Client $client Our client. */ public function __construct( Boldgrid_Backup_Premium_Admin_Remote_S3_Client $client ) { $this->client = $client; } /** * Determine whether or not a bucket exists. * * @since 1.2.0 * * @param string $bucket_id The bucket id to check. * @return bool */ public function has_bucket( $bucket_id ) { $this->set_buckets(); $buckets = $this->buckets->get( 'Buckets' ); foreach ( $buckets as $bucket ) { if ( $bucket['Name'] === $bucket_id ) { return true; } } return false; } /** * Get our buckets. * * @since 1.2.0 * * @return Guzzle\Service\Resource\Model */ public function get_buckets() { return $this->buckets; } /** * Initialize our buckets. * * @since 1.2.0 */ public function set_buckets() { // First, try to set buckets via transient. if ( is_null( $this->buckets ) && $this->client->has_provider() ) { if ( $from_transient = $this->client->get_provider()->get_transient()->get_buckets() ) { // phpcs:ignore $this->buckets = $from_transient; } } /* * If no transient data, get the buckets fresh and set the transient. * * Transient is cleared whenever we create a new bucket. */ if ( is_null( $this->buckets ) ) { $this->buckets = $this->client->get_client()->listBuckets(); if ( $this->client->has_provider() ) { $this->client->get_provider()->get_transient()->set_buckets( $this->buckets ); } } } } admin/remote/provider.php000064400000005753147600362700011511 0ustar00 */ /** * Class: Boldgrid_Backup_Premium_Admin_Remote_Provider * * @since 1.2.0 */ class Boldgrid_Backup_Premium_Admin_Remote_Provider { /** * Core. * * @since 1.2.0 * @var Boldgrid_Backup_Admin_Core * @access protected */ protected $core; /** * Default retention count. * * By default it's 5, but this can be changed. * * @since 1.2.0 * @var int $default_retention * @access protected */ protected $default_retention = 5; /** * Key. * * @since 1.2.0 * @var string * @access protected */ protected $key; /** * Remote settings. * * @since 1.2.0 * @var Boldgrid_Backup_Admin_Remote_Settings * @access protected */ protected $remote_settings; /** * Title. * * @since 1.2.0 * @var string * @access protected */ protected $title; /** * Constructor. * * @since 1.2.0 */ public function __construct() { $this->remote_settings = new Boldgrid_Backup_Admin_Remote_Settings( $this->key ); } /** * Delete settings. * * Delete all of the settings for this provider. * * @since 1.2.0 */ public function delete_settings() { $this->remote_settings->delete_settings(); } /** * Get the parent plugin core class. * * @since 1.2.0 * * @return Boldgrid_Backup_Admin_Core */ public function get_core() { if ( is_null( $this->core ) ) { $this->core = apply_filters( 'boldgrid_backup_get_core', null ); } return $this->core; } /** * Get our default retention count. * * @since 1.2.0 * * @return int */ public function get_default_retention() { return $this->default_retention; } /** * Get our key. * * @since 1.2.0 * * @return string */ public function get_key() { return $this->key; } /** * Get our nickname. * * @since 1.2.0 * * @return string */ public function get_nickname() { $nickname = $this->get_setting( 'nickname' ); return ! empty( $nickname ) ? $nickname : $this->title; } /** * Get our remote settings. * * @since 1.2.0 * * @return Boldgrid_Backup_Admin_Remote_Settings */ public function get_remote_settings() { return $this->remote_settings; } /** * Get one setting. * * @since 1.2.0 * * @param string $key The key of the setting to get. * @param mixed $default The default value to return. * @return mixed */ public function get_setting( $key, $default = false ) { return $this->remote_settings->get_setting( $key, $default ); } /** * Get our title. * * @since 1.2.0 * * @return string */ public function get_title() { return $this->title; } /** * Whether or not this provider has settings saved. * * @since 1.2.0 * * @return bool */ public function has_settings() { return $this->remote_settings->has_settings(); } } admin/remote/google_drive_logs.php000064400000004751147600362700013345 0ustar00 */ /** * Google Drive Logs. * * @since SINCEVERSION */ class Boldgrid_Backup_Premium_Admin_Remote_Google_Drive_Logs { /** * Google Drive Connection Log. * * A log used for various connection related issues. Initially added to help troubleshoot an issue * with a user needing to continually reauthenticate with Google Drive. * * @since SINCEVERSION * @access private * @var Boldgrid_Backup_Admin_Log */ private $connect_log; /** * The core class object. * * @since SINCEVERSION * @access private * @var Boldgrid_Backup_Admin_Core */ private $core; /** * Google Drive Download Log. * * A log used to store info about downloads. * * @since 1.5.8 * @access private * @var Boldgrid_Backup_Admin_Log */ private $download_log; /** * Google Drive Upload Log. * * A log used to store info about uploads. * * @since SINCEVERSION * @access private * @var Boldgrid_Backup_Admin_Log */ private $upload_log; /** * Constructor. * * @since SINCEVERSION * * @param Boldgrid_Backup_Admin_Core $core Boldgrid_Backup_Admin_Core object. */ public function __construct( Boldgrid_Backup_Admin_Core $core ) { $this->core = $core; } /** * Get our Connect Log. * * @since SINCEVERSION * * @return Boldgrid_Backup_Admin_Log */ public function get_connect_log() { if ( is_null( $this->connect_log ) ) { $this->connect_log = new Boldgrid_Backup_Admin_Log( $this->core ); $this->connect_log->init( 'google-drive-connect.log' ); } return $this->connect_log; } /** * Get our Download Log. * * @since 1.5.8 * * @return Boldgrid_Backup_Admin_Log */ public function get_download_log() { if ( is_null( $this->download_log ) ) { $this->download_log = new Boldgrid_Backup_Admin_Log( $this->core ); $this->download_log->init( 'google-drive-download.log' ); } return $this->download_log; } /** * Get our Upload Log. * * @since SINCEVERSION * * @return Boldgrid_Backup_Admin_Log */ public function get_upload_log() { if ( is_null( $this->upload_log ) ) { $this->upload_log = new Boldgrid_Backup_Admin_Log( $this->core ); $this->upload_log->init( 'google-drive-upload.log' ); } return $this->upload_log; } } admin/remote/google_drive.php000064400000014510147600362700012313 0ustar00 */ /** * Google Drive class. * * @since 1.1.0 */ class Boldgrid_Backup_Premium_Admin_Remote_Google_Drive { /** * An instance of Boldgrid_Backup_Premium_Admin_Remote_Google_Drive_Client. * * @since 1.1.0 * @var Boldgrid_Backup_Premium_Admin_Remote_Google_Drive_Client */ public $client; /** * An instance of Boldgrid_Backup_Premium_Admin_Remote_Google_Drive_Folder. * * @since 1.1.0 * @var Boldgrid_Backup_Premium_Admin_Remote_Google_Drive_Folder */ public $folder; /** * An instance of Boldgrid_Backup_Premium_Admin_Remote_Google_Drive_Hooks. * * @since 1.1.0 * @var Boldgrid_Backup_Premium_Admin_Remote_Google_Drive_Hooks */ public $hooks; /** * An instance our our Google Drive Logs. * * @since SINCEVERSION * @var Boldgrid_Backup_Premium_Admin_Remote_Google_Drive_Logs */ public $logs; /** * An instance of Boldgrid_Backup_Premium_Admin_Remote_Google_Drive_Page. * * @since 1.1.0 * @var Boldgrid_Backup_Premium_Admin_Remote_Google_Drive_Page */ public $page; /** * An instance of the Boldgrid_Backup_Admin_Remote_Settings class. * * @since 1.1.0 * @var Boldgrid_Backup_Admin_Remote_Settings */ public $settings; /** * The core class object. * * @since 1.1.0 * @access private * @var Boldgrid_Backup_Admin_Core */ private $core; /** * Key. * * @since 1.1.0 * @access private * @var string */ private $key = 'google_drive'; /** * An instance of Boldgrid_Backup_Premium_Admin_Core. * * @since 1.1.0 * @access private * @var Boldgrid_Backup_Premium_Admin_Core */ private $premium_core; /** * Title. * * @since 1.1.0 * @access private * @var string */ private $title = 'Google Drive'; /** * Constructor. * * @since 1.0.0 * * @param Boldgrid_Backup_Admin_Core $core Boldgrid_Backup_Admin_Core object. * @param Boldgrid_Backup_Premium_Admin_Core $premium_core Boldgrid_Backup_Premium_Admin_Core object. */ public function __construct( Boldgrid_Backup_Admin_Core $core, Boldgrid_Backup_Premium_Admin_Core $premium_core ) { $this->core = $core; $this->premium_core = $premium_core; $this->hooks = new Boldgrid_Backup_Premium_Admin_Remote_Google_Drive_Hooks( $core, $premium_core ); $this->client = new Boldgrid_Backup_Premium_Admin_Remote_Google_Drive_Client( $core, $premium_core ); $this->folder = new Boldgrid_Backup_Premium_Admin_Remote_Google_Drive_Folder( $core, $premium_core ); $this->settings = new Boldgrid_Backup_Admin_Remote_Settings( 'google_drive' ); $this->page = new Boldgrid_Backup_Premium_Admin_Remote_Google_Drive_Page( $core, $premium_core ); $this->logs = new Boldgrid_Backup_Premium_Admin_Remote_Google_Drive_Logs( $core ); } /** * Get details * * @since 1.1.0 * * @param bool $try_cache Whether or not to use last_login to validate the Google Drive * account. Please see param definition in $this->is_setup(). * @return array */ public function get_details( $try_cache = false ) { $this->client->init(); $configs = $this->premium_core->get_configs(); // Defaults. $configure_url = 'admin.php?page=boldgrid-backup-google-drive'; $authorize_url = ''; $authorized = true; $enabled = false; if ( ! $this->client->has_access_token() ) { $this->logs->get_connect_log()->add( __METHOD__ . ' Missing access token.' ); } /* * Config changes if the access token is expired. * * This method begins with a call to client->init(). In that method, the access token should * be refreshed if it needs to be. If it's expired by this point, then the user will need to * reauthorize. */ if ( $this->client->client->isAccessTokenExpired() ) { $this->logs->get_connect_log()->add( __METHOD__ . ' Access token is expired.' ); $is_setup = false; $authorized = false; $authorize_params = array( // phpcs:disable WordPress.PHP.DiscouragedPHPFunctions 'site_url' => urlencode( get_site_url() ), 'return_url' => urlencode( admin_url( 'admin.php?page=boldgrid-backup-settings§ion=section_storage' ) ), 'key' => urlencode( get_option( 'boldgrid_api_key' ) ), // phpcs:enable WordPress.PHP.DiscouragedPHPFunctions ); $authorize_url = add_query_arg( $authorize_params, $configs['asset_server'] . $configs['ajax_calls']['google_drive_auth'] ); } else { $this->logs->get_connect_log()->add( __METHOD__ . ' Access token is valid.' ); /* * Determine if we're setup. * * Initially, this was done before the isAccessTokenExpired() call above. In the case of * isAccessTokenExpired(), calling is_setup() first would mean we can't properly run the * is_setup() method because we're not authorized, resulting in the following error: * Error 403: Daily Limit for Unauthenticated Use Exceeded. */ $is_setup = $this->is_setup( $try_cache ); $enabled = $this->settings->get_setting( 'enabled', false ) && $is_setup; } $storage_location = array( 'title' => __( 'Google Drive', 'boldgrid-backup' ), 'key' => 'google_drive', 'configure' => $configure_url, 'authorize' => $authorize_url, 'authorized' => $authorized, 'is_setup' => $is_setup, 'enabled' => $enabled, /* * If we have an error, return it. The only class really doing any work right now is the * folder class, so we'll return that error. If the is_setup() method changes, we'll need * change how we detect an error. */ 'error' => $this->folder->last_error, ); return $storage_location; } /** * Get our key. * * @since 1.1.0 * * @return string */ public function get_key() { return $this->key; } /** * Get our title. * * @since 1.1.0 * * @return string */ public function get_title() { return $this->title; } /** * Determine whether or not Google Drive is setup. * * @since 1.1.0 * * @return bool */ public function is_setup() { $backup_folder_id = $this->folder->get_backup_id(); $parent_folder_id = $this->folder->get_parent_id(); return ! empty( $backup_folder_id ) && ! empty( $parent_folder_id ); } } admin/remote/dreamobjects.php000064400000002414147600362700012310 0ustar00 */ /** * Dreamobjects class. * * @since 1.2.0 */ class Boldgrid_Backup_Premium_Admin_Remote_Dreamobjects extends Boldgrid_Backup_Premium_Admin_Remote_S3_Provider { /** * An instance of Boldgrid_Backup_Premium_Admin_Remote_Dreamobjects_Hooks. * * @since 1.2.0 * @var Boldgrid_Backup_Premium_Admin_Remote_S3_Hooks */ public $hooks; /** * An instance of Boldgrid_Backup_Premium_Admin_Remote_Dreamobjects_Page. * * @since 1.2.0 * @var Boldgrid_Backup_Premium_Admin_Remote_S3_Page */ public $page; /** * Key. * * @since 1.2.0 * @var string * @access protected */ protected $key = 'dreamobjects'; /** * Title. * * @since 1.2.0 * @access protected * @var string */ protected $title = 'DreamObjects'; /** * Constructor. * * @since 1.2.0 */ public function __construct() { parent::__construct(); $this->hooks = new Boldgrid_Backup_Premium_Admin_Remote_S3_Hooks( $this ); $this->page = new Boldgrid_Backup_Premium_Admin_Remote_S3_Page( $this ); } } admin/class-settings.php000064400000013470147600362700011322 0ustar00 */ namespace Boldgrid\Backup\Premium\Admin; /** * Class: Settings * * @since 1.3.0 */ class Settings { /** * The BoldGrid Backup core class object. * * @since 1.3.0 * @access private * @var \Boldgrid_Backup_Admin_Core */ private $core; /** * The Boldgrid Backup Premium core object. * * @since 1.3.0 * @access private * @var \Boldgrid_Backup_Premium_Admin_Core */ private $premium_core; /** * Constructor. * * @since 1.3.0 * * @param \Boldgrid_Backup_Admin_Core $core \Boldgrid_Backup_Admin_Core object. * @param \Boldgrid_Backup_Premium_Admin_Core $premium_core \Boldgrid_Backup_Premium_Admin_Core object. */ public function __construct( \Boldgrid_Backup_Admin_Core $core, \Boldgrid_Backup_Premium_Admin_Core $premium_core ) { $this->core = $core; $this->premium_core = $premium_core; } /** * Enqueue scripts for admin pages. * * @since 1.3.0 */ public function admin_enqueue_scripts() { if ( isset( $_GET['page'] ) && 'boldgrid-backup-settings' === $_GET['page'] ) { // phpcs:ignore wp_register_script( 'boldgrid-backup-premium-admin-settings', plugin_dir_url( __FILE__ ) . 'js/boldgrid-backup-premium-admin-settings.js', array( 'jquery' ), BOLDGRID_BACKUP_PREMIUM_VERSION, false ); wp_localize_script( 'boldgrid-backup-premium-admin-settings', 'settingsData', array( 'cryptToken' => ( new Crypt( $this->core, $this->premium_core ) )->get_crypt_token(), 'lang' => array( 'encryptionToken' => __( 'Encryption Token', 'boldgrid-backup' ), 'revealToken' => __( 'Show', 'boldgrid-backup' ), 'copyText' => __( 'Copy', 'boldgrid-backup' ), 'copiedText' => __( 'Copied!', 'boldgrid-backup' ), 'deleteToken' => __( 'Delete', 'boldgrid-backup' ), 'deleteConfirmText' => __( 'If you delete the encryption token, then you will not be able to access encrpyted file stored in previously-created backup archives.', 'boldgrid-backup' ), 'addHelpText' => sprintf( // translators: 1: HTML break tag, 2: HTML strong open tag, 3: HTML strong closing tag. esc_html__( '%1$s%1$s%2$sEncryption Token%3$s%1$sThe token is used for encryption and should be saved in a secure place (such as a password safe or keyring) and can be used to decrypt files in your encrypted backup archives.', 'boldgrid-backup' ), '
', '', '' ), 'addTokenText' => sprintf( // translators: 1: HTML break tag. esc_html__( 'Save this token in a secure place, such as a password safe or keyring. It is used to decrypt encrypted files stored in backup archives.%1$sIf you change or delete this token, then you will not be able to restore encrypted contents from backup archives.', 'boldgrid-backup' ), '
' ), ), ) ); wp_enqueue_script( 'boldgrid-backup-premium-admin-settings' ); wp_enqueue_script( 'clipboard' ); } } /** * Filter the boldgrid_backup_settings site option before saving to the database. * * @since 1.3.0 * * @param array $settings BoldGrid Backup settings. * @return array */ public function filter_settings( $settings ) { $settings = (array) $settings; // The there is a crypt_token, then update the encryption settings. if ( ! empty( $_POST['crypt_token'] ) ) { // phpcs:ignore $crypt_settings = ( new Crypt( $this->core, $this->premium_core ) ) ->decode_crypt_token( sanitize_key( $_POST['crypt_token'] ) ); // phpcs:ignore if ( ! empty( $crypt_settings ) && ( empty( $settings['openssl'] ) || $settings['openssl'] !== $crypt_settings ) ) { $settings = array_merge( $settings, $crypt_settings ); } } elseif ( ! empty( $_POST['delete_crypt_token'] ) ) { // phpcs:ignore // Delete the encryption settings. They will regenerate if needed. unset( $settings['openssl'] ); } return $settings; } /** * Filter plugin links. * * @since 1.3.0 * * @link https://developer.wordpress.org/reference/hooks/plugin_action_links/ * * @param array $actions An array of plugin action links. By default this can include 'activate', * 'deactivate', and 'delete'. With Multisite active this can also include * 'network_active' and 'network_only' items. * @param string $plugin_file Path to the plugin file relative to the plugins directory. * @param array $plugin_data An array of plugin data. See `get_plugin_data()`. * @param string $context The plugin context. By default this can include 'all', 'active', 'inactive', * 'recently_activated', 'upgrade', 'mustuse', 'dropins', and 'search'. */ public function plugin_action_links( $actions, $plugin_file, $plugin_data, $context ) { $row_actions = array( 'settings' => '' . esc_html__( 'Settings', 'boldgrid-backup' ) . '', ); if ( ! $this->core->config->get_is_premium() ) { $row_actions[] = '' . esc_html__( 'Get Premium', 'boldgrid-backup' ) . ''; } $actions = array_merge( $row_actions, $actions ); return $actions; } /** * Is Timely Updates Enabled. * * @since 1.4.0 * * @return bool */ public function is_timely_updates( $arg ) { $settings = $this->core->settings->get_settings(); return ! empty( $settings['auto_update']['timely-updates-enabled'] ); } } admin/class-crypt.php000064400000023552147600362700010625 0ustar00 */ namespace Boldgrid\Backup\Premium\Admin; /** * Class: Crypt * * @since 1.3.0 */ class Crypt { /** * The BoldGrid Backup core class object. * * @since 1.3.0 * @access private * @var \Boldgrid_Backup_Admin_Core */ private $core; /** * The Boldgrid Backup Premium core object. * * @since 1.3.0 * @access private * @var \Boldgrid_Backup_Premium_Admin_Core */ private $premium_core; /** * Allowed modes for encryption operations. * * Crypt mode: "e" (encrypt) or "d" (decrypt). * * @since 1.3.0 * @access private * @var array */ private $allowed_modes = array( 'e', // Encrypt. 'd', // Decrypt. ); /** * Constructor. * * @since 1.3.0 * * @param \Boldgrid_Backup_Admin_Core $core \Boldgrid_Backup_Admin_Core object. * @param \Boldgrid_Backup_Premium_Admin_Core $premium_core \Boldgrid_Backup_Premium_Admin_Core object. */ public function __construct( \Boldgrid_Backup_Admin_Core $core, \Boldgrid_Backup_Premium_Admin_Core $premium_core ) { $this->core = $core; $this->premium_core = $premium_core; } /** * Post database dump action hook. * * Encrypt file contents, if configured in the settings. * * @since 1.3.0 * * @see \Boldgrid_Backup_Admin_Settings::get_settings() * @see \Boldgrid_Backup_Admin_Config::get_is_premium() * @see self::crypt_file() * * @param string $filepath Filepath. * @return bool */ public function post_dump( $filepath ) { $success = false; $settings = $this->core->settings->get_settings(); if ( ! empty( $settings['encrypt_db'] ) ) { $success = $this->crypt_file( $filepath, 'e' ); } if ( $success ) { /** * Allow the filtering of our $info before generating a backup. * * @since 1.5.1 * * @param array $info Archive information. */ add_filter( 'boldgrid_backup_pre_archive_info', function( $info ) { $info['encrypt_db'] = true; $info['encrypt_sig'] = $this->get_token_hash(); return $info; } ); } return $success; } /** * Filter hook for "boldgrid_backup_post_get_dump_file". * * Decrypts file contents/data, if encrypted. * * @since 1.3.0 * * @see self::crypt_data() * * @param string $file_contents Possibly encrypted MySQL dump file contents. * @return string Unencrypted MySQL dump file contents. */ public function post_get_dump_file( $file_contents ) { if ( ! empty( $file_contents[0]['content'] ) ) { $data = $this->crypt_data( $file_contents[0]['content'], 'd' ); } if ( ! empty( $data ) ) { $file_contents[0]['content'] = $data; } return $file_contents; } /** * Filter archive attribute value. * * Allows operations to be performed on attribute changes and alter the value depending on results. * * @since 1.3.1 * * @see \Boldgrid_Backup_Admin_Core::get_dump_file() * @see self::archive_crypt_file() * * @param mixed $value New value. * @param mixed $old_value Old value. * @param mixed $key Key name. * @param string $filepath Archive filepath. * @return string Filtered new value. */ public function filter_update_attribute( $value, $old_value, $key, $filepath ) { // Handle changing of "encrypt_db". if ( 'encrypt_db' === $key && $value != $old_value ) { // phpcs:ignore $dump_file = basename( $this->core->get_dump_file( $filepath ) ); $mode = empty( $value ) ? 'd' : 'e'; $success = $this->archive_crypt_file( $filepath, $dump_file, $mode ); $value = (bool) ( $success ? $value : $old_value ); } return $value; } /** * Get the BoldGrid Backup Settings. * * Checks the openssl settings and save defaults if not present. * * @since 1.3.0 * * @see \Boldgrid_Backup_Admin_Settings::get_settings() * * @return array */ public function get_settings() { $settings = $this->core->settings->get_settings(); $settings['updated'] = time(); $settings['openssl']['cipher'] = ! empty( $settings['openssl']['cipher'] ) && openssl_cipher_iv_length( $settings['openssl']['cipher'] ) ? $settings['openssl']['cipher'] : 'AES-256-CBC'; $settings['openssl']['iv'] = ! empty( $settings['openssl']['iv'] ) ? $settings['openssl']['iv'] : bin2hex( openssl_random_pseudo_bytes( openssl_cipher_iv_length( $settings['openssl']['cipher'] ) ) ); $settings['openssl']['key'] = ! empty( $settings['openssl']['key'] ) ? $settings['openssl']['key'] : hash( 'sha256', openssl_random_pseudo_bytes( 16 ) ); update_site_option( 'boldgrid_backup_settings', $settings ); return $settings; } /** * Get the crypt token hash. * * A crypt token hash is used to compare the encryption settings signature. * * @since 1.3.0 * * @return string */ public function get_token_hash() { return md5( $this->get_crypt_token() ); } /** * Get the crypt token. * * A crypt token is a represeantation of the encryption settings for a user to copy and paste. * * @since 1.3.0 * * @return string */ public function get_crypt_token() { return bin2hex( gzcompress( wp_json_encode( $this->get_settings()['openssl'] ), 9 ) ); } /** * Decode the encryption settings using a crypt token. * * A crypt token is a represeantation of the encryption settings for a user to copy and paste. * * @since 1.3.0 * * @param string $crypt_token A crypt token string. * @return array Decoded encryption settings. */ public function decode_crypt_token( $crypt_token ) { $settings = array(); if ( ! empty( $crypt_token ) && 0 === strlen( $crypt_token ) % 2 ) { $setting = json_decode( @gzuncompress( hex2bin( $crypt_token ) ), true ); // phpcs:ignore } if ( ! empty( $setting['cipher'] ) && ! empty( $setting['iv'] ) && ! empty( $setting['key'] ) ) { $settings['openssl'] = $setting; } return $settings; } /** * Is the specified file encrypted with our settings. * * @since 1.3.0 * * @param string $filepath Filepath. * @return bool */ public function is_file_encrypted( $filepath ) { $data = $this->core->wp_filesystem->get_contents( $filepath ); if ( empty( $data ) ) { return false; } return false !== $this->crypt_data( $data, 'd' ); } /** * Encrypt or decrypt file contents. * * @since 1.3.0 * * @see \WP_Filesystem::get_contents() * @see self::crypt_data() * @see \WP_Filesystem::put_contents() * * @param string $filepath Filepath. * @param string $mode Crypt mode: "e" (encrypt) or "d" (decrypt). * @return bool */ public function crypt_file( $filepath, $mode ) { $success = false; if ( ! in_array( $mode, $this->allowed_modes, true ) ) { return false; } $data = $this->core->wp_filesystem->get_contents( $filepath ); if ( ! empty( $data ) ) { $data = $this->crypt_data( $data, $mode ); } if ( false !== $data ) { $success = (bool) $this->core->wp_filesystem->put_contents( $filepath, $data ); } return $success; } /** * Encrypt or decrypt data, depending on mode argument. * * @since 1.3.0 * * @param string $data Data. * @param string $mode Crypt mode: "e" (encrypt) or "d" (decrypt). * @return string|false */ public function crypt_data( $data, $mode ) { if ( ! in_array( $mode, $this->allowed_modes, true ) || empty( $data ) ) { return false; } $function = 'e' === $mode ? 'openssl_encrypt' : 'openssl_decrypt'; $settings = $this->get_settings(); $data = $function( $data, $settings['openssl']['cipher'], $settings['openssl']['key'], 0, hex2bin( $settings['openssl']['iv'] ) ); return $data; } /** * Encrypt or decrypt a file in a backup archive. * * @since 1.3.0 * * @see \Boldgrid_Backup_Admin_Archive::init() * @see self::crypt_data() * @see \Boldgrid_Backup_Admin_Backup_Dir::get() * * @uses \Boldgrid_Backup_Admin_Archive::log * * @param string $filepath Archive filepath. * @param string $filename Filename. * @param string $mode Crypt mode: "e" (encrypt) or "d" (decrypt). * @return bool */ public function archive_crypt_file( $filepath, $filename, $mode ) { if ( empty( $filepath ) || empty( $filename ) || ! in_array( $mode, $this->allowed_modes, true ) || ! $this->core->wp_filesystem->is_writable( $filepath ) ) { return false; } if ( ! class_exists( '\PclZip' ) ) { require_once ABSPATH . 'wp-admin/includes/class-pclzip.php'; } $this->core->archive->init( $filepath ); $archive = new \PclZip( $filepath ); if ( 0 === $archive ) { return false; } $list = $archive->listContent(); if ( empty( $list ) ) { return false; } $file_index = false; foreach ( $list as $index => $filedata ) { if ( $filename === $filedata['filename'] ) { $file_index = $index; break; } } if ( false === $file_index ) { return false; } $file_contents = $archive->extractByIndex( $file_index, PCLZIP_OPT_EXTRACT_AS_STRING ); if ( empty( $file_contents[0]['content'] ) ) { return false; } $data = $this->crypt_data( $file_contents[0]['content'], $mode ); if ( empty( $data ) ) { return false; } $crypt_file_path = $this->core->backup_dir->get() . '/' . $filename; $written = $this->core->wp_filesystem->put_contents( $crypt_file_path, $data ); if ( ! $written ) { return false; } $log = $this->core->archive->log; // Ensure the act updating a file does not change the backup file's timestamp. if ( ! empty( $log['lastmodunix'] ) ) { $this->core->wp_filesystem->touch( $crypt_file_path, $log['lastmodunix'] ); } $status = $archive->deleteByIndex( $file_index ); if ( 0 === $status ) { return false; } $status = $archive->add( $crypt_file_path, PCLZIP_OPT_REMOVE_ALL_PATH ); if ( 0 === $status ) { return false; } return true; } } admin/class-boldgrid-backup-premium-admin-update.php000064400000026135147600362700016537 0ustar00 */ /** * BoldGrid update class. */ class Boldgrid_Backup_Premium_Admin_Update { /** * Plugin configuration array. * * @var array */ private $configs = array(); /** * Constructor. * * @param array $configs Plugin configuration array. */ public function __construct( array $configs ) { $this->configs = $configs; } /** * Adds filters for plugin update hooks. * * @see self::wpcron() * @see self::wp_update_this_plugin() */ public function add_hooks() { $is_cron = ( defined( 'DOING_CRON' ) && DOING_CRON ); $is_wpcli = ( defined( 'WP_CLI' ) && WP_CLI ); if ( $is_cron || $is_wpcli || is_admin() ) { /* * The plugins_api filter allows a plugin to override the WordPress.org Plugin Installation * API entirely. */ add_filter( 'plugins_api', array( $this, 'custom_plugins_transient_update', ), 11, 3 ); // Filter the value of the update_plugins site transient. add_filter( 'site_transient_update_plugins', array( $this, 'site_transient_update_plugins', ), 11 ); // Filter the value of the update_plugins site transient before it is set. add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'custom_plugins_transient_update', ), 11, 2 ); } if ( $is_cron ) { $this->wpcron(); } if ( $is_cron || $is_wpcli ) { $this->wp_update_this_plugin(); } } /** * WP-CRON init. */ public function wpcron() { // Ensure required definitions for pluggable. if ( ! defined( 'AUTH_COOKIE' ) ) { define( 'AUTH_COOKIE', null ); } if ( ! defined( 'LOGGED_IN_COOKIE' ) ) { define( 'LOGGED_IN_COOKIE', null ); } // Load the pluggable class, if needed. require_once ABSPATH . 'wp-includes/pluggable.php'; } /** * Update the plugin transient. * * @global $pagenow The current WordPress page filename. * @global $wp_version The WordPress version. * * @param object $transient WordPress plugin update transient. * @param string $action Action name. * @param array $args Optional arguments. * @return object $transient */ public function custom_plugins_transient_update( $transient, $action, $args = array() ) { $slug = ! empty( $args->slug ) ? $args->slug : ''; /* * If we're looking for information about a specific plugin, and it's not this plugin, abort. * * We may be in various $actions. * @see plugins_api() * * If the $action is 'plugin_information', we are looking for information on a specific plugin. * If that specific plugin is not this plugin, there's no need to filter anything, just return. * * The 'plugin_information' is generally set when a plugin is being installed. * @see wp_ajax_install_plugin() * @see WP_REST_Plugins_Controller::create_item() * @see wp-admin/update.php */ if ( 'plugin_information' === $action && $slug !== $this->configs['plugin_name'] ) { return $transient; } $version_data = get_site_transient( $this->configs['plugin_transient_name'] ); if ( ! function_exists( 'get_plugin_data' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $plugin_data = get_plugin_data( $this->configs['main_file_path'], false ); $is_force_check = isset( $_GET['force-check'] ); // phpcs:ignore // Was the version data recently updated? $is_data_old = ( empty( $version_data->updated ) || $version_data->updated < time() - 5 ); global $wp_version; // If we have no transient or force-check is called, and we do have configs, then get data and set transient. if ( ! $version_data || ( $is_force_check && $is_data_old ) ) { $options = get_site_option( 'boldgrid_settings' ); $channel = isset( $options['release_channel'] ) ? $options['release_channel'] : 'stable'; $params = http_build_query( array( 'key' => $this->configs['plugin_key_code'], 'channel' => $channel, 'installed_' . $this->configs['plugin_key_code'] . '_version' => $plugin_data['Version'], 'installed_wp_version' => $wp_version, 'site_hash' => get_option( 'boldgrid_site_hash' ), ) ); $query = $this->configs['asset_server'] . $this->configs['ajax_calls']['get_plugin_version'] . '?' . $params; $version_data = json_decode( wp_remote_retrieve_body( wp_remote_get( $query ) ) ); // phpcs:ignore // Set the version data transient, expire in 8 hours. if ( ! empty( $version_data ) && 200 === $version_data->status && ! empty( $version_data->result->data ) ) { // Add the current timestamp (in seconds). $version_data->updated = time(); // Set version data transient, expire in 8 hours. delete_site_transient( $this->configs['plugin_transient_name'] ); set_site_transient( $this->configs['plugin_transient_name'], $version_data, 8 * HOUR_IN_SECONDS ); } else { // Something went wrong, so just skip adding update data; return unchanged transient data. return $transient; } } global $pagenow; // Create a new object to be injected into transient. if ( 'plugin-install.php' === $pagenow && isset( $_GET['plugin'] ) && // phpcs:ignore $this->configs['plugin_name'] === $_GET['plugin'] ) { // phpcs:ignore // For version information iframe (/plugin-install.php). $transient = new stdClass(); // If we have section data, then prepare it for use. if ( ! empty( $version_data->result->data->sections ) ) { // Remove new lines and double-spaces, to help prevent a broken JSON set. $version_data->result->data->sections = preg_replace( '/\s+/', ' ', trim( $version_data->result->data->sections ) ); // Convert the JSON set into an array. $transient->sections = json_decode( $version_data->result->data->sections, true ); // If we have data, format it for use, else set a default message. if ( ! empty( $transient->sections ) && count( $transient->sections ) ) { foreach ( $transient->sections as $section => $section_data ) { $transient->sections[ $section ] = html_entity_decode( $section_data, ENT_QUOTES ); } } else { $transient->sections['description'] = 'Data not available'; } } else { $transient->sections['description'] = 'Data not available'; } // Set the other elements. $transient->name = $version_data->result->data->title; $transient->requires = $version_data->result->data->requires_wp_version; $transient->tested = $version_data->result->data->tested_wp_version; $transient->last_updated = $version_data->result->data->release_date; $transient->download_link = $this->configs['asset_server'] . $this->configs['ajax_calls']['get_asset'] . '?id=' . $version_data->result->data->asset_id . '&installed_plugin_version=' . $plugin_data['Version'] . '&installed_wp_version=' . $wp_version; if ( ! empty( $version_data->result->data->compatibility ) && ( $compatibility = json_decode( $version_data->result->data->compatibility, true ) ) ) { // phpcs:ignore $transient->compatibility = $version_data->result->data->compatibility; } $transient->added = '2015-03-19'; if ( ! empty( $version_data->result->data->siteurl ) ) { $transient->homepage = $version_data->result->data->siteurl; } if ( ! empty( $version_data->result->data->tags ) && ( $tags = json_decode( $version_data->result->data->tags, true ) ) ) { // phpcs:ignore $transient->tags = $version_data->result->data->tags; } if ( ! empty( $version_data->result->data->banners ) && ( $banners = json_decode( $version_data->result->data->banners, true ) ) ) { // phpcs:ignore $transient->banners = $banners; } $transient->plugin_name = basename( $this->configs['main_file_path'] ); $transient->slug = $this->configs['plugin_name']; $transient->version = $version_data->result->data->version; $transient->new_version = $version_data->result->data->version; } elseif ( 'update_plugins' === $action || 'plugin_information' === $action ) { $obj = new stdClass(); $obj->slug = $this->configs['plugin_name']; $obj->plugin = $this->configs['plugin_name'] . '/' . basename( $this->configs['main_file_path'] ); $obj->new_version = $version_data->result->data->version; $obj->tested = $version_data->result->data->tested_wp_version; if ( ! empty( $version_data->result->data->siteurl ) ) { $obj->url = $version_data->result->data->siteurl; } $obj->package = $this->configs['asset_server'] . $this->configs['ajax_calls']['get_asset'] . '?id=' . $version_data->result->data->asset_id . '&installed_plugin_version=' . $plugin_data['Version'] . '&installed_wp_version=' . $wp_version; if ( $plugin_data['Version'] !== $version_data->result->data->version ) { if ( ! empty( $version_data->result->data->autoupdate ) ) { $obj->autoupdate = true; } $transient->response[ $obj->plugin ] = $obj; $transient->tested = $obj->tested; } else { /* * Fail-safe. This is a band-aid to avoid updating a property on a non object. * * @todo Fix the underlying issue. Most likely when 'plugin_information' === $action. */ $transient = is_object( $transient ) ? $transient : new stdClass(); $transient->no_update[ $obj->plugin ] = $obj; } } return $transient; } /** * Force WP to check for updates, don't rely on cache / transients. * * @global $pagenow The current WordPress page filename. * * @param object $value WordPress plugin update transient. * @return object */ public function site_transient_update_plugins( $value ) { global $pagenow; // Only require fresh data if user clicked "Check Again". if ( 'update-core.php' !== $pagenow || ! isset( $_GET['force-check'] ) ) { // phpcs:ignore return $value; } // Set the last_checked to 1, so it will trigger the timeout and check again. if ( isset( $value->last_checked ) ) { $value->last_checked = 1; } return $value; } /** * Action to add a filter to check if this plugin should be auto-updated. * * @see wp_maybe_auto_update() */ public function wp_update_this_plugin() { add_filter( 'auto_update_plugin', array( $this, 'auto_update_this_plugin', ), 11, 2 ); add_filter( 'auto_update_plugins', array( $this, 'auto_update_this_plugin', ), 11, 2 ); // Have WordPress check for plugin updates. wp_maybe_auto_update(); } /** * Filter to check if this plugin should be auto-updated. * * @param bool $update Whether or not this plugin is set to update. * @param object $item The plugin transient object. * @return bool Whether or not to update this plugin. */ public function auto_update_this_plugin( $update, $item ) { if ( isset( $item->slug[ $this->configs['plugin_name'] ] ) && isset( $item->autoupdate ) ) { return true; } else { return $update; } } } admin/class-boldgrid-backup-premium-admin-support.php000064400000005166147600362700016772 0ustar00 */ /** * Support class. * * @since 1.1.0 */ class Boldgrid_Backup_Premium_Admin_Support { /** * The core class object. * * @since 1.1.0 * @var Boldgrid_Backup_Admin_Core */ private $core; /** * An instance of Boldgrid_Backup_Premium_Admin_Core. * * @since 1.1.0 * @var Boldgrid_Backup_Premium_Admin_Core */ private $premium_core; /** * Constructor. * * @since 1.1.0 * * @param Boldgrid_Backup_Admin_Core $core Boldgrid_Backup_Admin_Core object. * @param Boldgrid_Backup_Premium_Admin_Core $premium_core Boldgrid_Backup_Premium_Admin_Core object. */ public function __construct( Boldgrid_Backup_Admin_Core $core, Boldgrid_Backup_Premium_Admin_Core $premium_core ) { $this->core = $core; $this->premium_core = $premium_core; } /** * Whether or not this plugin should register its hooks. * * @since 1.1.0 * * @return bool */ public function has_hook_support() { return $this->has_parent_version( true ); } /** * Whether or not we have the minimum version of the parent plugin. * * @since 1.1.0 * * @param bool $admin_notice Whether or not to show an admin notice if the we don't have the * minimum version. * @return bool */ public function has_parent_version( $admin_notice = false ) { $configs = $this->premium_core->get_configs(); $support = version_compare( BOLDGRID_BACKUP_VERSION, $configs['required_parent_version'], '>=' ); if ( ! $support && $admin_notice ) { $message = sprintf( // translators: 1: Required parent plugin version, 2: Actual version, 3: HTML opening strong tag, 4: HTML closing strong tag, 5: HTML opening anchor tag with link to updates page, 6: HTML closing tag, 7: Premium plugin title, 8: Parent plugin title. __( 'The %3$s%7$s%4$s plugin requires version %1$s of the %3$s%8$s%4$s plugin to run, but you are running version %2$s. Please %5$supdate your %3$s%8$s%4$s plugin%6$s to continue.', 'boldgrid-bacukp' ), $configs['required_parent_version'], BOLDGRID_BACKUP_VERSION, '', '', '', '', BOLDGRID_BACKUP_PREMIUM_TITLE, BOLDGRID_BACKUP_TITLE ); add_action( 'admin_notices', function() use ( $message ) { $this->core->notice->boldgrid_backup_notice( $message ); } ); } return $support; } } admin/class-boldgrid-backup-premium-admin-plugins.php000064400000024464147600362700016741 0ustar00 */ /** * Class: Boldgrid_Backup_Premium_Admin_Plugins. * * This is a generic class designed to help manage how this plugin behaves within the scope of. * "WordPress Dashboard > Plugins > *". * * @since 1.4.0 */ class Boldgrid_Backup_Premium_Admin_Plugins { /** * Active Plugins * * An array of \Boldgrid\Library\Library\Plugin\Plugin instances * * @since 1.4.0 * @var array */ public $active_plugins = array(); /** * Add Auto Update Message. * * @since 1.4.0 */ public function add_update_message() { $core = apply_filters( 'boldgrid_backup_get_core', null ); $settings = $core->settings->get_settings(); $plugins = new \Boldgrid\Library\Library\Plugin\Plugins(); $this->active_plugins = $plugins->getAllPlugins(); foreach ( $this->active_plugins as $plugin ) { add_action( 'in_plugin_update_message-' . $plugin->getFile(), array( $this, 'print_update_message' ), 10, 2 ); } } /** * Filters Auto Update Message * * In WordPress 5.5, the Auto Update message displays how many hours * till the next update. We must filter that, and apply our message when * Timely Auto Updates is enabled. * * @since 1.5.2 * * @param string $html Original HTML Markup. * @param string $plugin_file Path to main plugin file. * @param array $plugin_data An array of plugin data. */ public function filter_update_message( $html, $plugin_file, $plugin_data ) { $core = apply_filters( 'boldgrid_backup_get_core', null ); $auto_update_settings = $core->settings->get_setting( 'auto_update' ); /* * If a plugin is installed for the first time before a setting is created for it in Total Upkeep, it can * generate an undefined index notice, so this has been changed to check if it is empty rather than just check * if it is true or false */ $auto_updates_enabled = empty( $auto_update_settings['plugins'][ $plugin_file ] ) ? '0' : '1'; $plugin = \Boldgrid\Library\Library\Plugin\Factory::create( $plugin_file ); // If the plugin has an update available modify the auto-update-time class. if ( $plugin->hasUpdate() ) { $plugin->setUpdateData(); $time_till_update = $plugin->updateData->timeTillUpdate(); //phpcs:ignore WordPress.NamingConventions.ValidVariableName $update_schedule_string = ''; /* * If the plugin is listed in the auto_update_plugins option then auto updates are enabled for that plugin. * We have to test that $time_till_update is an integer because the timeTillUpdate() can return false in some cases. * If the $time_till_update is greater than 0, then display the human readable time difference, * otherwise display the standard wp_get_auto_update_message(). */ if ( is_int( $time_till_update ) && 0 < $time_till_update ) { $update_schedule_string = sprintf( '%s %s.', esc_html__( 'Automatic update scheduled in', 'boldgrid-backup' ), human_time_diff( $time_till_update ) ); /* * This pattern must be able to apply both to the "auto-update-time" and the "auto-update-time hidden" divs or else it will not work when the * auto updates are not enabled for that plugin on page load, but then enabled after page load. */ $html = preg_replace( '/(
)(.*)(<\/div>)/', '$1' . $update_schedule_string . '$3', $html ); } } return $html; } /** * Prints Update Message. * * @since 1.4.0 * * @param array $data Data sent to callback. * @param array $response Response sent to callback. * * @global string $wp_version WordPress Version Number. */ public function print_update_message( $data, $response ) { global $wp_version; $core = apply_filters( 'boldgrid_backup_get_core', null ); $plugin = apply_filters( 'boldgrid_backup_get_plugin', $this->active_plugins, $data['slug'] ); $settings = get_site_option( 'boldgrid_backup_settings', array() ); $default_days = 0; $is_wp55 = version_compare( $wp_version, '5.4.99', 'gt' ); // If 'auto_update' is not set, then the user has neither enabled or disabled the feature yet, So add a link to settings. if ( ! isset( $settings['auto_update'] ) ) { printf( ' %s', esc_url( $core->settings->get_settings_url( 'section_auto_updates' ) ), esc_html__( 'View Update Settings', 'boldgrid-backup' ) ); return; } // In cases where there is a WP_Error returned when the plugin is checked for updateData, return. if ( is_wp_error( $plugin ) ) { return; } $plugin->setUpdateData( true ); $version = $plugin->updateData->version; // phpcs:ignore WordPress.NamingConventions.ValidVariableName $days = $plugin->updateData->days; // phpcs:ignore WordPress.NamingConventions.ValidVariableName $third_party = $plugin->updateData->thirdParty; // phpcs:ignore WordPress.NamingConventions.ValidVariableName $api_called = $plugin->updateData->apiFetchTime; // phpcs:ignore WordPress.NamingConventions.ValidVariableName $auto_update_settings = $settings['auto_update']; $plugin_updates_enabled = false; if ( isset( $auto_update_settings['plugins'][ $plugin->getFile() ] ) ) { $plugin_updates_enabled = (bool) $auto_update_settings['plugins'][ $plugin->getFile() ]; } else { $plugin_updates_enabled = (bool) $auto_update_settings['plugins']['default']; } $days_setting = ! empty( $auto_update_settings['days'] ) ? $auto_update_settings['days'] : $default_days; $updates_enabled = ! empty( $auto_update_settings['timely-updates-enabled'] ) ? $auto_update_settings['timely-updates-enabled'] : '0'; // If the auto updates are disabled, don't print anything. if ( '0' === $updates_enabled ) { return; } // if the update is old enough, then the plugin would have an 'update_pending', otherwise it is 'deferred'. $plugin_updates_status = ( $days_setting - $days <= 0 ) ? 'update_pending' : 'deferred'; $plugin_updates_status = ( true === $third_party ) ? 'third_party' : $plugin_updates_status; // if auto updates are disabled for this plugin, change status to 'disabled', otherwise it stays 'update_pending' or 'deferred'. $plugin_updates_status = ( true === $plugin_updates_enabled ) ? $plugin_updates_status : 'disabled'; $markup = ''; switch ( $plugin_updates_status ) { case 'disabled': // Advise user that updates for this plugin are disabled, and provide link to settings. $markup = sprintf( '
%s', esc_html__( 'Automatic updates for this plugin are not enabled.', 'boldgrid-backup' ) ); break; case 'deferred': // Advise user in how many days updates will occur. $markup = sprintf( '
%s %s %s %s %s', esc_html__( 'Version', 'boldgrid-backup' ), esc_html( $version ), esc_html__( 'was released', 'boldgrid-backup' ), esc_html( $days ), esc_html__( 'days ago.', 'boldgrid-backup' ), esc_html( $days_setting - $days ) ); // In wp5.5 and newer, the below message is handled by a different filter, but in wp < 5.5 it needs to be added here. if ( ! $is_wp55 ) { $markup .= wp_kses( sprintf( // translators: 1 an opening strong tag, 2 its closing strong tag, 3 the number of days until an auto update. __( 'Total Upkeep will Automatically update this plugin in %1$s%3$s%2$s days.', 'boldgrid-backup' ), '', '', ( $days_setting - $days ) ), array( 'strong' => array() ) ); } break; case 'update_pending': // If the update is pending, then determine what time the next plugin update cron even runs, do the math, and advise user approximately when it will update. $markup = sprintf( '
%s %s %s %s %s.', esc_html__( 'Version', 'boldgrid-backup' ), esc_html( $version ), esc_html__( 'was released', 'boldgrid-backup' ), esc_html( $days ), esc_html__( 'days ago', 'boldgrid-backup' ) ); // In wp5.5 and newer, the below message is handled by a different filter, but in wp < 5.5 it needs to be added here. if ( ! $is_wp55 ) { $markup .= sprintf( ' %s %s.', esc_html__( 'Total Upkeep will update this plugin within the next ', 'boldrid-backup' ), esc_html( $this->when_updates_occur() ) ); } break; case 'third_party': // If the update is pending, then determine what time the next plugin update cron even runs, do the math, and advise user when it will update. $markup = sprintf( '
%s %s %s.', esc_html__( 'Version', 'boldgrid-backup' ), esc_html( $version ), esc_html__( 'is now available', 'boldgrid-backup' ) ); // In wp5.5 and newer, the below message is handled by a different filter, but in wp < 5.5 it needs to be added here. if ( ! $is_wp55 ) { $markup .= sprintf( ' %s %s.', esc_html__( 'Total Upkeep will update this plugin within the next ', 'boldrid-backup' ), esc_html( $this->when_updates_occur() ) ); } break; default: break; } if ( $markup ) { $full_markup = $markup . sprintf( // translators: 1 Update Settings URL, 2 View Update Settings Text. ' %2$s', /* 1 */esc_url( $core->settings->get_settings_url( 'section_auto_updates' ) ), /* 2 */esc_html__( 'View Update Settings', 'boldgrid-backup' ) ); echo $full_markup; // phpcs:ignore WordPress.XSS.EscapeOutput } return $markup; } /** * When Updates Occur. * * @since 1.4.0 * @return string */ public function when_updates_occur() { $next_run = new DateTime(); $next_run->setTimestamp( wp_next_scheduled( 'wp_update_plugins' ) ); $time_till_next_run = $next_run->diff( new DateTime() ); switch ( $time_till_next_run->h ) { case 0: return $time_till_next_run->i . ' minutes'; case 1: return 'one hour'; default: return $time_till_next_run->h . ' hours'; } } } admin/class-boldgrid-backup-premium-admin-plugin-editor.php000064400000006273147600362700020040 0ustar00 */ /** * Plugin Editor class. * * @since 1.5.3 */ class Boldgrid_Backup_Premium_Admin_Plugin_Editor { /** * The core class object. * * @since 1.5.3 * @var Boldgrid_Backup_Admin_Core */ private $core; /** * An instance of Boldgrid_Backup_Premium_Admin_Core. * * @since 1.5.3 * @var Boldgrid_Backup_Premium_Admin_Core */ private $premium_core; /** * Constructor. * * @since 1.5.3 * * @param Boldgrid_Backup_Admin_Core $core Boldgrid_Backup_Admin_Core object. * @param Boldgrid_Backup_Premium_Admin_Core $premium_core Boldgrid_Backup_Premium_Admin_Core object. */ public function __construct( Boldgrid_Backup_Admin_Core $core, Boldgrid_Backup_Premium_Admin_Core $premium_core ) { $this->core = $core; $this->premium_core = $premium_core; } /** * Enqueue scripts. * * @since 1.5.3 * * @param string $hook Hook name. */ public function admin_enqueue_scripts( $hook ) { if ( 'plugin-editor.php' !== $hook ) { return; } $rel_plugin_path = str_replace( ABSPATH, '', dirname( BOLDGRID_BACKUP_PREMIUM_PATH ) ) . DIRECTORY_SEPARATOR; wp_register_script( 'boldgrid-backup-premium-admin-plugin-editor', plugin_dir_url( __FILE__ ) . 'js/boldgrid-backup-premium-admin-plugin-editor.js', array( 'jquery' ), BOLDGRID_BACKUP_PREMIUM_VERSION, true ); $lang = array( 'rel_plugin_path' => $rel_plugin_path, 'help' => include BOLDGRID_BACKUP_PREMIUM_PATH . '/admin/partials/plugin-editor.php', 'error_saving' => __( 'Error. Unable to create a copy of this file.', 'boldgrid-backup' ), 'success_saving' => __( 'Success. A copy of this file was made (or already existed).', 'boldgrid-backup' ), 'save_a_copy' => __( 'Save a copy before updating', 'boldgrid-backup' ), 'find_a_version' => __( 'Find a version to restore', 'boldgrid-backup' ), ); wp_localize_script( 'boldgrid-backup-premium-admin-plugin-editor', 'boldgrid_backup_premium_admin_plugin_editor', $lang ); wp_enqueue_script( 'boldgrid-backup-premium-admin-plugin-editor' ); } /** * Save a copy of a file. * * @since 1.5.3 */ public function wp_ajax_save_copy() { if ( ! current_user_can( 'update_plugins' ) ) { wp_send_json_error( __( 'Permission denied.', 'boldgrid-backup' ) ); } $plugin_file = ! empty( $_POST['pluginFile'] ) ? $_POST['pluginFile'] : false; // phpcs:ignore $file = ! empty( $_POST['file'] ) ? $_POST['file'] : false; // phpcs:ignore if ( empty( $file ) || empty( $plugin_file ) ) { wp_send_json_error( __( 'Invalid file / plugin file.', 'boldgrid-backup' ) ); } // This check is similar to WordPress' check when saving a file. if ( ! check_ajax_referer( 'edit-plugin_' . $plugin_file, 'nonce', false ) ) { wp_send_json_error( $error . ' ' . __( 'Invalid nonce.', 'boldgrid-backup' ) ); } $saved = $this->premium_core->historical->save( $file ); if ( $saved ) { wp_send_json_success(); } else { wp_send_json_error(); } } } admin/class-boldgrid-backup-premium-admin-history.php000064400000024631147600362700016755 0ustar00 */ /** * History class. * * @since 1.5.3 */ class Boldgrid_Backup_Premium_Admin_History { /** * The core class object. * * @since 1.5.3 * @var Boldgrid_Backup_Admin_Core */ private $core; /** * Max number of entries to keep in the history. * * @since 1.0.0 * @var int */ public $max_entries = 100; /** * An instance of Boldgrid_Backup_Premium_Admin_Core. * * @since 1.5.3 * @var Boldgrid_Backup_Premium_Admin_Core */ private $premium_core; /** * Constructor. * * @since 1.5.3 * * @param Boldgrid_Backup_Admin_Core $core Boldgrid_Backup_Admin_Core object. * @param Boldgrid_Backup_Premium_Admin_Core $premium_core Boldgrid_Backup_Premium_Admin_Core object. */ public function __construct( Boldgrid_Backup_Admin_Core $core, Boldgrid_Backup_Premium_Admin_Core $premium_core ) { $this->core = $core; $this->premium_core = $premium_core; } /** * Add a message to the history log. * * @since 1.5.3 * * @param string $message Message. */ public function add( $message ) { if ( empty( $message ) ) { return; } $history = $this->get(); // Determine our user_id. $sapi_type = php_sapi_name(); if ( $this->core->doing_cron && 'cli ' === substr( $sapi_type, 0, 3 ) ) { $user_id = 'Cron'; } elseif ( $this->core->doing_cron ) { $user_id = 'WP Cron'; } else { $user_id = get_current_user_id(); } $history[] = array( 'user_id' => $user_id, 'timestamp' => time(), 'message' => $message, ); $this->save( $history ); } /** * Enqueue scripts for the history page. * * @since 1.5.3 * * @param string $hook Hook name. */ public function admin_enqueue_scripts( $hook ) { if ( 'admin_page_boldgrid-backup-history' !== $hook ) { return; } } /** * Filter tools section. * * @since 1.5.3 * * @param array $sections Sections. * @return array */ public function filter_tools_section( array $sections ) { $history = $this->get(); $sections['sections'][] = array( 'id' => 'section_history', 'title' => __( 'History', 'boldgrid-backup' ), 'content' => include BOLDGRID_BACKUP_PREMIUM_PATH . '/admin/partials/history.php', ); return $sections; } /** * Get history. * * @since 1.5.3 * * @return array */ public function get() { $history = get_site_option( 'boldgrid_backup_history', array() ); return $history; } /** * Take action after a backup has been generated. * * @since 1.5.3 * * @param array $info Info. */ public function post_archive_files( array $info ) { if ( empty( $info['filepath'] ) ) { return; } $this->add( sprintf( // Translators: 1: File path. __( 'Backup file created: %1$s.', 'boldgrid-backup' ), $info['filepath'] ) ); } /** * Take action when a remove backup has been deleted due to retention * settings. * * @since 1.5.3 * * @param string $title Title. * @param string $message Message. */ public function remote_retention_deleted( $title, $message ) { if ( empty( $title ) || empty( $message ) ) { return; } $this->add( sprintf( // Translators: 1: Title, 2: Message. __( 'Due to your retention settings with %1$s, the following was deleted remotely: %2$s.', 'boldgrid-backup' ), $title, $message ) ); } /** * Take action when a file has been uploaded to a remove storage provider. * * @since 1.5.3 * * @param string $title Title. * @param string $filepath File path. */ public function remote_uploaded( $title, $filepath ) { if ( empty( $title ) || empty( $filepath ) ) { return; } $this->add( sprintf( // Translators: 1: File path, 2: Title. __( 'Backup file %1$s was uploaded to %2$s.', 'boldgrid-backup' ), $filepath, $title ) ); } /** * Take action when a local backup has been deleted due to retention * settings. * * @since 1.5.3 * * @param string $filepath File path. */ public function retention_deleted( $filepath ) { if ( empty( $filepath ) ) { return; } $this->add( sprintf( // Translators: 1: File path. __( 'Due to retention settings, the following backup was deleted: %1$s', 'boldgrid-backup' ), $filepath ) ); } /** * Take action when a plugin has been deleted. * * @since 1.5.3 * * @param string $plugin_file Plugin file name. */ public function delete_plugin( $plugin_file ) { $data = $this->core->utility->get_plugin_data( $plugin_file ); // Translators: 1: Plugin name, 2: Plugin version. $this->add( sprintf( __( '%1$s plugin (version %2$s) deleted.', 'boldgrid-backup' ), $data['Name'], $data['Version'] ) ); } /** * Save our history. * * @since 1.5.3 * * @param array $history History. * @return bool */ public function save( $history ) { if ( ! is_array( $history ) ) { return false; } $number_to_delete = count( $history ) - $this->max_entries; if ( $number_to_delete > 0 ) { for ( $x = 1; $x <= $number_to_delete; $x++ ) { array_shift( $history ); } } $updated = update_site_option( 'boldgrid_backup_history', $history ); return $updated; } /** * Take action when the settings have been updated. * * @since 1.5.3 */ public function settings_updated() { $this->add( BOLDGRID_BACKUP_TITLE . ' ' . __( 'settings updated.', 'boldgrid-backup' ) ); } /** * Take action when a theme has been switched. * * @since 1.5.3 * * @param string $new_name Name of the new theme. * @param WP_Theme $new_theme WP_Theme instance of the new theme. * @param WP_Theme $old_theme WP_Theme instance of the old theme. */ public function switch_theme( $new_name, $new_theme, $old_theme ) { // Translators: 1: Theme name, 2: Theme version. $this->add( sprintf( __( '%1$s theme (version %2$s) deactivated.', 'boldgrid-backup' ), $old_theme->get( 'Name' ), $old_theme->get( 'Version' ) ) ); // Translators: 1: Theme name, 2: Theme version. $this->add( sprintf( __( '%1$s theme (version %2$s) activated.', 'boldgrid-backup' ), $new_theme->get( 'Name' ), $new_theme->get( 'Version' ) ) ); } /** * Take option when the active_plugins option is updated. * * Read the values, determine which plugins were activated and which were * deactivated. * * @since 1.5.3 * * @param array $old_value Old active plugins. * @param array $value New active plugins. * @param string $option Option name. */ public function update_option_active_plugins( $old_value, $value, $option ) { $old_value = ! is_array( $old_value ) ? array() : $old_value; $value = ! is_array( $value ) ? array() : $value; $activated = array_diff( $value, $old_value ); $deactivated = array_diff( $old_value, $value ); foreach ( $activated as $key => $plugin ) { $data = $this->core->utility->get_plugin_data( $plugin ); // Translators: 1: Plugin name, 2: Plugin version. $this->add( sprintf( __( '%1$s plugin (version %2$s) activated.', 'boldgrid-backup' ), $data['Name'], $data['Version'] ) ); } foreach ( $deactivated as $key => $plugin ) { $data = $this->core->utility->get_plugin_data( $plugin ); // Translators: 1: Plugin name, 2: Plugin version. $this->add( sprintf( __( '%1$s plugin (version %2$s) deactivated.', 'boldgrid-backup' ), $data['Name'], $data['Version'] ) ); } } /** * Log whenever core, a plugin, or a theme are upgraded. * * @since 1.5.3 * * @param object $upgrader_object Upgrader object. * @param array $options Example: https://pastebin.com/ah4E048B . */ public function upgrader_process_complete( $upgrader_object, $options ) { $action = ! empty( $options['action'] ) ? $options['action'] : null; $type = ! empty( $options['type'] ) ? $options['type'] : null; if ( 'update' !== $action ) { return; } switch ( $type ) { case 'core': $wordpress_version = get_bloginfo( 'version' ); $this->add( sprintf( // Translators: 1: WordPress version. __( 'WordPress updated to version %1$s.', 'boldgrid-backup' ), get_bloginfo( 'version' ) ) ); break; case 'theme': /* * Get a list of themes that have been updated. * * During a bulk upgrade, such as in Dashboard > Updates, we'll have an array of * themes in $options['themes']. During an autoupdate, we'll just have one theme in * #options['theme']. */ if ( ! empty( $options['themes'] ) ) { $themes = $options['themes']; } else { $themes[] = $options['theme']; } foreach ( $themes as $theme ) { /* * Get our theme's new name and version. * * Typically, we could use wp_get_theme() to get this information. It works * during a bulk upgrade, but during an autoupdate it returns the old version * number and not the new one. Therefore, we'll just get the data directly from * the theme's style.css. */ $theme = wp_get_theme( $theme ); $style_path = $theme->get_stylesheet_directory() . '/style.css'; $default_headers = array( 'Version' => 'Version', 'Name' => 'Theme Name', ); $data = get_file_data( $style_path, $default_headers ); $this->add( sprintf( // Translators: 1: Theme version. __( '%1$s theme upgraded to version %2$s.', 'boldgrid-backup' ), $data['Name'], $data['Version'] ) ); } break; case 'plugin': $plugins = ! empty( $options['plugins'] ) ? $options['plugins'] : array( $options['plugin'] ); foreach ( $plugins as $plugin ) { $data = $this->core->utility->get_plugin_data( $plugin ); $this->add( sprintf( // Translators: 1: Plugin version. __( '%1$s plugin upgraded to version %2$s.', 'boldgrid-backup' ), $data['Name'], $data['Version'] ) ); } break; } } /** * Take action when a user has deleted a backup. * * @since 1.5.3 * * @param string $filepath File path. * @param bool $deleted Is deleted. */ public function user_deleted_backup( $filepath, $deleted ) { if ( empty( $filepath ) || ! $deleted ) { return; } $this->add( sprintf( // Translators: 1: File path that was deleted. __( 'The following backup file was deleted: %1$s', 'boldgrid-backup' ), $filepath ) ); } } admin/class-boldgrid-backup-premium-admin-historical.php000064400000043364147600362700017421 0ustar00 */ /** * Historical class. * * @since 1.5.3 */ class Boldgrid_Backup_Premium_Admin_Historical { /** * The core class object. * * @since 1.5.3 * @var Boldgrid_Backup_Admin_Core */ private $core; /** * Our historical dir. * * This will be 'historical-' followed by 16 random characters. * * @since 1.5.3 * @var string */ public $dir = null; /** * Errors. * * @since 1.5.3 * @var array */ public $errors = array(); /** * Regular expression search for a historical file. * * @since 1.5.3 * @var string */ public $filename_expression = '/^([0-9]{10})[.]([0-9]{10})[.](.*)$/'; /** * An array of language strings. * * @since 1.5.3 * @var array */ public $lang = array(); /** * An instance of Boldgrid_Backup_Premium_Admin_Core. * * @since 1.5.3 * @var Boldgrid_Backup_Premium_Admin_Core */ private $premium_core; /** * Constructor. * * @since 1.5.3 * * @param Boldgrid_Backup_Admin_Core $core Boldgrid_Backup_Admin_Core object. * @param Boldgrid_Backup_Premium_Admin_Core $premium_core Boldgrid_Backup_Premium_Admin_Core object. */ public function __construct( Boldgrid_Backup_Admin_Core $core, Boldgrid_Backup_Premium_Admin_Core $premium_core ) { $this->core = $core; $this->premium_core = $premium_core; $this->set_lang(); } /** * Create a page for the historical versions. * * @since 1.5.3 */ public function add_menu_items() { add_submenu_page( 'boldgrid-backup-settings', __( 'Historical', 'boldgrid-backup' ), __( 'Historical', 'boldgrid-backup' ), 'administrator', 'boldgrid-backup-historical', array( $this, 'page', ) ); } /** * Enqueue scripts for the historical page. * * @since 1.5.3 * * @param string $hook Hook name. */ public function admin_enqueue_scripts( $hook ) { if ( 'admin_page_boldgrid-backup-historical' !== $hook ) { return; } wp_register_script( 'boldgrid-backup-premium-admin-historical', plugin_dir_url( __FILE__ ) . 'js/boldgrid-backup-premium-admin-historical.js', array( 'jquery' ), BOLDGRID_BACKUP_PREMIUM_VERSION, true ); wp_localize_script( 'boldgrid-backup-premium-admin-historical', 'boldgrid_backup_premium_admin_historical', $this->lang ); wp_enqueue_script( 'boldgrid-backup-premium-admin-historical' ); wp_enqueue_style( 'boldgrid-backup-premium-historical', plugin_dir_url( __FILE__ ) . 'css/boldgrid-backup-premium-admin-historical.css', array(), BOLDGRID_BACKUP_PREMIUM_VERSION, 'all' ); } /** * Filter the config of files created by the backup dir class. * * @since 1.5.3 * * @param array $files Files. * @param string $backup_dir Backup directory path. * @return array */ public function create_dir_config( $files, $backup_dir ) { $backup_identifier = $this->core->get_backup_identifier(); $base_dir = 'historical-' . $backup_identifier . '-'; $dirlist = $this->core->wp_filesystem->dirlist( $backup_dir ); $dirlist = is_array( $dirlist ) ? $dirlist : array(); foreach ( $dirlist as $file ) { if ( 'd' !== $file['type'] ) { continue; } preg_match( '/^' . $base_dir . '[a-zA-Z0-9]{16}$/', $file['name'], $matches ); if ( ! empty( $matches ) ) { $this->dir = Boldgrid_Backup_Admin_Utility::trailingslashit( $backup_dir ) . $matches[0]; break; } } if ( is_null( $this->dir ) ) { /* * Generate a 16 character key to help make the "historical" directory name unique. * * Originally we used the wp_generate_password function to do this, however a report * came in of a fatal error when this function was undefined: * https://wordpress.org/support/topic/fatal-error-blocked-me-from-wp/ * * There are other methods to create a random string, such as time(). However, to keep * backwards compatibility, the random string MUST be 16 characters long. */ $random_key = substr( md5( time() ), -16 ); $this->dir = Boldgrid_Backup_Admin_Utility::trailingslashit( $backup_dir ) . $base_dir . $random_key; $files[] = array( 'type' => 'dir', 'path' => $this->dir, 'chmod' => 0700, ); } return $files; } /** * Determine if a historical copy of a file already exists. * * @since 1.5.3 * * @param string $file File path. * @return bool */ public function exists( $file ) { if ( empty( $file ) ) { return false; } $versions = $this->get_versions( $file ); if ( empty( $versions ) ) { return false; } $current_file = ABSPATH . $file; if ( ! $this->core->wp_filesystem->exists( $current_file ) ) { return false; } $this->set_dir(); foreach ( $versions as $file_data ) { $historical_file = Boldgrid_Backup_Admin_Utility::trailingslashit( $this->dir ) . dirname( $file ) . DIRECTORY_SEPARATOR . $file_data['name']; if ( sha1_file( $current_file ) === sha1_file( $historical_file ) ) { return true; } } return false; } /** * Get an array of all historical versions we have of a file. * * @since 1.5.3 * * @param string $file Filename. * @return array */ public function get_versions( $file ) { $this->set_dir(); $parts = pathinfo( $file ); $versions = array(); $dirlist = $this->core->wp_filesystem->dirlist( Boldgrid_Backup_Admin_Utility::trailingslashit( $this->dir ) . $parts['dirname'] ); if ( empty( $dirlist ) ) { return $versions; } foreach ( $dirlist as $file ) { preg_match( $this->filename_expression, $file['name'], $matches ); if ( empty( $matches[3] ) ) { continue; } if ( $matches[3] !== $parts['basename'] ) { continue; } $file['created'] = $matches[1]; $versions[] = $file; } return $versions; } /** * Create a clean array of ALL versions of a file. * * This array is useful when needing to display a table of all versions. * * @since 1.5.3 * * @param string $file Filename. * @return array */ public function get_versions_clean( $file ) { $versions_clean = array(); $versions = $this->find_all( $file ); foreach ( $versions as $type => $type_versions ) { foreach ( $type_versions as $version_key => $version_data ) { switch ( $type ) { case 'current': $versions_clean[] = array( 'type' => $type, // This is UTC by default. 'lastmodunix' => $version_data['lastmodunix'], 'size' => $version_data['size'], ); break; case 'historical': $versions_clean[] = array( 'type' => $type, 'created' => $version_data['created'], // This is UTC by default. 'lastmodunix' => $version_data['lastmodunix'], 'size' => $version_data['size'], 'name' => $version_data['name'], ); break; case 'in_archives': $versions_clean[] = array( 'type' => $type, 'created' => $version_data['created'], // This was previously converted to unix time. 'lastmodunix' => $version_data['mtime'], 'size' => $version_data['size'], 'archive_filepath' => $version_data['archive_filepath'], ); break; } } } usort( $versions_clean, function( $a, $b ) { return $a['lastmodunix'] > $b['lastmodunix']; } ); return $versions_clean; } /** * Find all versions of a file. * * We find the current file, all historical versions, and even versions in * all of the backups. * * @since 1.5.3 * * @param string $file Filename. * @return array */ public function find_all( $file ) { $versions = array(); $current = $this->core->wp_filesystem->dirlist( ABSPATH . $file ); if ( ! empty( $current ) ) { $versions['current'] = $current; } $historical = $this->get_versions( $file ); if ( ! empty( $historical ) ) { $versions['historical'] = $historical; } $in_archives = $this->find_in_archives( $file ); if ( ! empty( $in_archives ) ) { $versions['in_archives'] = $in_archives; } return $versions; } /** * Search for a file in all of our archives. * * @since 1.5.3 * * @param string $file Filename. * @return array */ public function find_in_archives( $file ) { $in_archives = array(); $archives = $this->core->get_archive_list(); foreach ( $archives as $archive ) { $this->core->archive->init( $archive['filepath'] ); $file_contents = $this->core->archive->get_file( $file, true ); if ( ! empty( $file_contents ) ) { $file_contents[0]['archive_filepath'] = $archive['filepath']; $file_contents[0]['created'] = $archive['lastmodunix']; $in_archives[] = $file_contents[0]; } } return $in_archives; } /** * Render the historical page. * * @since 1.5.3 */ public function page() { $file = ! empty( $_GET['file'] ) ? $_GET['file'] : null; // phpcs:ignore include BOLDGRID_BACKUP_PREMIUM_PATH . '/admin/partials/historical.php'; } /** * Restore a historical file. * * @since 1.5.3 * * @param string $file Example: "wp-content/index.php". * @param string $file_version Example: "1508848488.1508773689.index.php". * @return bool */ public function restore( $file, $file_version ) { $this->set_dir(); $full_historical = Boldgrid_Backup_Admin_Utility::trailingslashit( $this->dir ) . dirname( $file ) . DIRECTORY_SEPARATOR . $file_version; $full_current = ABSPATH . $file; if ( ! $this->core->wp_filesystem->exists( $full_historical ) ) { $this->errors[] = __( 'Historical file does not exist.', 'boldgrid-backup' ); return false; } /* * Create directory to store this historical file. * * WordPress' mkdir does not support recursive, so we'll do what they * do and simply mkdir with @. */ $dir = dirname( $full_current ); if ( ! $this->core->wp_filesystem->exists( $dir ) ) { @mkdir( $dir, FS_CHMOD_DIR, true ); // phpcs:ignore } $this->save( $file ); $copied = $this->core->wp_filesystem->copy( $full_historical, $full_current, true ); if ( ! $copied ) { $this->errors[] = __( 'Unable to copy historical file copy into place.', 'boldgrid-backup' ); return false; } $touched = false; preg_match( $this->filename_expression, $file_version, $matches ); if ( ! empty( $matches[2] ) ) { $touched = $this->core->wp_filesystem->touch( $full_current, $matches[2] ); } if ( ! $touched ) { $this->errors[] = __( 'Unable to touch file.', 'boldgrid-backup' ); return false; } $this->premium_core->history->add( sprintf( // Translators: 1: File, 2: Date/Time. __( 'A copy of %1$s from %2$s was restored.', 'boldgrid-backup' ), $file, gmdate( 'Y.m.d h:i:s a', $matches[2] ) ) ); return true; } /** * Save a file to the historical folder. * * @since 1.5.3 * * @param string $file Relative to ABSPATH. * @return bool */ public function save( $file ) { $exists = $this->exists( $file ); if ( $exists ) { return true; } $dirlist = $this->core->wp_filesystem->dirlist( ABSPATH . $file ); if ( empty( $dirlist ) ) { return false; } $this->set_dir(); // Create path to new historical file. $parts = pathinfo( $file ); $lastmodunix = $dirlist[ $parts['basename'] ]['lastmodunix']; $new_filename = $parts['dirname'] . DIRECTORY_SEPARATOR . time() . '.' . $lastmodunix . '.' . $parts['basename']; $new_path = Boldgrid_Backup_Admin_Utility::trailingslashit( $this->dir ) . $new_filename; /* * Create directory to store this historical file. * * WordPress' mkdir does not support recursive, so we'll do what they * do and simply mkdir with @. */ $new_path_parts = pathinfo( $new_path ); @mkdir( $new_path_parts['dirname'], 0700, true ); // phpcs:ignore // Copy the file and adjust the timestamp. $copied = $this->core->wp_filesystem->copy( ABSPATH . $file, $new_path ); if ( ! $copied ) { return false; } $touched = $this->core->wp_filesystem->touch( $new_path, $lastmodunix ); if ( $touched ) { $this->premium_core->history->add( sprintf( // Translators: 1: File. __( 'A copy of the following file was saved: %1$s', 'boldgrid-backup' ), $file ) ); } return $touched; } /** * Set the historical dir. * * Usually the dir will be set by self::create_dir_config, but this method * is to guarantee we have it set. * * @since 1.5.3 */ public function set_dir() { if ( ! is_null( $this->dir ) ) { return; } $this->core->backup_dir->get(); } /** * Set lang. * * @since 1.5.3 */ public function set_lang() { $icon_current = ' '; $icon_archive = ' '; $icon_historical = ' '; $this->lang = array( 'archive_file' => $icon_archive . __( 'Copy in a backup archive', 'boldgrid-backup' ), 'archive_file_description' => __( 'This is a copy of the file currently stored in a local archive file (zip file).', 'boldgrid-backup' ), 'current_file' => $icon_current . __( 'Current file', 'boldgrid-backup' ), 'current_file_description' => __( 'This is the actual file right now.', 'boldgrid-backup' ), 'historical_file' => $icon_historical . __( 'Local copy', 'boldgrid-backup' ), 'historical_file_description' => __( 'A "local copy" of a file is a copy of that file made that doesn\'t actually exist within a backup / .zip file. Local copies of files are usually created in one of two ways: (1) If you restore a single file from a backup, a copy of the file is made first. (2) You have used the save a copy feature we have added to the WordPress Plugin Editor.', 'boldgrid-backup' ), 'icon_warning' => $this->core->lang['icon_warning'], 'reloading_table' => __( 'Reloading table', 'boldgrid-backup' ), 'restoring' => __( 'Restoring', 'boldgrid-backup' ), 'unknown_error_load' => __( 'An unknown error occurred when attempting to find all versions of this file.', 'boldgrid-backup' ), 'unknown_error_restore' => __( 'An unknown error occurred when attempting to restore this file.', 'boldgrid-backup' ), ); } /** * Get an html table that lists all versions. * * @since 1.5.3 */ public function wp_ajax_get_historical_versions() { $error = __( 'There was an error retrieving historical versions.', 'boldgrid-backup' ); if ( ! current_user_can( 'update_plugins' ) ) { wp_send_json_error( $error . ' ' . __( 'Permission denied.', 'boldgrid-backup' ) ); } if ( ! check_ajax_referer( 'bgbkup_historical_version_page', 'security', false ) ) { wp_send_json_error( $error . ' ' . __( 'Invalid nonce.', 'boldgrid-backup' ) ); } $file = ! empty( $_POST['file'] ) ? $_POST['file'] : false; // phpcs:ignore if ( empty( $file ) ) { wp_send_json_error( $error . ' ' . __( 'Invalid file / version.', 'boldgrid-backup' ) ); } $versions_clean = $this->get_versions_clean( $file ); // Create a count of how many versions of a file we have. $version_count = array(); foreach ( $versions_clean as $version ) { $count = empty( $version_count[ $version['lastmodunix'] ] ) ? 1 : $version_count[ $version['lastmodunix'] ] + 1; $version_count[ $version['lastmodunix'] ] = $count; } $versions_table = '

' . sprintf( // Translators: 1: File count. __( 'We found %1$s different version(s) of this file you can restore.', 'boldgrid-backup' ), count( $version_count ) ) . '

'; $versions_table .= sprintf( ' ', __( 'Type', 'boldgrid-bacukp' ), __( 'Last Modified', 'boldgrid-backup' ), __( 'Size', 'boldgrid-backup' ) ); // Loop through each version and create a for it. $last_modified = null; $version_number = 0; foreach ( $versions_clean as $version ) { $versions_table .= include BOLDGRID_BACKUP_PREMIUM_PATH . '/admin/partials/historical/entry.php'; } $versions_table .= '
# %1$s %2$s %3$s
'; if ( empty( $versions_clean ) ) { $versions_table = sprintf( '

%1$s %2$s

', $this->core->lang['icon_warning'], __( 'No versions of this file could be found.', 'boldgrid-backup' ) ); } wp_send_json_success( $versions_table ); } /** * Restore a historical version. * * @since 1.5.3 */ public function wp_ajax_restore_historical() { $error = __( 'An error occurred while attempting to restore this file:', 'boldgrid-backup' ); if ( ! current_user_can( 'update_plugins' ) ) { wp_send_json_error( $error . ' ' . __( 'Permission denied.', 'boldgrid-backup' ) ); } if ( ! check_ajax_referer( 'bgbkup_historical_version_page', 'security', false ) ) { wp_send_json_error( $error . ' ' . __( 'Invalid nonce.', 'boldgrid-backup' ) ); } $file_version = ! empty( $_POST['file_version'] ) ? $_POST['file_version'] : false; // phpcs:ignore $file = ! empty( $_POST['file'] ) ? $_POST['file'] : false; // phpcs:ignore if ( empty( $file_version ) || empty( $file ) ) { wp_send_json_error( $error . ' ' . __( 'Invalid file / version.', 'boldgrid-backup' ) ); } $restored = $this->restore( $file, $file_version ); if ( $restored ) { wp_send_json_success( __( '✓ Restored', 'boldgrid-backup' ) ); } $error_message = ! empty( $this->errors ) ? implode( ' ', $this->errors ) : __( 'Unknown error.', 'boldgrid-backup' ); wp_send_json_error( $error . ' ' . $error_message ); } } admin/class-boldgrid-backup-premium-admin-core.php000064400000013744147600362700016207 0ustar00 */ /** * Class: Boldgrid_Backup_Premium_Admin_Core * * @since 1.0.0 */ class Boldgrid_Backup_Premium_Admin_Core { /** * Amazon S3 class. * * @since 1.0.0 * @var Boldgrid_Backup_Premium_Admin_Remote_Amazon_S3 */ public $amazon_s3; /** * An instance of the Boldgrid_Backup_Premium_Admin_Archive_Browser class. * * @since 1.5.3 * @var Boldgrid_Backup_Premium_Admin_Archive_Browser */ public $archive_browser; /** * Our DreamObjects class. * * @since 1.2.0 * @var Boldgrid_Backup_Premium_Admin_Remote_Dreamobjects */ public $dreamobjects; /** * An instance of Boldgrid_Backup_Premium_Admin_Core * * @since 1.1.0 * @var Boldgrid_Backup_Premium_Admin_Core */ public $google_drive; /** * An instance of Boldgrid_Backup_Premium_Admin_Historical. * * @since 1.5.3 * @var Boldgrid_Backup_Premium_Admin_Historical */ public $historical; /** * An instance of Boldgrid_Backup_Premium_Admin_History. * * @since 1.5.3 * @var Boldgrid_Backup_Premium_Admin_History */ public $history; /** * An instance of Boldgrid_Backup_Premium_Admin_Plugin_Editor. * * @since 1.5.3 * @var Boldgrid_Backup_Premium_Admin_Plugin_Editor */ public $plugin_editor; /** * An instance of Boldgrid_Backup_Premium_Admin_Recent. * * @since 1.5.4 * @var Boldgrid_Backup_Premium_Admin_Recent */ public $recent; /** * An instance of Boldgrid_Backup_Premium_Admin_Support. * * @since 1.1.0 * @var Boldgrid_Backup_Premium_Admin_Support */ public $support; /** * An instance of Boldgrid\Backup\Premium\Admin\Crypt. * * @since 1.3.0 * @var Boldgrid\Backup\Premium\Admin\Crypt */ public $crypt; /** * An instance of Boldgrid\Backup\Premium\Admin\Settings. * * @since 1.3.0 * @var Boldgrid\Backup\Premium\Admin\Settings */ public $settings; /** * Configuration array. * * @since 1.0.0 * @access private * @var array * @staticvar */ private static $configs; /** * The core class object. * * @since 1.0.0 * @access private * @var Boldgrid_Backup_Admin_Core */ private $core; /** * Constructor. * * @since 1.0.0 * * @param Boldgrid_Backup_Admin_Core $core Boldgrid_Backup_Admin_Core object. */ public function __construct( Boldgrid_Backup_Admin_Core $core ) { $this->core = $core; $this->archive_browser = new Boldgrid_Backup_Premium_Admin_Archive_Browser( $this->core, $this ); $this->amazon_s3 = new Boldgrid_Backup_Premium_Admin_Remote_Amazon_S3( $this->core, $this ); $this->historical = new Boldgrid_Backup_Premium_Admin_Historical( $this->core, $this ); $this->plugin_editor = new Boldgrid_Backup_Premium_Admin_Plugin_Editor( $this->core, $this ); $this->history = new Boldgrid_Backup_Premium_Admin_History( $this->core, $this ); $this->recent = new Boldgrid_Backup_Premium_Admin_Recent( $this->core, $this ); $this->support = new Boldgrid_Backup_Premium_Admin_Support( $this->core, $this ); $this->crypt = new \Boldgrid\Backup\Premium\Admin\Crypt( $this->core, $this ); $this->settings = new \Boldgrid\Backup\Premium\Admin\Settings( $this->core, $this ); $this->google_drive = new Boldgrid_Backup_Premium_Admin_Remote_Google_Drive( $this->core, $this ); $this->dreamobjects = new Boldgrid_Backup_Premium_Admin_Remote_Dreamobjects(); $this->prepare_plugin_update(); } /** * Prepare the plugin update class. * * @since 1.0.0 * * @see self::get_configs() */ public function prepare_plugin_update() { $is_cron = ( defined( 'DOING_CRON' ) && DOING_CRON ); $is_wpcli = ( defined( 'WP_CLI' ) && WP_CLI ); if ( $is_cron || $is_wpcli || is_admin() ) { require_once BOLDGRID_BACKUP_PREMIUM_PATH . '/admin/class-boldgrid-backup-premium-admin-update.php'; $plugin_update = new Boldgrid_Backup_Premium_Admin_Update( self::get_configs() ); add_action( 'init', array( $plugin_update, 'add_hooks', ) ); } } /** * Get configuration settings. * * @since 1.0.0 * * @static * * @return array An array of configuration settings. */ public static function get_configs() { // If the configuration array was already created, then return it. if ( ! empty( self::$configs ) ) { return self::$configs; } // Set the config directory. $config_dir = BOLDGRID_BACKUP_PREMIUM_PATH . '/includes/config'; // Set the config file paths. $global_config_path = $config_dir . '/config.plugin.php'; $local_config_path = $config_dir . '/config.local.php'; // Initialize $global_configs array. $global_configs = array(); // If a global config file exists, read the global configuration settings. if ( file_exists( $global_config_path ) ) { $global_configs = require $global_config_path; } // Initialize $local_configs array. $local_configs = array(); // If a local configuration file exists, then read the settings. if ( file_exists( $local_config_path ) ) { $local_configs = require $local_config_path; } // If an api key hash stored in the database, then set it as the global api_key. $api_key_from_database = get_option( 'boldgrid_api_key' ); if ( ! empty( $api_key_from_database ) ) { $global_configs['api_key'] = $api_key_from_database; } // Get the WordPress site url and set it in the global configs array. $global_configs['site_url'] = get_site_url(); // Merge global and local configuration settings. if ( ! empty( $local_configs ) ) { $configs = array_replace_recursive( $global_configs, $local_configs ); } else { $configs = $global_configs; } // Set the configuration array in the class property. self::$configs = $configs; // Return the configuration array. return $configs; } } admin/class-boldgrid-backup-premium-admin-archive-browser.php000064400000011335147600362700020353 0ustar00 */ /** * Amazon S3 class. * * @since 1.0.0 */ class Boldgrid_Backup_Premium_Admin_Archive_Browser { /** * The core class object. * * @since 1.5.3 * @var Boldgrid_Backup_Admin_Core */ private $core; /** * An instance of Boldgrid_Backup_Premium_Admin_Core. * * @since 1.5.3 * @var Boldgrid_Backup_Premium_Admin_Core */ private $premium_core; /** * Constructor. * * @since 1.5.3 * * @param Boldgrid_Backup_Admin_Core $core Boldgrid_Backup_Admin_Core object. * @param Boldgrid_Backup_Premium_Admin_Core $premium_core Boldgrid_Backup_Premium_Admin_Core object. */ public function __construct( Boldgrid_Backup_Admin_Core $core, Boldgrid_Backup_Premium_Admin_Core $premium_core ) { $this->core = $core; $this->premium_core = $premium_core; } /** * Enqueue scripts on the archive details page. * * @since 1.5.3 */ public function enqueue_archive_details() { wp_register_script( 'boldgrid-backup-premium-admin-zip-browser', plugin_dir_url( __FILE__ ) . 'js/boldgrid-backup-premium-admin-zip-browser.js', array( 'jquery' ), BOLDGRID_BACKUP_PREMIUM_VERSION, true ); $translations = array( 'restoring' => __( 'Restoring', 'boldgrid-backup' ), 'unknownError' => __( 'An unknown error occurred when attempting to restore this file.', 'boldgrid-backup' ), ); wp_localize_script( 'boldgrid-backup-premium-admin-zip-browser', 'boldgrid_backup_premium_zip_browser', $translations ); wp_enqueue_script( 'boldgrid-backup-premium-admin-zip-browser' ); } /** * Provide a list of premium features for one file in an archive. * * For example, allow the user to restore one file from an archive. * * @since 1.5.3 * * @param string $response Response. * @param string $file File. * @return string */ public function wp_ajax_file_actions( $response, $file ) { $response = sprintf( ' %1$s | %2$s

%4$s

', __( 'Restore this version', 'boldgrid-backup' ), __( 'Find other versions to restore', 'boldgrid-backup' ), $file, __( 'When you choose to restore a single file from backup, a copy of the file is made just before it is overwritten. You can restore this file on the Find other versions to restore page.', 'boldgrid-backup' ) ); return $response; } /** * Restore one file (via an ajax call). * * @since 1.5.3 */ public function wp_ajax_restore_file() { $error = __( 'An error occurred while attempting to restore this file:', 'boldgrid-backup' ); if ( ! current_user_can( 'update_plugins' ) ) { wp_send_json_error( $error . ' ' . __( 'Permission denied.', 'boldgrid-backup' ) ); } /* * Nonce validation. * * Single files can be restored from either the: * 1. Arhchive Browser (bgbkup_archive_details_page). Total Upkeep provides a general nonce * for usage on the Archive Details page. * 2. Historical Versions page (bgbkup_historical_version_page). */ $valid_nonce = check_ajax_referer( 'bgbkup_archive_details_page', 'security', false ) || check_ajax_referer( 'bgbkup_historical_version_page', 'security', false ); if ( ! $valid_nonce ) { wp_send_json_error( $error . ' ' . __( 'Invalid nonce.', 'boldgrid-backup' ) ); } $filename = ! empty( $_POST['filename'] ) ? $_POST['filename'] : false; // phpcs:ignore $filepath = $this->core->backup_dir->get_path_to( $filename ); $file = ! empty( $_POST['file'] ) ? $_POST['file'] : false; // phpcs:ignore if ( ! $this->core->archive->is_archive( $filepath ) || empty( $file ) ) { wp_send_json_error( $error . ' ' . __( 'Invalid file / filepath.', 'boldgrid-backup' ) ); } $this->premium_core->historical->save( $file ); $zip = new Boldgrid_Backup_Admin_Compressor_Pcl_Zip( $this->core ); $status = $zip->extract_one( $filepath, $file ); if ( ! $status ) { $error_message = ! empty( $zip->test_errors ) ? implode( '
', $zip->test_errors ) : __( 'Unknown error', 'boldgrid-backup' ); wp_send_json_error( $error_message ); } $this->premium_core->history->add( sprintf( // Translators: 1: Filename, 2: File path. __( 'A copy of %1$s has been restored from this archive file: %2$s.', 'boldgrid-bacup' ), $file, $filepath ) ); wp_send_json_success( __( '✓ Restored', 'boldgrid-backup' ) ); } } admin/class-boldgrid-backup-premium-timely-auto-updates.php000064400000011051147600362700020072 0ustar00 */ /** * Class: Boldgrid_Backup_Premium_Timely_Auto_Updates. * * This is used to generate the controls for the Settings page. * * @since 1.5.0 * @package Boldgrid_Backup_Premium * @subpackage Boldgrid_Backup_Premium/includes * @author BoldGrid.com */ class Boldgrid_Backup_Premium_Timely_Auto_Updates { /** * No Auto Updates Set. * * @since 1.4.0 * * @param array $auto_update_settings Auto Update Settings. * @return string */ public function no_auto_updates_set( $auto_update_settings ) { foreach ( $auto_update_settings['plugins'] as $plugin ) { if ( '1' === $plugin ) { return false; } } foreach ( $auto_update_settings['themes'] as $theme ) { if ( '1' === $theme ) { return false; } } $auto_updates = new Boldgrid_Backup_Admin_Auto_Updates(); $auto_updates->auto_update_core(); $results = array(); $results[] = apply_filters( 'allow_dev_auto_core_updates', false ); $results[] = apply_filters( 'allow_major_auto_core_updates', false ); $results[] = apply_filters( 'allow_minor_auto_core_updates', false ); $results[] = apply_filters( 'auto_update_translation', false, wp_get_translation_updates() ); foreach ( $results as $result ) { if ( true === $result ) { return false; } } return true; } /** * Get When To Make Auto Update Markup. * * @since 1.4.0 * * @param array $auto_update_settings Auto Update Settings from DB. * @return string */ public function get_markup( $auto_update_settings ) { // $default_days_setting is used for a placeholder in days input field if the value is not already set. $default_days_setting = 7; $timely_updates_enabled = isset( $auto_update_settings['timely-updates-enabled'] ) ? (bool) $auto_update_settings['timely-updates-enabled'] : false; $timely_updates_days = isset( $auto_update_settings['days'] ) ? $auto_update_settings['days'] : $default_days_setting; $when_updates_markup = '

' . esc_html__( 'Its often that when new software is released, there are bugs. Users who update right away end up finding those bugs first. It\'s a good idea to delay updating until the developers have had time to work out the kinks. ', 'boldgrid-backup' ) . '

' . esc_html__( 'When To Perform Updates', 'boldgrid-backup' ) . '

' . esc_html__( 'Select the number of days you wish to wait after an update is released before updating plugins or themes.', 'boldgrid-backup' ) . '

'; $when_updates_markup .= ' ' . esc_html__( 'Days since update was released' ) . '
'; if ( $this->no_auto_updates_set( $auto_update_settings ) ) { $when_updates_markup .= '

Timely Auto Updates are enabled, but you have not selected any themes or plugins to Auto Update.

'; } $when_updates_markup .= '
'; return $when_updates_markup; } } admin/class-boldgrid-backup-premium-admin-themes.php000064400000021127147600362700016536 0ustar00 */ /** * Class: Boldgrid_Backup_Admin_Premium_Themes. * * This is a generic class designed to help manage how this plugin behaves within the scope of. * "WordPress Dashboard > Themes > *". * * @since 1.4.0 */ class Boldgrid_Backup_Premium_Admin_Themes { /** * Core. * * @since 1.4.0 * @var Boldgrid_Backup_Admin_Core */ public $core; /** * Themes. * * @since 1.4.0 * @var \Boldgrid\Library\Library\Theme\Themes */ public $themes; /** * Settings. * * @var array */ public $settings; /** * Auto Update Settings. * * @var array */ public $auto_update_settings; /** * Data. * * @var array */ public $data; /** * Constructor. * * @since 1.4.0 */ public function __construct() { $this->core = apply_filters( 'boldgrid_backup_get_core', null ); $this->themes = new \Boldgrid\Library\Library\Theme\Themes(); $this->settings = get_site_option( 'boldgrid_backup_settings', array() ); if ( array_key_exists( 'auto_update', $this->settings ) ) { $this->auto_update_settings = $this->settings['auto_update']; } } /** * Admin Enqueue Scripts. * * @since 1.4.0 * * @param string $hook Hook String passed to callback. */ public function admin_enqueue_scripts( $hook ) { if ( 'themes.php' !== $hook ) { return; } $handle = 'boldgrid-backup-premium-admin-timely-updates'; wp_register_script( $handle, plugin_dir_url( dirname( __FILE__ ) ) . 'admin/js/boldgrid-backup-premium-admin-timely-updates.js', array( 'jquery' ), BOLDGRID_BACKUP_PREMIUM_VERSION, true ); $this->add_update_message(); $translation = $this->data; wp_localize_script( $handle, 'BgbckTheme', $translation ); wp_enqueue_script( $handle ); } /** * Filters auto update markup on themes page. * * In WP5.5+ the core UI already includes a message indicating when * auto updates will occur. Therefore we must filter that markup to include * the correct amount of time based on Timely Auto Update settings. * * Because of the fact that the $template string is a JS template, not actual HTML, this replacement needs to be * done via regex rather than using PHP's DOMDocument. While it is possible that this pattern could change in future * WordPress updates, it is not likely. In the event that a change is made to this template by WordPress, then the pattern * will need to be updated as well. * * @since 1.14.3 * * @param string $template The unfiltered javascript template for themes page. * * @return string */ public function filter_update_message( $template ) { $time_pattern = '/(\\n\\t\\t\\t\\t<# } else { #>\\n\\t\\t\\t\\t\\t