├── .gitmodules ├── .gitignore ├── screenshot-1.png ├── screenshot-2.png ├── screenshot-3.png ├── languages ├── wp-remote-wordpress-plugin.mo ├── index.php └── wp-remote-wordpress-plugin.pot ├── tests ├── pluginsTest.php └── bootstrap.php ├── wprp.integration.php ├── phpunit.xml ├── inc ├── class-wprp-core-upgrader-skin.php ├── class-wprp-plugin-upgrader-skin.php ├── class-wprp-theme-upgrader-skin.php └── class-wprp-automatic-upgrader-skin.php ├── CONTRIBUTING.md ├── cli └── wprp.cli.php ├── .travis.yml ├── bin └── install-wp-tests.sh ├── wprp.content.php ├── wprp.compatability.php ├── wprp.admin.php ├── wprp.log.php ├── wprp.themes.php ├── plugin.php ├── readme.txt ├── wprp.plugins.php ├── wprp.api.php ├── wprp.backups.php └── wprp.hm.backup.php /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | trunk 2 | .svn 3 | .DS_Store 4 | .idea -------------------------------------------------------------------------------- /screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akshatc/WP-Remote-WordPress-Plugin/HEAD/screenshot-1.png -------------------------------------------------------------------------------- /screenshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akshatc/WP-Remote-WordPress-Plugin/HEAD/screenshot-2.png -------------------------------------------------------------------------------- /screenshot-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akshatc/WP-Remote-WordPress-Plugin/HEAD/screenshot-3.png -------------------------------------------------------------------------------- /languages/wp-remote-wordpress-plugin.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akshatc/WP-Remote-WordPress-Plugin/HEAD/languages/wp-remote-wordpress-plugin.mo -------------------------------------------------------------------------------- /tests/pluginsTest.php: -------------------------------------------------------------------------------- 1 | assertTrue( function_exists( 'wprp_catch_api_call' ) ); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /languages/index.php: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | ./tests/ 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /inc/class-wprp-core-upgrader-skin.php: -------------------------------------------------------------------------------- 1 | error = $error; 9 | } 10 | 11 | function feedback( $feedback ) { 12 | $this->feedback = $feedback; 13 | } 14 | 15 | function before() { } 16 | 17 | function after() { } 18 | 19 | function header() { } 20 | 21 | function footer() { } 22 | 23 | } -------------------------------------------------------------------------------- /inc/class-wprp-plugin-upgrader-skin.php: -------------------------------------------------------------------------------- 1 | error = $error; 10 | } 11 | 12 | function feedback( $feedback ) { 13 | $this->feedback = $feedback; 14 | } 15 | 16 | function before() { } 17 | 18 | function after() { } 19 | 20 | function header() { } 21 | 22 | function footer() { } 23 | } -------------------------------------------------------------------------------- /inc/class-wprp-theme-upgrader-skin.php: -------------------------------------------------------------------------------- 1 | error = $error; 10 | } 11 | 12 | function feedback( $feedback ) { 13 | $this->feedback = $feedback; 14 | } 15 | 16 | function before() { } 17 | 18 | function after() { } 19 | 20 | function header() { } 21 | 22 | function footer() { } 23 | 24 | } -------------------------------------------------------------------------------- /inc/class-wprp-automatic-upgrader-skin.php: -------------------------------------------------------------------------------- 1 | error = $error; 10 | } 11 | 12 | function feedback( $feedback ) { 13 | $this->feedback = $feedback; 14 | } 15 | 16 | function before() { } 17 | 18 | function after() { } 19 | 20 | function header() { } 21 | 22 | function footer() { } 23 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contribution guidelines ## 2 | 3 | ## Workflow ## 4 | 5 | * Develop on a feature branch and send a pull request for review. 6 | * Assign the pull request to one of the following contacts: 7 | * Primary: Theo Savage [@tcrsavage](https://github.com/tcrsavage) 8 | * Secondary: Joe Hoyle [@joe_hoyle](https://github.com/joehoyle) 9 | 10 | ## Coding Standards ## 11 | 12 | Please follow these recommendations 13 | [http://codex.wordpress.org/WordPress_Coding_Standards](http://codex.wordpress.org/WordPress_Coding_Standards) 14 | -------------------------------------------------------------------------------- /cli/wprp.cli.php: -------------------------------------------------------------------------------- 1 | 1) { 13 | WP_CLI::line( 'Please set the args correctly' ); 14 | die(); 15 | } 16 | delete_option( 'wpr_api_key' ); 17 | add_option( 'wpr_api_key', $args[0]); 18 | WP_CLI::line( 'API Key Set' ); 19 | 20 | // WP_CLI::line( sprintf( "[%s] Worker %d completed its work.", date( 'Y-m-d H:i:s' ), getmypid() ) ); 21 | } 22 | 23 | } 24 | 25 | WP_CLI::add_command( 'wpremote', 'WPRP_tasks' ); -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.2 5 | - 5.3 6 | - 5.4 7 | 8 | env: 9 | - WP_VERSION=master WP_MULTISITE=0 10 | - WP_VERSION=master WP_MULTISITE=1 11 | - WP_VERSION=3.5.1 WP_MULTISITE=0 12 | - WP_VERSION=3.5.1 WP_MULTISITE=1 13 | - WP_VERSION=3.4 WP_MULTISITE=0 14 | - WP_VERSION=3.4 WP_MULTISITE=1 15 | - WP_VERSION=3.3 WP_MULTISITE=0 16 | - WP_VERSION=3.3 WP_MULTISITE=1 17 | - WP_VERSION=3.2 WP_MULTISITE=0 18 | - WP_VERSION=3.2 WP_MULTISITE=1 19 | 20 | notifications: 21 | secure: "gwybVEhn3tYVngWMegtYJ0dfSBjLa1+0LsD9LXypHtyMjkUBuoEu0NWkupkp4HA27Euq5Cryg01vhWhy7+8kUIFeSnkYDqcvIHduPvyYqSwaZFNEgINZ/2OiQOomg23C+/sYxqzmXeFiRChHvR26/9FfhLSDqvGPZ4/n/URRFgg=" 22 | 23 | before_script: 24 | - export WP_TESTS_DIR=/tmp/wordpress-tests/ 25 | - bash bin/install-wp-tests.sh wordpress_test root '' $WP_VERSION 26 | 27 | script: phpunit 28 | -------------------------------------------------------------------------------- /bin/install-wp-tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ $# -lt 3 ]; then 4 | echo "usage: $0 [wp-version]" 5 | exit 1 6 | fi 7 | 8 | DB_NAME=$1 9 | DB_USER=$2 10 | DB_PASS=$3 11 | WP_VERSION=${4-master} 12 | 13 | set -ex 14 | 15 | # set up a WP install 16 | WP_CORE_DIR=/tmp/wordpress/ 17 | mkdir -p $WP_CORE_DIR 18 | wget -nv -O /tmp/wordpress.tar.gz https://github.com/WordPress/WordPress/tarball/$WP_VERSION 19 | tar --strip-components=1 -zxmf /tmp/wordpress.tar.gz -C $WP_CORE_DIR 20 | 21 | # set up testing suite 22 | svn co --ignore-externals --quiet http://unit-tests.svn.wordpress.org/trunk/ $WP_TESTS_DIR 23 | 24 | cd $WP_TESTS_DIR 25 | cp wp-tests-config-sample.php wp-tests-config.php 26 | sed -i "s:dirname( __FILE__ ) . '/wordpress/':'$WP_CORE_DIR':" wp-tests-config.php 27 | sed -i "s/yourdbnamehere/$DB_NAME/" wp-tests-config.php 28 | sed -i "s/yourusernamehere/$DB_USER/" wp-tests-config.php 29 | sed -i "s/yourpasswordhere/$DB_PASS/" wp-tests-config.php 30 | 31 | # create database 32 | mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS" 33 | -------------------------------------------------------------------------------- /wprp.content.php: -------------------------------------------------------------------------------- 1 | 0 ) ) ); 13 | $num_comments = wp_count_comments(); 14 | $num_themes = count( wp_get_themes() ); 15 | $num_plugins = count( get_plugins() ); 16 | $num_users = count_users(); 17 | 18 | $content_summary = array( 19 | 'post_count' => ( ! empty( $num_posts->publish ) ) ? $num_posts->publish : 0, 20 | 'page_count' => ( ! empty( $num_pages->publish ) ) ? $num_pages->publish : 0, 21 | 'category_count' => $num_categories, 22 | 'comment_count' => ( ! empty( $num_comments->total_comments ) ) ? $num_comments->total_comments: 0, 23 | 'theme_count' => $num_themes, 24 | 'plugin_count' => $num_plugins, 25 | 'user_count' => ( ! empty( $num_users['total_users'] ) ) ? $num_users['total_users'] : 0 26 | ); 27 | 28 | return $content_summary; 29 | } 30 | 31 | 32 | -------------------------------------------------------------------------------- /wprp.compatability.php: -------------------------------------------------------------------------------- 1 | $plugin_name ) : 42 | 43 | ?> 44 | 45 |
46 | 47 | 48 | 49 |

50 | 51 | 52 | 53 | 54 | 55 |

56 | 57 |
58 | 59 | \n" 13 | "Language-Team: Human Made Limited \n" 14 | "X-Generator: Poedit 1.5.7\n" 15 | 16 | #: plugin.php:47 17 | msgid "WP Remote requires PHP version 5.2.4 or greater." 18 | msgstr "" 19 | 20 | #: plugin.php:180 21 | msgid "The filesystem is not writable with the supplied credentials" 22 | msgstr "" 23 | 24 | #: wprp.admin.php:29 25 | msgid "WP Remote is almost ready" 26 | msgstr "" 27 | 28 | #: wprp.admin.php:29 29 | msgid "enter your API key to continue" 30 | msgstr "" 31 | 32 | #: wprp.admin.php:33 33 | msgid "Save API Key" 34 | msgstr "" 35 | 36 | #: wprp.admin.php:39 37 | msgid "Don't have a WP Remote account yet?" 38 | msgstr "" 39 | 40 | #: wprp.admin.php:39 41 | msgid "Sign up" 42 | msgstr "" 43 | 44 | #: wprp.admin.php:39 45 | msgid "register your site, and report back once you've grabbed your API key." 46 | msgstr "" 47 | 48 | #: wprp.admin.php:71 49 | msgid "WP Remote API Key successfully added" 50 | msgstr "" 51 | 52 | #: wprp.admin.php:71 53 | msgid "WP Remote" 54 | msgstr "" 55 | 56 | #: wprp.backups.php:140 57 | msgid "No backup was found" 58 | msgstr "" 59 | 60 | #: wprp.backups.php:190 wprp.backups.php:198 61 | msgid "Calculating" 62 | msgstr "" 63 | 64 | #: wprp.backups.php:213 65 | msgid "Dumping Database %s" 66 | msgstr "" 67 | 68 | #: wprp.backups.php:219 69 | msgid "Verifying Database Dump %s" 70 | msgstr "" 71 | 72 | #: wprp.backups.php:225 73 | msgid "Creating zip archive %s" 74 | msgstr "" 75 | 76 | #: wprp.backups.php:231 77 | msgid "Verifying Zip Archive %s" 78 | msgstr "" 79 | 80 | #: wprp.backups.php:326 81 | msgid "" 82 | "This %s file ensures that other people cannot download your backup files." 83 | msgstr "" 84 | 85 | #: wprp.backups.php:335 86 | msgid "WP Remote Backup" 87 | msgstr "" 88 | 89 | #: wprp.compatability.php:10 90 | msgid "BulletProof Security" 91 | msgstr "" 92 | 93 | #: wprp.compatability.php:11 94 | msgid "Wordfence Security" 95 | msgstr "" 96 | 97 | #: wprp.compatability.php:12 98 | msgid "Better WP Security" 99 | msgstr "" 100 | 101 | #: wprp.compatability.php:13 102 | msgid "Wordpress Firewall 2" 103 | msgstr "" 104 | 105 | #: wprp.compatability.php:48 106 | msgid "Don't show again" 107 | msgstr "" 108 | 109 | #: wprp.compatability.php:52 110 | msgid "The plugin" 111 | msgstr "" 112 | 113 | #: wprp.compatability.php:52 114 | msgid "may cause issues with WP Remote." 115 | msgstr "" 116 | 117 | #: wprp.compatability.php:54 118 | msgid "Click here for instructions on how to resolve this issue" 119 | msgstr "" 120 | 121 | #: wprp.hm.backup.php:289 122 | msgid "archive filename must be a non empty string" 123 | msgstr "" 124 | 125 | #: wprp.hm.backup.php:292 126 | msgid "invalid file extension for archive filename" 127 | msgstr "" 128 | 129 | #: wprp.hm.backup.php:334 130 | msgid "database dump filename must be a non empty string" 131 | msgstr "" 132 | 133 | #: wprp.hm.backup.php:337 134 | msgid "invalid file extension for database dump filename" 135 | msgstr "" 136 | 137 | #: wprp.hm.backup.php:370 138 | msgid "Invalid root path %s must be a valid directory path" 139 | msgstr "" 140 | 141 | #: wprp.hm.backup.php:401 142 | msgid "Invalid backup path %s must be a non empty (string)" 143 | msgstr "" 144 | 145 | #: wprp.hm.backup.php:456 146 | msgid "" 147 | "Invalid backup type %s must be one of (string) file, database or complete" 148 | msgstr "" 149 | -------------------------------------------------------------------------------- /wprp.admin.php: -------------------------------------------------------------------------------- 1 | 22 | 23 |
24 | 25 |
26 | 27 |

28 | 29 | , 30 | 31 | 32 | 33 | 34 | 35 |

36 | 37 |

38 | 39 | , 40 | 41 |

42 | 43 | 44 | 45 | 49 | 50 |
51 | 52 |
53 | 54 | 55 | base != 'plugins' || empty( $_GET['settings-updated'] ) || ! wprp_get_api_keys() ) 68 | return; ?> 69 | 70 |
71 |

, close this window to go back to .

72 |
73 | 74 | ' . __( 'Clear API key' ) . ''; 93 | array_unshift($links, $settings_link); 94 | return $links; 95 | } 96 | 97 | add_filter( "plugin_action_links_" . WPRP_PLUGIN_BASE, 'wprp_plugin_add_settings_link' ); 98 | 99 | /** 100 | * Register WPR Pages 101 | */ 102 | function wpr_register_pages() { 103 | add_submenu_page( null, __('WP Remote Settings'), __('WP Remote Settings'), 'activate_plugins', 'wpremote', 'wpr_settings_page' ); 104 | } 105 | add_action('admin_menu', 'wpr_register_pages'); 106 | 107 | /** 108 | * Show settings page 109 | * TODO: Implement a more comprehensive setting page 110 | */ 111 | function wpr_settings_page( ) { 112 | delete_wpr_options(); 113 | // TODO : Build proper settings page 114 | echo 'Successfully cleared API key. Redirecting back to the plugins page...'; 115 | echo ''; 116 | } -------------------------------------------------------------------------------- /wprp.log.php: -------------------------------------------------------------------------------- 1 | setup_actions(); 20 | } 21 | 22 | public function disable_logging() { 23 | $this->is_logging_enabled = false; 24 | } 25 | 26 | public function setup_actions() { 27 | 28 | if ( ! $this->is_logging_enabled ) 29 | return; 30 | 31 | add_action( 'wp_login', array( $this, 'action_wp_login' ), 10, 2 ); 32 | add_action( 'user_register', array( $this, 'action_user_register' ) ); 33 | add_action( 'profile_update', array( $this, 'action_profile_updated' ), 10, 2 ); 34 | 35 | 36 | add_action( 'update_option_current_theme', array( $this, 'updated_option_current_theme' ), 10, 2 ); 37 | } 38 | 39 | public function action_wp_login( $user_login, $user ) { 40 | 41 | // we are only interested in administators 42 | if ( ! array_intersect( $user->roles, array( 'administrator' ) ) ) 43 | return; 44 | 45 | $this->add_item( array( 46 | 'type' => 'user', 47 | 'action' => 'login', 48 | 'remote_user' => array( 'user_login' => $user_login, 'display_name' => $user->display_name ), 49 | )); 50 | } 51 | 52 | public function action_user_register( $user_id ) { 53 | 54 | $new_user = get_user_by( 'id', $user_id ); 55 | 56 | // we are only interested in administators 57 | if ( ! array_intersect( $new_user->roles, array( 'administrator' ) ) ) 58 | return; 59 | 60 | $this->add_item( array( 61 | 'type' => 'user', 62 | 'action' => 'create', 63 | 'user_login' => $new_user->user_login, 64 | 'display_name' => $new_user->display_name, 65 | 'role' => $new_user->roles[0], 66 | /** remote_user is added in the `add_item()` method **/ 67 | )); 68 | } 69 | 70 | public function action_profile_updated( $user_id, $old_user_data ) { 71 | 72 | $user_data = get_user_by( 'id', $user_id ); 73 | 74 | // we are only interested in administators 75 | if ( ! array_intersect( $user_data->roles, array( 'administrator' ) ) ) 76 | return; 77 | 78 | 79 | if ( $user_data->user_email !== $old_user_data->user_email ) { 80 | $this->add_item( array( 81 | 'type' => 'user', 82 | 'action' => 'email-update', 83 | 'old_email' => $old_user_data->user_email, 84 | 'new_email' => $user_data->user_email, 85 | /** remote_user is added in the `add_item()` method **/ 86 | )); 87 | } 88 | 89 | if ( $user_data->user_pass !== $old_user_data->user_pass ) { 90 | $this->add_item( array( 91 | 'type' => 'user', 92 | 'action' => 'password-update', 93 | /** remote_user is added in the `add_item()` method **/ 94 | )); 95 | } 96 | } 97 | 98 | public function updated_option_current_theme( $old_theme, $new_theme ) { 99 | 100 | $this->add_item( array( 101 | 'type' => 'theme', 102 | 'action' => 'switch', 103 | 'old_theme' => $old_theme, 104 | 'new_theme' => $new_theme, 105 | /** remote_user is added in the `add_item()` method **/ 106 | )); 107 | } 108 | 109 | public function add_item( $item ) { 110 | 111 | if ( ! $this->is_logging_enabled ) 112 | return; 113 | 114 | $item = wp_parse_args( $item, array( 115 | 'date' => time(), 116 | 'remote_user' => is_user_logged_in() ? array( 'user_login' => wp_get_current_user()->user_login, 'display_name' => wp_get_current_user()->display_name ) : array(), 117 | )); 118 | 119 | $items = $this->get_items(); 120 | $items[] = $item; 121 | 122 | // only store the last 100 items 123 | if ( count( $items ) > 100 ) 124 | $items = array_slice( $items, 0, 100 ); 125 | 126 | update_option( 'wprp_log', $items ); 127 | } 128 | 129 | public function get_items() { 130 | 131 | return get_option( 'wprp_log', array() ); 132 | } 133 | 134 | public function delete_items() { 135 | 136 | delete_option( 'wprp_log' ); 137 | } 138 | } 139 | 140 | add_action( 'plugins_loaded', array( 'WPRP_Log', 'get_instance' ) ); -------------------------------------------------------------------------------- /wprp.themes.php: -------------------------------------------------------------------------------- 1 | $theme ) { 43 | 44 | // WordPress 3.4+ 45 | if ( is_object( $theme ) && is_a( $theme, 'WP_Theme' ) ) { 46 | 47 | /* @var $theme WP_Theme */ 48 | $new_version = isset( $current->response[$theme->get_stylesheet()] ) ? $current->response[$theme->get_stylesheet()]['new_version'] : null; 49 | 50 | $theme_array = array( 51 | 'Name' => $theme->get( 'Name' ), 52 | 'active' => $active == $theme->get( 'Name' ), 53 | 'Template' => $theme->get_template(), 54 | 'Stylesheet' => $theme->get_stylesheet(), 55 | 'Screenshot' => $theme->get_screenshot(), 56 | 'AuthorURI' => $theme->get( 'AuthorURI' ), 57 | 'Author' => $theme->get( 'Author' ), 58 | 'latest_version' => $new_version ? $new_version : $theme->get( 'Version' ), 59 | 'Version' => $theme->get( 'Version' ), 60 | 'ThemeURI' => $theme->get( 'ThemeURI' ) 61 | ); 62 | 63 | $themes[$key] = $theme_array; 64 | 65 | } else { 66 | 67 | $new_version = isset( $current->response[$theme['Stylesheet']] ) ? $current->response[$theme['Stylesheet']]['new_version'] : null; 68 | 69 | if ( $active == $theme['Name'] ) 70 | $themes[$key]['active'] = true; 71 | 72 | else 73 | $themes[$key]['active'] = false; 74 | 75 | if ( $new_version ) { 76 | 77 | $themes[$key]['latest_version'] = $new_version; 78 | $themes[$key]['latest_package'] = $current->response[$theme['Template']]['package']; 79 | 80 | } else { 81 | 82 | $themes[$key]['latest_version'] = $theme['Version']; 83 | 84 | } 85 | } 86 | } 87 | 88 | return $themes; 89 | } 90 | 91 | /** 92 | * Install a theme 93 | * 94 | * @param mixed $theme 95 | * @param array $args 96 | * @return array|bool 97 | */ 98 | function _wprp_install_theme( $theme, $args = array() ) { 99 | 100 | if ( defined( 'DISALLOW_FILE_MODS' ) && DISALLOW_FILE_MODS ) 101 | return new WP_Error( 'disallow-file-mods', __( "File modification is disabled with the DISALLOW_FILE_MODS constant.", 'wpremote' ) ); 102 | 103 | if ( wp_get_theme( $theme )->exists() ) 104 | return new WP_Error( 'theme-installed', __( 'Theme is already installed.' ) ); 105 | 106 | include_once ABSPATH . 'wp-admin/includes/admin.php'; 107 | include_once ABSPATH . 'wp-admin/includes/upgrade.php'; 108 | include_once ABSPATH . 'wp-includes/update.php'; 109 | require_once ( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' ); 110 | require_once WPRP_PLUGIN_PATH . 'inc/class-wprp-theme-upgrader-skin.php'; 111 | 112 | // Access the themes_api() helper function 113 | include_once ABSPATH . 'wp-admin/includes/theme-install.php'; 114 | $api_args = array( 115 | 'slug' => $theme, 116 | 'fields' => array( 'sections' => false ) 117 | ); 118 | $api = themes_api( 'theme_information', $api_args ); 119 | 120 | if ( is_wp_error( $api ) ) 121 | return $api; 122 | 123 | $skin = new WPRP_Theme_Upgrader_Skin(); 124 | $upgrader = new Theme_Upgrader( $skin ); 125 | 126 | // The best way to get a download link for a specific version :( 127 | // Fortunately, we can depend on a relatively consistent naming pattern 128 | if ( ! empty( $args['version'] ) && 'stable' != $args['version'] ) 129 | $api->download_link = str_replace( $api->version . '.zip', $args['version'] . '.zip', $api->download_link ); 130 | 131 | $result = $upgrader->install( $api->download_link ); 132 | if ( is_wp_error( $result ) ) 133 | return $result; 134 | else if ( ! $result ) 135 | return new WP_Error( 'unknown-install-error', __( 'Unknown error installing theme.', 'wpremote' ) ); 136 | 137 | return array( 'status' => 'success' ); 138 | } 139 | 140 | /** 141 | * Activate a theme 142 | * 143 | * @param mixed $theme 144 | * @return array 145 | */ 146 | function _wprp_activate_theme( $theme ) { 147 | 148 | if ( ! wp_get_theme( $theme )->exists() ) 149 | return new WP_Error( 'theme-not-installed', __( 'Theme is not installed.', 'wpremote' ) ); 150 | 151 | switch_theme( $theme ); 152 | return array( 'status' => 'success' ); 153 | } 154 | 155 | /** 156 | * Update a theme 157 | * 158 | * @param mixed $theme 159 | * @return array|WP_Error 160 | */ 161 | function _wprp_update_theme( $theme ) { 162 | 163 | if ( defined( 'DISALLOW_FILE_MODS' ) && DISALLOW_FILE_MODS ) 164 | return new WP_Error( 'disallow-file-mods', __( "File modification is disabled with the DISALLOW_FILE_MODS constant.", 'wpremote' ) ); 165 | 166 | include_once ( ABSPATH . 'wp-admin/includes/admin.php' ); 167 | require_once ( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' ); 168 | require_once WPRP_PLUGIN_PATH . 'inc/class-wprp-theme-upgrader-skin.php'; 169 | require_once WPRP_PLUGIN_PATH . 'inc/class-wprp-automatic-upgrader-skin.php'; 170 | 171 | // check for filesystem access 172 | if ( ! _wpr_check_filesystem_access() ) 173 | return new WP_Error( 'filesystem-not-writable', __( 'The filesystem is not writable with the supplied credentials', 'wpremote' ) ); 174 | 175 | $skin = new WPRP_Theme_Upgrader_Skin(); 176 | $upgrader = new Theme_Upgrader( $skin ); 177 | 178 | remove_action( 'upgrader_process_complete', array( 'Language_Pack_Upgrader', 'async_upgrade' ), 20 ); 179 | 180 | // Do the upgrade 181 | ob_start(); 182 | $result = $upgrader->upgrade( $theme ); 183 | $data = ob_get_contents(); 184 | ob_clean(); 185 | 186 | // Run the language upgrader 187 | try { 188 | ob_start(); 189 | $skin2 = new WPRP_Automatic_Upgrader_Skin(); 190 | $lang_upgrader = new Language_Pack_Upgrader($skin2); 191 | $result2 = $lang_upgrader->upgrade($upgrader); 192 | if ($data2 = ob_get_contents()) { 193 | ob_end_clean(); 194 | } 195 | } catch (\Exception $e) {} // Fail silently 196 | 197 | if ( ! empty( $skin->error ) ){ 198 | return new WP_Error( 'theme-upgrader-skin', $upgrader->strings[$skin->error] ); 199 | } elseif ( is_wp_error( $result ) ) { 200 | return $result; 201 | } elseif ( ( ! $result && ! is_null( $result ) ) || $data ) { 202 | return new WP_Error('theme-update', __('Unknown error updating theme.', 'wpremote')); 203 | } 204 | 205 | return array( 'status' => 'success' ); 206 | 207 | } 208 | 209 | /** 210 | * Delete a theme. 211 | * 212 | * @param mixed $theme 213 | * @return array 214 | */ 215 | function _wprp_delete_theme( $theme ) { 216 | global $wp_filesystem; 217 | 218 | if ( defined( 'DISALLOW_FILE_MODS' ) && DISALLOW_FILE_MODS ) 219 | return new WP_Error( 'disallow-file-mods', __( "File modification is disabled with the DISALLOW_FILE_MODS constant.", 'wpremote' ) ); 220 | 221 | if ( ! wp_get_theme( $theme )->exists() ) 222 | return new WP_Error( 'theme-missing', __( 'Theme is not installed.', 'wpremote' ) ); 223 | 224 | include_once ABSPATH . 'wp-admin/includes/admin.php'; 225 | include_once ABSPATH . 'wp-admin/includes/upgrade.php'; 226 | include_once ABSPATH . 'wp-includes/update.php'; 227 | 228 | if ( ! _wpr_check_filesystem_access() || ! WP_Filesystem() ) 229 | return new WP_Error( 'filesystem-not-writable', __( 'The filesystem is not writable with the supplied credentials', 'wpremote' ) ); 230 | 231 | $themes_dir = $wp_filesystem->wp_themes_dir(); 232 | if ( empty( $themes_dir ) ) 233 | return new WP_Error( 'theme-dir-missing', __( 'Unable to locate WordPress theme directory', 'wpremote' ) ); 234 | 235 | $themes_dir = trailingslashit( $themes_dir ); 236 | $theme_dir = trailingslashit( $themes_dir . $theme ); 237 | $deleted = $wp_filesystem->delete( $theme_dir, true ); 238 | 239 | if ( ! $deleted ) 240 | return new WP_Error( 'theme-delete', sprintf( __( 'Could not fully delete the theme: %s.', 'wpremote' ), $theme ) ); 241 | 242 | // Force refresh of theme update information 243 | delete_site_transient('update_themes'); 244 | 245 | return array( 'status' => 'success' ); 246 | } 247 | -------------------------------------------------------------------------------- /plugin.php: -------------------------------------------------------------------------------- 1 | WP Remote. 6 | Version: 2.8.4.3 7 | Author: maekit 8 | Author URI: https://maek.it/ 9 | */ 10 | 11 | /* Copyright 2017 maekit (email : hello@maek.it) 12 | 13 | This program is free software; you can redistribute it and/or modify 14 | it under the terms of the GNU General Public License as published by 15 | the Free Software Foundation; either version 2 of the License, or 16 | (at your option) any later version. 17 | 18 | This program is distributed in the hope that it will be useful, 19 | but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | GNU General Public License for more details. 22 | 23 | You should have received a copy of the GNU General Public License 24 | along with this program; if not, write to the Free Software 25 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 26 | */ 27 | 28 | define( 'WPRP_PLUGIN_SLUG', 'wpremote' ); 29 | define( 'WPRP_PLUGIN_BASE', plugin_basename(__FILE__) ); 30 | define( 'WPRP_PLUGIN_PATH', plugin_dir_path( __FILE__ ) ); 31 | 32 | if ( ! defined( 'WPR_URL' ) ) 33 | define( 'WPR_URL', 'https://wpremote.com/' ); 34 | 35 | if ( ! defined( 'WPR_API_URL' ) ) 36 | define( 'WPR_API_URL', 'https://wpremote.com/api/json/' ); 37 | 38 | if ( ! defined( 'WPR_LANG_DIR' ) ) 39 | define( 'WPR_LANG_DIR', apply_filters( 'wpr_filter_lang_dir', trailingslashit( WPRP_PLUGIN_PATH ) . trailingslashit( 'languages' ) ) ); 40 | 41 | // Don't activate on anything less than PHP 5.2.4 42 | if ( version_compare( phpversion(), '5.2.4', '<' ) ) { 43 | 44 | require_once( ABSPATH . 'wp-admin/includes/plugin.php' ); 45 | deactivate_plugins( WPRP_PLUGIN_SLUG . '/plugin.php' ); 46 | 47 | if ( isset( $_GET['action'] ) && ( $_GET['action'] == 'activate' || $_GET['action'] == 'error_scrape' ) ) 48 | die( __( 'WP Remote requires PHP version 5.2.4 or greater.', 'wpremote' ) ); 49 | 50 | } 51 | 52 | require_once( WPRP_PLUGIN_PATH . '/wprp.admin.php' ); 53 | require_once( WPRP_PLUGIN_PATH . '/wprp.compatability.php' ); 54 | 55 | if ( get_option( 'wprp_enable_log' ) ) 56 | require_once( WPRP_PLUGIN_PATH . '/wprp.log.php' ); 57 | 58 | // Backups require 3.1 59 | if ( version_compare( get_bloginfo( 'version' ), '3.1', '>=' ) ) { 60 | 61 | require_once( WPRP_PLUGIN_PATH . '/wprp.hm.backup.php' ); 62 | require_once( WPRP_PLUGIN_PATH . '/wprp.backups.php' ); 63 | 64 | } 65 | 66 | /** 67 | * Get a needed URL on the WP Remote site 68 | * 69 | * @param string $uri URI for the URL (optional) 70 | * @return string $url Fully-qualified URL to WP Remote 71 | */ 72 | function wprp_get_wpr_url( $uri = '' ) { 73 | 74 | if ( empty( $uri ) ) 75 | return WPR_URL; 76 | 77 | $url = rtrim( WPR_URL, '/' ); 78 | $uri = trim( $uri, '/' ); 79 | return $url . '/' . $uri . '/'; 80 | } 81 | 82 | /** 83 | * Catch the API calls and load the API 84 | * 85 | * @return null 86 | */ 87 | function wprp_catch_api_call() { 88 | 89 | if ( empty( $_POST['wpr_verify_key'] ) ) 90 | return; 91 | 92 | require_once( WPRP_PLUGIN_PATH . '/wprp.integration.php' ); 93 | require_once( WPRP_PLUGIN_PATH . '/wprp.plugins.php' ); 94 | require_once( WPRP_PLUGIN_PATH . '/wprp.themes.php' ); 95 | require_once( WPRP_PLUGIN_PATH . '/wprp.content.php' ); 96 | 97 | require_once( WPRP_PLUGIN_PATH . '/wprp.api.php' ); 98 | 99 | exit; 100 | 101 | } 102 | add_action( 'init', 'wprp_catch_api_call', 100 ); 103 | 104 | 105 | /** 106 | * Check for a bat signal from the mothership 107 | * 108 | * @since 2.7.0 109 | */ 110 | function wprp_check_bat_signal() { 111 | 112 | $bat_signal_key = 'wprp_bat_signal'; 113 | 114 | if ( false === get_transient( $bat_signal_key ) ) { 115 | 116 | $bat_signal_url = trailingslashit( WPR_URL ) . 'bat-signal/'; 117 | $response = wp_remote_get( $bat_signal_url ); 118 | $response_body = wp_remote_retrieve_body( $response ); 119 | if ( 'destroy the evidence!' == trim( $response_body ) ) 120 | delete_option( 'wpr_api_key' ); 121 | 122 | // One request per day 123 | set_transient( $bat_signal_key, 'the coast is clear', 60 * 60 * 24 ); 124 | } 125 | 126 | } 127 | add_action( 'init', 'wprp_check_bat_signal' ); 128 | 129 | /** 130 | * Get the stored WPR API key 131 | * 132 | * @return mixed 133 | */ 134 | function wprp_get_api_keys() { 135 | $keys = apply_filters( 'wpr_api_keys', get_option( 'wpr_api_key' ) ); 136 | if ( ! empty( $keys ) ) 137 | return (array)$keys; 138 | else 139 | return array(); 140 | } 141 | 142 | function wprp_plugin_update_check() { 143 | 144 | $plugin_data = get_plugin_data( __FILE__ ); 145 | 146 | // define the plugin version 147 | define( 'WPRP_VERSION', $plugin_data['Version'] ); 148 | 149 | // Fire the update action 150 | if ( WPRP_VERSION !== get_option( 'wprp_plugin_version' ) ) 151 | wprp_update(); 152 | 153 | } 154 | add_action( 'admin_init', 'wprp_plugin_update_check' ); 155 | 156 | /** 157 | * Run any update code and update the current version in the db 158 | * 159 | * @access public 160 | * @return void 161 | */ 162 | function wprp_update() { 163 | 164 | /** 165 | * Remove the old _wpremote_backups directory 166 | */ 167 | $uploads_dir = wp_upload_dir(); 168 | 169 | $old_wpremote_dir = trailingslashit( $uploads_dir['basedir'] ) . '_wpremote_backups'; 170 | 171 | if ( file_exists( $old_wpremote_dir ) ) 172 | WPRP_Backups::rmdir_recursive( $old_wpremote_dir ); 173 | 174 | // If BackUpWordPress isn't installed then lets just delete the whole backups directory 175 | if ( ! defined( 'HMBKP_PLUGIN_PATH' ) && $path = get_option( 'hmbkp_path' ) ) { 176 | 177 | WPRP_Backups::rmdir_recursive( $path ); 178 | 179 | delete_option( 'hmbkp_path' ); 180 | delete_option( 'hmbkp_default_path' ); 181 | delete_option( 'hmbkp_plugin_version' ); 182 | 183 | } 184 | 185 | // Update the version stored in the db 186 | if ( get_option( 'wprp_plugin_version' ) !== WPRP_VERSION ) 187 | update_option( 'wprp_plugin_version', WPRP_VERSION ); 188 | 189 | } 190 | 191 | function _wprp_upgrade_core() { 192 | 193 | if ( defined( 'DISALLOW_FILE_MODS' ) && DISALLOW_FILE_MODS ) 194 | return new WP_Error( 'disallow-file-mods', __( "File modification is disabled with the DISALLOW_FILE_MODS constant.", 'wpremote' ) ); 195 | 196 | include_once ( ABSPATH . 'wp-admin/includes/admin.php' ); 197 | include_once ( ABSPATH . 'wp-admin/includes/upgrade.php' ); 198 | include_once ( ABSPATH . 'wp-includes/update.php' ); 199 | require_once ( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' ); 200 | require_once WPRP_PLUGIN_PATH . 'inc/class-wprp-core-upgrader-skin.php'; 201 | 202 | // check for filesystem access 203 | if ( ! _wpr_check_filesystem_access() ) 204 | return new WP_Error( 'filesystem-not-writable', __( 'The filesystem is not writable with the supplied credentials', 'wpremote' ) ); 205 | 206 | // force refresh 207 | wp_version_check(); 208 | 209 | $updates = get_core_updates(); 210 | 211 | if ( is_wp_error( $updates ) || ! $updates ) 212 | return new WP_Error( 'no-update-available' ); 213 | 214 | $update = reset( $updates ); 215 | 216 | if ( ! $update ) 217 | return new WP_Error( 'no-update-available' ); 218 | 219 | $skin = new WPRP_Core_Upgrader_Skin(); 220 | 221 | $upgrader = new Core_Upgrader( $skin ); 222 | $result = $upgrader->upgrade($update); 223 | 224 | if ( is_wp_error( $result ) ) 225 | return $result; 226 | 227 | global $wp_current_db_version, $wp_db_version; 228 | 229 | // we have to include version.php so $wp_db_version 230 | // will take the version of the updated version of wordpress 231 | require( ABSPATH . WPINC . '/version.php' ); 232 | 233 | wp_upgrade(); 234 | 235 | return true; 236 | } 237 | 238 | function _wpr_check_filesystem_access() { 239 | 240 | ob_start(); 241 | $success = request_filesystem_credentials( '' ); 242 | ob_end_clean(); 243 | 244 | return (bool) $success; 245 | } 246 | 247 | function _wpr_set_filesystem_credentials( $credentials ) { 248 | 249 | if ( empty( $_POST['filesystem_details'] ) ) 250 | return $credentials; 251 | 252 | $_credentials = array( 253 | 'username' => $_POST['filesystem_details']['credentials']['username'], 254 | 'password' => $_POST['filesystem_details']['credentials']['password'], 255 | 'hostname' => $_POST['filesystem_details']['credentials']['hostname'], 256 | 'connection_type' => $_POST['filesystem_details']['method'] 257 | ); 258 | 259 | // check whether the credentials can be used 260 | if ( ! WP_Filesystem( $_credentials ) ) { 261 | return $credentials; 262 | } 263 | 264 | return $_credentials; 265 | } 266 | add_filter( 'request_filesystem_credentials', '_wpr_set_filesystem_credentials' ); 267 | 268 | /** 269 | * 270 | */ 271 | function wprp_translations_init() { 272 | 273 | if ( is_admin() ) { 274 | 275 | /** Set unique textdomain string */ 276 | $wprp_textdomain = 'wpremote'; 277 | 278 | /** The 'plugin_locale' filter is also used by default in load_plugin_textdomain() */ 279 | $plugin_locale = apply_filters( 'plugin_locale', get_locale(), $wprp_textdomain ); 280 | 281 | /** Set filter for WordPress languages directory */ 282 | $wprp_wp_lang_dir = apply_filters( 283 | 'wprp_filter_wp_lang_dir', 284 | trailingslashit( WP_LANG_DIR ) . trailingslashit( 'wp-remote' ) . $wprp_textdomain . '-' . $plugin_locale . '.mo' 285 | ); 286 | 287 | /** Translations: First, look in WordPress' "languages" folder = custom & update-secure! */ 288 | load_textdomain( $wprp_textdomain, $wprp_wp_lang_dir ); 289 | 290 | /** Translations: Secondly, look in plugin's "languages" folder = default */ 291 | load_plugin_textdomain( $wprp_textdomain, FALSE, WPR_LANG_DIR ); 292 | } 293 | } 294 | add_action( 'plugins_loaded', 'wprp_translations_init' ); 295 | 296 | /** 297 | * Format a WP User object into a better 298 | * object for the API 299 | */ 300 | function wprp_format_user_obj( $user_obj ) { 301 | $new_user_obj = new stdClass; 302 | 303 | foreach( $user_obj->data as $key => $value ) { 304 | $new_user_obj->$key = $value; 305 | } 306 | 307 | $new_user_obj->roles = $user_obj->roles; 308 | $new_user_obj->caps = $user_obj->caps; 309 | 310 | return $new_user_obj; 311 | } 312 | 313 | // == CLI == // 314 | if ( defined( 'WP_CLI' ) && WP_CLI ) { 315 | require_once 'cli/wprp.cli.php'; 316 | } -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | === The WP Remote WordPress Plugin === 2 | Contributors: jeramynirodha, bmett, humanmade, willmot, joehoyle, danielbachhuber, mattheu, pauldewouters, cuvelier, tcrsavage 3 | Tags: wpremote, remote administration, multiple wordpress 4 | Requires at least: 3.0 5 | Tested up to: 4.9 6 | Stable tag: 2.8.4.3 7 | 8 | WP Remote is a free web app that enables you to easily manage all of your WordPress powered sites from one place. 9 | 10 | == Description == 11 | 12 | The WP Remote WordPress Plugin works with [WP Remote](https://wpremote.com/) to enable you to remotely manage and update all your WordPress sites. 13 | 14 | = Features = 15 | 16 | * Free to monitor and update an unlimited number of sites. 17 | * Track and update all of your WordPress sites from one place. 18 | * Track and update all of your WordPress plugins and themes from one place. 19 | * Schedule automatic backups to AWS, SFTP, FTP. 20 | * Perform manual backups of your WordPress database and files. 21 | * Download previous backups from the one place. 22 | * Install and activate plugins and themes from the one place. 23 | * Early beta access to [maekit](https://maek.it/) web design business platform. 24 | 25 | = Support = 26 | 27 | You can email us at support@wpremote.com for support. 28 | 29 | == Installation == 30 | 31 | 1. Install The WP Remote WordPress Plugin either via the WordPress.org plugin directory, or by uploading the files to your server. 32 | 2. Activate the plugin. 33 | 3. Sign up for an account at wpremote.com and add your site. 34 | 35 | == Frequently Asked Questions == 36 | 37 | ** I've forgotten my password ** 38 | Use the “I’ve forgotten my password” link on the log-in screen to generate an email with a link to reset your password. 39 | 40 | https://wpremote.com/login/lost-password/ 41 | 42 | ** How do I fix the “Does not appear to be a valid URL” message? ** 43 | 44 | 1. If the domain name has been typed incorrectly: 45 | The easiest way to ensure you have the correct domain name is to open your site in a different browser window and then copy and paste the site address. 46 | 47 | 2. If you have made recent changes to your DNS/Nameservers records: 48 | If this is the case then just give it a little more time and try again later. 49 | 50 | **What if I want to back up my site to another destination?** 51 | 52 | You can also store your backups on your own Amazon S3 or you can upload backups to your own server via FTP or SFTP. 53 | 54 | **How do I restore my site from a backup?** 55 | 56 | WP Remote does not provide an automated way to restore your site. We recommend downloading a copy of your backup, unzipping it and then uploading to your site's server via FTP/SSH. Database importing can be done via your PHPMyAdmin interface or a similar tool - Your database backup can be found in the root folder of your downloaded backup zip. 57 | 58 | **Further Support & Feedback** 59 | 60 | General support questions should be posted in the WordPress support forums. 61 | 62 | You can email us at support@wpremote.com for support. 63 | 64 | == Screenshots == 65 | 66 | 1. The WP Remote dashboard at wpremote.com 67 | 2. See all of the plugins and themes needing update across all Sites in one view. 68 | 3. Download nightly Automatic Backups. 69 | 70 | == Changelog == 71 | 72 | #### 2.8.4.3 (11 January 2019) 73 | 74 | * Backport bug fix for theme updates from v3.0.a 75 | * Plugins will now be re-installed if they vanish and add in user_abort prevention. 76 | 77 | #### 2.8.4.2 (9 January 2019) 78 | 79 | * Backport WPEngine bug fix from v3.0.a 80 | 81 | #### 2.8.4.1 (3 December 2017) 82 | 83 | * Correct handling of up_to_date error 84 | 85 | #### 2.8.4 (3 December 2017) 86 | 87 | * Modify error message response in certain situations 88 | 89 | #### 2.8.3 (21 November 2017) 90 | 91 | * Add endpoint to validate plugin update 92 | * Improved error handling 93 | * Fix 'Clear Api' redirect 94 | 95 | #### 2.8.2 (25 October 2017) 96 | 97 | * Change settings page function name for compatibility 98 | * Allow the WP Remote API key to be updated from CLI 99 | 100 | #### 2.8.1 (10 October 2017) 101 | 102 | * Add link to clear API key from the plugin settings page. 103 | * Prevent WP Remote from clearing the API key on deactivation 104 | * Clear API key on uninstall 105 | 106 | #### 2.8.0.1 (31 August 2017) 107 | 108 | * Bug fix for PHP < 5.4 109 | 110 | #### 2.8.0 (30 August 2017) 111 | 112 | * Modify plugin activation and return plugin active status to WP Remote 113 | 114 | #### 2.7.9.2 (25 August 2017) 115 | 116 | * Bug fix for php 5.4 and lower 117 | 118 | #### 2.7.9.1 (25 August 2017) 119 | 120 | * Add Fallback method for when current user isn't found 121 | 122 | #### 2.7.9 (22 August 2017) 123 | 124 | * Query DB to find an admin user to run updates 125 | 126 | #### 2.7.8 (20 July 2017) 127 | 128 | * Replaced mysql class and functions with mysqli 129 | 130 | #### 2.7.7 (20 April 2017) 131 | 132 | * Fixed fatal error with backup location 133 | 134 | #### 2.7.6 (18 Sept 2014) 135 | 136 | * Fixed issue with plugins not being reactivated when updated on an MU WordPress install 137 | * Fixed issue with child themes reporting an available update whenever the parent theme has an available update 138 | 139 | #### 2.7.5 (10 Sept 2014) 140 | 141 | * Fixed WordPress 4.0 issues with json_encode of a WP_Error object which would result in malformed responses from the WP_Remote WordPress plugin 142 | * Added FAQ to readme 143 | * Updated incompatible plugins list 144 | 145 | #### 2.7.3 (12 May 2014) 146 | 147 | * Added the ability to return basic content information for the site - post count, user count, plugin count etc. 148 | * Updated contribution guidelines 149 | 150 | #### 2.7.2 (22 January 2014) 151 | 152 | * Misc improvements to the accuracy of the backup restart mechanism. 153 | * Inline styles to insure the API key prompt always appears, even if a theme or plugin may hide admin notices. 154 | 155 | #### 2.7.1 (23 December 2013) 156 | 157 | * Bug fix: Restore plugin and theme installation mechanism. 158 | * Bug fix: On some hosts where `getmypid()` wasn't permitted, the backup process would be prematurely reported as killed. 159 | 160 | #### 2.7.0 (19 November 2013) 161 | 162 | * Improved durability of backups where the backup process can take more than 90 seconds. 163 | * New API support for posts, comments, and fixed support for users (oops). 164 | * Reporting and update integration with premium plugins that support ManageWP's API implementation. 165 | * Plugin, theme, and core updates now respect the `DISALLOW_FILE_MODS` constant. 166 | 167 | #### 2.6.7 (27 October 2013) 168 | 169 | * API improvement: specify database- and file-only backups 170 | * Bug fix: Make the backup download URL accessible on Apache servers again. The protective .htaccess was being generated with the wrong key. 171 | 172 | #### 2.6.6 (23 October 2013) 173 | 174 | * Bug fix: Due to some files moving around, WP Remote wasn't able to properly update the current version of the plugin. 175 | 176 | #### 2.6.5 (23 October 2013) 177 | 178 | * Incorporated a more reliable plugin re-activation process after update. 179 | * Bug fix: Properly delete backup folders for failed backups. Users may want to look inside of `/wp-content/` for any folders named as `*-backups`. If they were created by WP Remote, they can be safely deleted. 180 | * Bug fix: Log the proper fields in history when a new user is created. 181 | 182 | #### 2.6.4 (2 October 2013) 183 | 184 | * Misc API improvements for Premium. 185 | * Bug fix: Disable all premium plugin and theme updates. Causing fatals too often. 186 | * Bug fix: Restore FTP-based core, theme, and plugin updates by properly accessing the passed credentials. 187 | 188 | #### 2.6.3 (10 September 2013) 189 | 190 | * Bug fix: Disabled updating BackupBuddy through WP Remote for BackupBuddy v4.1.1 and greater. BackupBuddy changed its custom update mechanism (as it's a premium plugin), which caused the WP Remote plugin not to function properly. 191 | 192 | #### 2.6.2 (2 September 2013) 193 | 194 | * Bug fix: Reactivating plugin after plugin upgrade. 195 | 196 | #### 2.6.1 (26 August 2013) 197 | 198 | * Add multiple API keys to your WP Remote plugin with a `wpr_api_keys` filter if you'd like to use more than WP Remote account with the site. 199 | * Plugin now supports localization. Please feel free to [submit your translation](http://translate.hmn.md/projects). 200 | * Update `HM Backup` to v2.3 201 | * Bug fix: Properly handle timestamp values in database backups. 202 | * Bug fix: Use super randomized backup directories. 203 | 204 | #### 2.6 205 | 206 | * Change to using better hmac style authentication 207 | * Fix error for sites running =< WordPress 3.1 208 | 209 | #### 2.5 210 | 211 | * Remove BackUpWordPress, backups are now handled by the `HM Backup` class. 212 | * BackUpWordPress can now be used alongside WP Remote without issues. 213 | * Exclude `.git` and `.svn` folders from backups automatically. 214 | 215 | #### 2.4.12 & 2.4.13 216 | 217 | * Upgrade bundled BackUpWordPress to 2.1.3. 218 | * Fix an issue with Download Site on Apache servers. 219 | * Set the correct location for the BackUpWordPress language files. 220 | 221 | #### 2.4.10 + 2.4.11 222 | 223 | * Plugin release shenaningans. 224 | 225 | #### 2.4.9 226 | 227 | * Pull in latest BackUpWordPress which fixes a possible Fatal error caused by `url_shorten` being called outside the admin. 228 | 229 | #### 2.4.8 230 | 231 | * Pull in latest BackUpWordPress which fixes a possible Fatal error caused by misc.php being included to early. 232 | 233 | #### 2.4.7 234 | 235 | * Update to BackUpWordPress 2.1 236 | * Fix an issue that could cause backups to be run when they shouldn't have. 237 | * Only hide the backups menu item if the site doesn't have any non wpremote schedules. 238 | * Hide all BackUpWordPress admin notices. 239 | * Fix the button styles for the save API Key button in WordPress 3.5 240 | * Fix a possible warning in the WP_Filesystem integration, props @tillkruess (github). 241 | * Support for updating the Pagelines premium theme, props @tillkruess (github) 242 | 243 | #### 2.4.6 244 | 245 | * Support for updating the BackupBuddy premium plugin, props @tillkruess (github) 246 | 247 | #### 2.4.1 - 2.4.5 248 | 249 | * Minor bug fixes 250 | 251 | #### 2.4 252 | 253 | * Backups are now powered by BackUpWordPress. 254 | * The BackUpWordPress Plugin can no longer be run alongside WP Remote. 255 | * Show a message if a security plugin is active which could affect WP Remote. 256 | * Emphasise that you can deactivate the plugin to clear your API key. 257 | 258 | #### 2.3.1 259 | 260 | * PHP 5.2.4 compat. 261 | 262 | #### 2.3 263 | 264 | * WP_Filesystem support for servers which don't allow PHP direct filesystem access. 265 | * Support for monitoring and updating Gravity Forms. 266 | 267 | #### 2.2.5 268 | 269 | * Implemented API call for Core updates 270 | 271 | #### 2.2.4 272 | 273 | * Fixed excludes for backups directories 274 | * Started on remote core upgrades 275 | * Fix memory limit in WP 3.1 276 | 277 | #### 2.2.3 278 | 279 | * Use WPR_HM_Backup instead of HM_Backup (fixes compatibilty with backupwordpress) 280 | 281 | #### 2.2 282 | 283 | * Start keeping a changelog of plugin changes 284 | * Pass home_url, site_url and admin_url to WP Remote instead of guessing at them, fixes issues with the urls being wrong for non-standard WordPress installs 285 | * Better error message when you have the wrong API key entered. 286 | 287 | ## Contribution guidelines ## 288 | 289 | see https://github.com/MyWorkAus/WP-Remote-WordPress-Plugin/blob/master/CONTRIBUTING.md 290 | -------------------------------------------------------------------------------- /wprp.plugins.php: -------------------------------------------------------------------------------- 1 | $plugin ) { 43 | 44 | if ( is_plugin_active( $plugin_file ) ) 45 | $plugins[$plugin_file]['active'] = true; 46 | else 47 | $plugins[$plugin_file]['active'] = false; 48 | 49 | $manage_wp_plugin_update = false; 50 | foreach( $manage_wp_updates as $manage_wp_update ) { 51 | 52 | if ( ! empty( $manage_wp_update['Name'] ) && $plugin['Name'] == $manage_wp_update['Name'] ) 53 | $manage_wp_plugin_update = $manage_wp_update; 54 | 55 | } 56 | 57 | if ( $manage_wp_plugin_update ) { 58 | 59 | $plugins[$plugin_file]['latest_version'] = $manage_wp_plugin_update['new_version']; 60 | 61 | } else if ( isset( $current->response[$plugin_file] ) ) { 62 | 63 | $plugins[$plugin_file]['latest_version'] = $current->response[$plugin_file]->new_version; 64 | $plugins[$plugin_file]['latest_package'] = $current->response[$plugin_file]->package; 65 | $plugins[$plugin_file]['slug'] = $current->response[$plugin_file]->slug; 66 | 67 | } else { 68 | 69 | $plugins[$plugin_file]['latest_version'] = $plugin['Version']; 70 | 71 | } 72 | 73 | } 74 | 75 | return $plugins; 76 | } 77 | 78 | /** 79 | * Wrap the Update Plugin function with a failsafe fallback 80 | * 81 | * @param $plugin_file 82 | * @param $args 83 | * @return array|bool|WP_Error 84 | */ 85 | function _wprp_update_plugin_wrap( $plugin_file, $args ) 86 | { 87 | @ignore_user_abort( true ); 88 | 89 | $response = false; 90 | $is_active = is_plugin_active( $plugin_file ); 91 | $is_active_network = is_plugin_active_for_network( $plugin_file ); 92 | 93 | try { 94 | $response = _wprp_update_plugin($plugin_file, $args); 95 | } catch (\Exception $exception) {} 96 | 97 | // FALLBACK for when the plugin is deleted. Just re-install. 98 | if ( ! file_exists(WP_PLUGIN_DIR . '/' . $plugin_file) ){ 99 | $plugin_slug = rtrim(plugin_dir_path($plugin_file), '/'); 100 | 101 | _wprp_install_plugin($plugin_slug); 102 | 103 | if ($is_active) { 104 | activate_plugin($plugin_file, '', $is_active_network, true); 105 | } 106 | 107 | $response = new WP_Error('rollback','Plugin update failed. Plugin has been re-installed.'); 108 | } 109 | 110 | if ($response === false) { 111 | return new WP_Error('update-failed', 'No message was set.'); 112 | } 113 | 114 | return $response; 115 | } 116 | 117 | /** 118 | * Update a plugin 119 | * 120 | * @access private 121 | * @param $plugin_file 122 | * @param $args 123 | * @return array|WP_Error 124 | */ 125 | function _wprp_update_plugin( $plugin_file, $args ) { 126 | global $wprp_zip_update; 127 | 128 | if ( defined( 'DISALLOW_FILE_MODS' ) && DISALLOW_FILE_MODS ) 129 | return new WP_Error( 'disallow-file-mods', __( "File modification is disabled with the DISALLOW_FILE_MODS constant.", 'wpremote' ) ); 130 | 131 | include_once ( ABSPATH . 'wp-admin/includes/admin.php' ); 132 | require_once ( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' ); 133 | require_once WPRP_PLUGIN_PATH . 'inc/class-wprp-plugin-upgrader-skin.php'; 134 | require_once WPRP_PLUGIN_PATH . 'inc/class-wprp-automatic-upgrader-skin.php'; 135 | 136 | // check for filesystem access 137 | if ( ! _wpr_check_filesystem_access() ) 138 | return new WP_Error( 'filesystem-not-writable', __( 'The filesystem is not writable with the supplied credentials', 'wpremote' ) ); 139 | 140 | $is_active = is_plugin_active( $plugin_file ); 141 | $is_active_network = is_plugin_active_for_network( $plugin_file ); 142 | 143 | foreach( get_plugins() as $path => $maybe_plugin ) { 144 | 145 | if ( $path == $plugin_file ) { 146 | $plugin = $maybe_plugin; 147 | break; 148 | } 149 | 150 | } 151 | 152 | // Permit specifying a zip URL to update the plugin with 153 | if ( ! empty( $args['zip_url'] ) ) { 154 | 155 | $zip_url = $args['zip_url']; 156 | 157 | } else { 158 | 159 | // Check to see if this is a premium plugin that supports the ManageWP implementation 160 | $manage_wp_updates = apply_filters( 'mwp_premium_perform_update', array() ); 161 | $manage_wp_plugin_update = false; 162 | foreach( $manage_wp_updates as $manage_wp_update ) { 163 | 164 | if ( ! empty( $manage_wp_update['Name'] ) 165 | && $plugin['Name'] == $manage_wp_update['Name'] 166 | && ! empty( $manage_wp_update['url'] ) ) { 167 | $zip_url = $manage_wp_update['url']; 168 | break; 169 | } 170 | 171 | } 172 | 173 | } 174 | 175 | $skin = new WPRP_Plugin_Upgrader_Skin(); 176 | $upgrader = new Plugin_Upgrader( $skin ); 177 | 178 | // Fake out the plugin upgrader with our package url 179 | if ( ! empty( $zip_url ) ) { 180 | $wprp_zip_update = array( 181 | 'plugin_file' => $plugin_file, 182 | 'package' => $zip_url, 183 | ); 184 | add_filter( 'pre_site_transient_update_plugins', '_wprp_forcably_filter_update_plugins' ); 185 | } else { 186 | wp_update_plugins(); 187 | } 188 | 189 | // Remove the Language Upgrader 190 | remove_action('upgrader_process_complete', array('Language_Pack_Upgrader', 'async_upgrade'), 20); 191 | 192 | // Do the upgrade 193 | ob_start(); 194 | $result = $upgrader->upgrade($plugin_file); 195 | if ($data = ob_get_contents()) { 196 | ob_end_clean(); 197 | } 198 | 199 | // Run the language upgrader 200 | ob_start(); 201 | $skin2 = new WPRP_Automatic_Upgrader_Skin(); 202 | $lang_upgrader = new Language_Pack_Upgrader($skin2); 203 | $result2 = $lang_upgrader->upgrade($upgrader); 204 | if ($data2 = ob_get_contents()) { 205 | ob_end_clean(); 206 | } 207 | 208 | if ( isset($manage_wp_plugin_update) && $manage_wp_plugin_update ) 209 | remove_filter( 'pre_site_transient_update_plugins', '_wprp_forcably_filter_update_plugins' ); 210 | 211 | // If the plugin was activited, we have to re-activate it 212 | // but if activate_plugin() fatals, then we'll just have to return 500 213 | if ( $is_active ) 214 | activate_plugin( $plugin_file, '', $is_active_network, true ); 215 | 216 | if ( ! empty( $skin->error ) ) { 217 | if (is_wp_error($skin->error)) { 218 | return $skin->error; 219 | } 220 | if ($skin->error == 'up_to_date') { 221 | return new WP_Error('up_to_date', __('Plugin already up to date.', 'wpremote')); 222 | } 223 | $msg = __('Unknown error updating plugin.', 'wpremote'); 224 | if (is_string($skin->error)) { 225 | $msg = $skin->error; 226 | } 227 | return new WP_Error('plugin-upgrader-skin', $msg); 228 | } else if ( is_wp_error( $result ) ) { 229 | return $result; 230 | } else if ( ( ! $result && ! is_null( $result ) ) || $data ) { 231 | return new WP_Error('plugin-update', __('Unknown error updating plugin.', 'wpremote')); 232 | } 233 | 234 | $active_status = array( 235 | 'was_active' => $is_active, 236 | 'was_active_network' => $is_active_network, 237 | 'is_active' => is_plugin_active( $plugin_file ), 238 | 'is_active_network' => is_plugin_active_for_network( $plugin_file ), 239 | ); 240 | 241 | return array( 'status' => 'success', 'active_status' => $active_status ); 242 | } 243 | 244 | /** 245 | * Validate Plugin Update 246 | * 247 | * @param $plugin_file 248 | * @return array|WP_Error 249 | */ 250 | function _wprp_validate($plugin_file) 251 | { 252 | $plugin_status = false; 253 | foreach( get_plugins() as $path => $maybe_plugin ) { 254 | if ( $path == $plugin_file ) { 255 | $plugin_status = true; 256 | break; 257 | } 258 | } 259 | if (!$plugin_status) { 260 | return new WP_Error('plugin-missing', __('Plugin has gone missing.', 'wpremote')); 261 | } 262 | return array( 263 | 'status' => 'success', 264 | 'plugin_status' => is_plugin_active( $plugin_file ) 265 | ); 266 | } 267 | 268 | /** 269 | * Filter `update_plugins` to produce a response it will understand 270 | * so we can have the Upgrader skin handle the update 271 | */ 272 | function _wprp_forcably_filter_update_plugins() { 273 | global $wprp_zip_update; 274 | 275 | $current = new stdClass; 276 | $current->response = array(); 277 | 278 | $plugin_file = $wprp_zip_update['plugin_file']; 279 | $current->response[$plugin_file] = new stdClass; 280 | $current->response[$plugin_file]->package = $wprp_zip_update['package']; 281 | 282 | return $current; 283 | } 284 | 285 | /** 286 | * Install a plugin on this site 287 | */ 288 | function _wprp_install_plugin( $plugin, $args = array() ) { 289 | 290 | if ( defined( 'DISALLOW_FILE_MODS' ) && DISALLOW_FILE_MODS ) 291 | return new WP_Error( 'disallow-file-mods', __( "File modification is disabled with the DISALLOW_FILE_MODS constant.", 'wpremote' ) ); 292 | 293 | include_once ABSPATH . 'wp-admin/includes/admin.php'; 294 | include_once ABSPATH . 'wp-admin/includes/upgrade.php'; 295 | include_once ABSPATH . 'wp-includes/update.php'; 296 | require_once ( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' ); 297 | require_once WPRP_PLUGIN_PATH . 'inc/class-wprp-plugin-upgrader-skin.php'; 298 | 299 | // Access the plugins_api() helper function 300 | include_once ABSPATH . 'wp-admin/includes/plugin-install.php'; 301 | $api_args = array( 302 | 'slug' => $plugin, 303 | 'fields' => array( 'sections' => false ) 304 | ); 305 | $api = plugins_api( 'plugin_information', $api_args ); 306 | 307 | if ( is_wp_error( $api ) ) 308 | return $api; 309 | 310 | $skin = new WPRP_Plugin_Upgrader_Skin(); 311 | $upgrader = new Plugin_Upgrader( $skin ); 312 | 313 | // The best way to get a download link for a specific version :( 314 | // Fortunately, we can depend on a relatively consistent naming pattern 315 | if ( ! empty( $args['version'] ) && 'stable' != $args['version'] ) 316 | $api->download_link = str_replace( $api->version . '.zip', $args['version'] . '.zip', $api->download_link ); 317 | 318 | $result = $upgrader->install( $api->download_link ); 319 | if ( is_wp_error( $result ) ) 320 | return $result; 321 | else if ( ! $result ) 322 | return new WP_Error( 'plugin-install', __( 'Unknown error installing plugin.', 'wpremote' ) ); 323 | 324 | return array( 'status' => 'success' ); 325 | } 326 | 327 | function _wprp_activate_plugin( $plugin ) { 328 | 329 | include_once ABSPATH . 'wp-admin/includes/plugin.php'; 330 | 331 | $result = activate_plugin( $plugin ); 332 | 333 | if ( is_wp_error( $result ) ) 334 | return $result; 335 | 336 | return array( 'status' => 'success' ); 337 | } 338 | 339 | /** 340 | * Deactivate a plugin on this site. 341 | */ 342 | function _wprp_deactivate_plugin( $plugin ) { 343 | 344 | include_once ABSPATH . 'wp-admin/includes/plugin.php'; 345 | 346 | if ( is_plugin_active( $plugin ) ) 347 | deactivate_plugins( $plugin ); 348 | 349 | return array( 'status' => 'success' ); 350 | } 351 | 352 | /** 353 | * Uninstall a plugin on this site. 354 | */ 355 | function _wprp_uninstall_plugin( $plugin ) { 356 | global $wp_filesystem; 357 | 358 | if ( defined( 'DISALLOW_FILE_MODS' ) && DISALLOW_FILE_MODS ) 359 | return new WP_Error( 'disallow-file-mods', __( "File modification is disabled with the DISALLOW_FILE_MODS constant.", 'wpremote' ) ); 360 | 361 | include_once ABSPATH . 'wp-admin/includes/admin.php'; 362 | include_once ABSPATH . 'wp-admin/includes/upgrade.php'; 363 | include_once ABSPATH . 'wp-includes/update.php'; 364 | 365 | if ( ! _wpr_check_filesystem_access() || ! WP_Filesystem() ) 366 | return new WP_Error( 'filesystem-not-writable', __( 'The filesystem is not writable with the supplied credentials', 'wpremote' ) ); 367 | 368 | $plugins_dir = $wp_filesystem->wp_plugins_dir(); 369 | if ( empty( $plugins_dir ) ) 370 | return new WP_Error( 'missing-plugin-dir', __( 'Unable to locate WordPress Plugin directory.' , 'wpremote' ) ); 371 | 372 | $plugins_dir = trailingslashit( $plugins_dir ); 373 | 374 | if ( is_uninstallable_plugin( $plugin ) ) 375 | uninstall_plugin( $plugin ); 376 | 377 | $this_plugin_dir = trailingslashit( dirname( $plugins_dir . $plugin ) ); 378 | // If plugin is in its own directory, recursively delete the directory. 379 | if ( strpos( $plugin, '/' ) && $this_plugin_dir != $plugins_dir ) //base check on if plugin includes directory separator AND that it's not the root plugin folder 380 | $deleted = $wp_filesystem->delete( $this_plugin_dir, true ); 381 | else 382 | $deleted = $wp_filesystem->delete( $plugins_dir . $plugin ); 383 | 384 | if ( $deleted ) { 385 | if ( $current = get_site_transient('update_plugins') ) { 386 | unset( $current->response[$plugin] ); 387 | set_site_transient('update_plugins', $current); 388 | } 389 | return array( 'status' => 'success' ); 390 | } else { 391 | return new WP_Error( 'plugin-uninstall', __( 'Plugin uninstalled, but not deleted.', 'wpremote' ) ); 392 | } 393 | 394 | } 395 | -------------------------------------------------------------------------------- /wprp.api.php: -------------------------------------------------------------------------------- 1 | time() + 360 || (int) $_POST['timestamp'] < time() - 360 ) { 29 | echo json_encode( 'bad-timstamp' ); 30 | exit; 31 | } 32 | 33 | self::$actions = $_POST['actions']; 34 | self::$args = $_POST; 35 | 36 | 37 | } else { 38 | exit; 39 | } 40 | 41 | return true; 42 | 43 | } 44 | 45 | static function generate_hashes( $vars ) { 46 | 47 | $api_key = wprp_get_api_keys(); 48 | if ( ! $api_key ) 49 | return array(); 50 | 51 | $hashes = array(); 52 | foreach( $api_key as $key ) { 53 | $hashes[] = hash_hmac( 'sha256', serialize( $vars ), $key ); 54 | } 55 | return $hashes; 56 | 57 | } 58 | 59 | static function get_actions() { 60 | return self::$actions; 61 | } 62 | 63 | static function get_args() { 64 | return self::$args; 65 | } 66 | 67 | static function get_arg( $arg ) { 68 | return ( isset( self::$args[$arg] ) ) ? self::$args[$arg] : null; 69 | } 70 | } 71 | 72 | WPR_API_Request::verify_request(); 73 | 74 | // disable logging for anythign done in API requests 75 | if ( class_exists( 'WPRP_Log' ) ) 76 | WPRP_Log::get_instance()->disable_logging(); 77 | 78 | // Disable error_reporting so they don't break the json request 79 | if ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) 80 | error_reporting( 0 ); 81 | 82 | // Temp hack so our requests to verify file size are signed. 83 | global $wprp_noauth_nonce; 84 | $wprp_noauth_nonce = wp_create_nonce( 'wprp_calculate_backup_size' ); 85 | 86 | // Log in as admin 87 | $users_query = new WP_User_Query( array( 88 | 'role' => 'administrator', 89 | 'orderby' => 'ID' 90 | ) ); 91 | wp_set_current_user(1); 92 | if ($users_query->get_total()) { 93 | foreach ($users_query->get_results() as $user) { 94 | if (!$user) { 95 | continue; 96 | } 97 | $currentUser = wp_set_current_user($user->ID); 98 | break; 99 | } 100 | if (empty($currentUser)) { 101 | wp_set_current_user(1); 102 | } 103 | } 104 | 105 | include_once ( ABSPATH . 'wp-admin/includes/admin.php' ); 106 | 107 | $actions = array(); 108 | 109 | foreach( WPR_API_Request::get_actions() as $action ) { 110 | 111 | // TODO Instead should just fire actions which we hook into. 112 | // TODO should namespace api methods? 113 | switch( $action ) { 114 | 115 | // TODO should be dynamic 116 | case 'get_plugin_version' : 117 | 118 | $actions[$action] = '1.1'; 119 | 120 | break; 121 | 122 | case 'get_filesystem_method' : 123 | 124 | $actions[$action] = get_filesystem_method(); 125 | 126 | break; 127 | 128 | case 'get_supported_filesystem_methods' : 129 | 130 | $actions[$action] = array(); 131 | 132 | if ( extension_loaded( 'ftp' ) || extension_loaded( 'sockets' ) || function_exists( 'fsockopen' ) ) 133 | $actions[$action][] = 'ftp'; 134 | 135 | if ( extension_loaded( 'ftp' ) ) 136 | $actions[$action][] = 'ftps'; 137 | 138 | if ( extension_loaded( 'ssh2' ) && function_exists( 'stream_get_contents' ) ) 139 | $actions[$action][] = 'ssh'; 140 | 141 | break; 142 | 143 | case 'get_wp_version' : 144 | 145 | global $wp_version; 146 | 147 | $actions[$action] = (string) $wp_version; 148 | 149 | break; 150 | 151 | case 'get_constants': 152 | 153 | $constants = array(); 154 | if ( is_array( WPR_API_Request::get_arg( 'constants' ) ) ) { 155 | 156 | foreach( WPR_API_Request::get_arg( 'constants' ) as $constant ) { 157 | if ( defined( $constant ) ) 158 | $constants[$constant] = constant( $constant ); 159 | else 160 | $constants[$constant] = null; 161 | } 162 | 163 | } 164 | $actions[$action] = $constants; 165 | 166 | break; 167 | 168 | case 'upgrade_core' : 169 | 170 | $actions[$action] = _wprp_upgrade_core(); 171 | 172 | break; 173 | 174 | case 'get_plugins' : 175 | 176 | $actions[$action] = _wprp_get_plugins(); 177 | 178 | break; 179 | 180 | case 'update_plugin' : 181 | case 'upgrade_plugin' : 182 | 183 | $api_args = array( 184 | 'zip_url' => esc_url_raw( WPR_API_Request::get_arg( 'zip_url' ) ), 185 | ); 186 | $actions[$action] = _wprp_update_plugin_wrap( sanitize_text_field( WPR_API_Request::get_arg( 'plugin' ) ), $api_args ); 187 | 188 | break; 189 | 190 | case 'validate_plugin' : 191 | $actions[$action] = _wprp_validate( sanitize_text_field( WPR_API_Request::get_arg( 'plugin' ) ) ); 192 | break; 193 | 194 | case 'install_plugin' : 195 | 196 | $api_args = array( 197 | 'version' => sanitize_text_field( WPR_API_Request::get_arg( 'version' ) ), 198 | ); 199 | $actions[$action] = _wprp_install_plugin( sanitize_text_field( WPR_API_Request::get_arg( 'plugin' ) ), $api_args ); 200 | 201 | break; 202 | 203 | case 'activate_plugin' : 204 | 205 | $actions[$action] = _wprp_activate_plugin( sanitize_text_field( WPR_API_Request::get_arg( 'plugin' ) ) ); 206 | 207 | break; 208 | 209 | case 'deactivate_plugin' : 210 | 211 | $actions[$action] = _wprp_deactivate_plugin( sanitize_text_field( WPR_API_Request::get_arg( 'plugin' ) ) ); 212 | 213 | break; 214 | 215 | case 'uninstall_plugin' : 216 | 217 | $actions[$action] = _wprp_uninstall_plugin( sanitize_text_field( WPR_API_Request::get_arg( 'plugin' ) ) ); 218 | 219 | break; 220 | 221 | case 'get_themes' : 222 | 223 | $actions[$action] = _wprp_get_themes(); 224 | 225 | break; 226 | 227 | case 'install_theme': 228 | 229 | $api_args = array( 230 | 'version' => sanitize_text_field( WPR_API_Request::get_arg( 'version' ) ), 231 | ); 232 | $actions[$action] = _wprp_install_theme( sanitize_text_field( WPR_API_Request::get_arg( 'theme' ) ), $api_args ); 233 | 234 | break; 235 | 236 | case 'activate_theme': 237 | 238 | $actions[$action] = _wprp_activate_theme( sanitize_text_field( WPR_API_Request::get_arg( 'theme' ) ) ); 239 | 240 | break; 241 | 242 | case 'update_theme' : 243 | case 'upgrade_theme' : // 'upgrade' is deprecated 244 | 245 | $actions[$action] = _wprp_update_theme( sanitize_text_field( WPR_API_Request::get_arg( 'theme' ) ) ); 246 | 247 | break; 248 | 249 | case 'delete_theme': 250 | 251 | $actions[$action] = _wprp_delete_theme( sanitize_text_field( WPR_API_Request::get_arg( 'theme' ) ) ); 252 | 253 | break; 254 | 255 | case 'do_backup' : 256 | 257 | if ( in_array( WPR_API_Request::get_arg( 'backup_type' ), array( 'complete', 'database', 'file' ) ) ) 258 | WPRP_Backups::get_instance()->set_type( WPR_API_Request::get_arg( 'backup_type' ) ); 259 | 260 | if ( WPR_API_Request::get_arg( 'backup_approach' ) && 'file_manifest' == WPR_API_Request::get_arg( 'backup_approach' ) ) 261 | WPRP_Backups::get_instance()->set_is_using_file_manifest( true ); 262 | 263 | $actions[$action] = WPRP_Backups::get_instance()->do_backup(); 264 | 265 | break; 266 | 267 | case 'get_backup' : 268 | 269 | $actions[$action] = WPRP_Backups::get_instance()->get_backup(); 270 | 271 | break; 272 | 273 | case 'delete_backup' : 274 | 275 | $actions[$action] = WPRP_Backups::get_instance()->cleanup(); 276 | 277 | break; 278 | 279 | case 'backup_heartbeat' : 280 | 281 | WPRP_Backups::get_instance()->set_is_using_file_manifest( true ); 282 | 283 | if ( in_array( WPR_API_Request::get_arg( 'backup_type' ), array( 'complete', 'database', 'file' ) ) ) 284 | WPRP_Backups::get_instance()->set_type( WPR_API_Request::get_arg( 'backup_type' ) ); 285 | 286 | $actions[$action] = WPRP_Backups::get_instance()->backup_heartbeat(); 287 | 288 | break; 289 | 290 | case 'supports_backups' : 291 | 292 | $actions[$action] = true; 293 | 294 | break; 295 | 296 | case 'get_site_info' : 297 | 298 | $actions[$action] = array( 299 | 'site_url' => get_site_url(), 300 | 'home_url' => get_home_url(), 301 | 'admin_url' => get_admin_url(), 302 | 'backups' => function_exists( '_wprp_get_backups_info' ) ? _wprp_get_backups_info() : array(), 303 | 'web_host' => _wprp_integration_get_web_host(), 304 | 'summary' => _wprp_get_content_summary(), 305 | ); 306 | 307 | break; 308 | 309 | case 'get_option': 310 | 311 | $actions[$action] = get_option( sanitize_text_field( WPR_API_Request::get_arg( 'option_name' ) ) ); 312 | 313 | break; 314 | 315 | case 'update_option': 316 | 317 | $actions[$action] = update_option( sanitize_text_field( WPR_API_Request::get_arg( 'option_name' ) ), WPR_API_Request::get_arg( 'option_value' ) ); 318 | 319 | break; 320 | 321 | case 'delete_option': 322 | 323 | $actions[$action] = delete_option( sanitize_text_field( WPR_API_Request::get_arg( 'option_name' ) ) ); 324 | 325 | break; 326 | 327 | case 'get_posts': 328 | 329 | $arg_keys = array( 330 | /** Author **/ 331 | 'author', 332 | 'author_name', 333 | 'author__in', 334 | 'author__not_in', 335 | 336 | /** Category **/ 337 | 'cat', 338 | 'category_name', 339 | 'category__and', 340 | 'category__in', 341 | 'category__not_in', 342 | 343 | /** Tag **/ 344 | 'tag', 345 | 'tag_id', 346 | 'tag__and', 347 | 'tag__in', 348 | 'tag__not_in', 349 | 'tag_slug__and', 350 | 'tag_slug__in', 351 | 352 | /** Search **/ 353 | 's', 354 | 355 | /** Post Attributes **/ 356 | 'name', 357 | 'pagename', 358 | 'post_parent', 359 | 'post_parent__in', 360 | 'post_parent__not_in', 361 | 'post__in', 362 | 'post__not_in', 363 | 'post_status', 364 | 'post_type', 365 | 366 | /** Order / Pagination / Etc. **/ 367 | 'order', 368 | 'orderby', 369 | 'nopaging', 370 | 'posts_per_page', 371 | 'offset', 372 | 'paged', 373 | 'page', 374 | 'ignore_sticky_posts', 375 | ); 376 | $args = array(); 377 | foreach( $arg_keys as $arg_key ) { 378 | // Note: WP_Query() supports validation / sanitization 379 | if ( null !== ( $value = WPR_API_Request::get_arg( $arg_key ) ) ) 380 | $args[$arg_key] = $value; 381 | } 382 | 383 | $query = new WP_Query; 384 | $query->query( $args ); 385 | $actions[$action] = $query->posts; 386 | 387 | break; 388 | 389 | case 'get_post': 390 | case 'delete_post': 391 | 392 | $post_id = (int)WPR_API_Request::get_arg( 'post_id' ); 393 | $post = get_post( $post_id ); 394 | 395 | if ( ! $post ) { 396 | $actions[$action] = new WP_Error( 'missing-post', __( "No post found.", 'wpremote' ) ); 397 | break; 398 | } 399 | 400 | if ( 'get_post' == $action ) { 401 | 402 | $actions[$action] = $post; 403 | 404 | } else if ( 'delete_post' == $action ) { 405 | 406 | $actions[$action] = wp_delete_post( $post_id ); 407 | 408 | } 409 | 410 | break; 411 | 412 | case 'create_post': 413 | case 'update_post': 414 | 415 | $arg_keys = array( 416 | 'menu_order', 417 | 'comment_status', 418 | 'ping_status', 419 | 'post_author', 420 | 'post_content', 421 | 'post_date', 422 | 'post_date_gmt', 423 | 'post_excerpt', 424 | 'post_name', 425 | 'post_parent', 426 | 'post_password', 427 | 'post_status', 428 | 'post_title', 429 | 'post_type', 430 | 'tags_input', 431 | ); 432 | $args = array(); 433 | foreach( $arg_keys as $arg_key ) { 434 | // Note: wp_update_post() supports validation / sanitization 435 | if ( null !== ( $value = WPR_API_Request::get_arg( $arg_key ) ) ) 436 | $args[$arg_key] = $value; 437 | } 438 | 439 | if ( 'create_post' == $action ) { 440 | 441 | if ( $post_id = wp_insert_post( $args ) ) 442 | $actions[$action] = get_post( $post_id ); 443 | else 444 | $actions[$action] = new WP_Error( 'create-post', __( "Error creating post.", 'wpremote' ) ); 445 | 446 | } else if ( 'update_post' == $action ) { 447 | 448 | $args['ID'] = (int)WPR_API_Request::get_arg( 'post_id' ); 449 | 450 | if ( ! get_post( $args['ID'] ) ) { 451 | $actions[$action] = new WP_Error( 'missing-post', __( "No post found.", 'wpremote' ) ); 452 | break; 453 | } 454 | 455 | if ( wp_update_post( $args ) ) 456 | $actions[$action] = get_post( $args['ID'] ); 457 | else 458 | $actions[$action] = new WP_Error( 'update-post', __( "Error updating post.", 'wpremote' ) ); 459 | 460 | } 461 | 462 | break; 463 | 464 | case 'get_metadata': 465 | 466 | $actions[$action] = get_metadata( WPR_API_Request::get_arg( 'meta_type' ), WPR_API_Request::get_arg( 'object_id' ), WPR_API_Request::get_arg( 'meta_key' ), false ); 467 | 468 | break; 469 | 470 | case 'add_metadata': 471 | 472 | $actions[$action] = add_metadata( WPR_API_Request::get_arg( 'meta_type' ), WPR_API_Request::get_arg( 'object_id' ), WPR_API_Request::get_arg( 'meta_key' ), WPR_API_Request::get_arg( 'meta_value' ) ); 473 | 474 | break; 475 | 476 | case 'update_metadata': 477 | 478 | $actions[$action] = update_metadata( WPR_API_Request::get_arg( 'meta_type' ), WPR_API_Request::get_arg( 'object_id' ), WPR_API_Request::get_arg( 'meta_key' ), WPR_API_Request::get_arg( 'meta_value' ) ); 479 | 480 | break; 481 | 482 | case 'delete_metadata': 483 | 484 | $actions[$action] = delete_metadata( WPR_API_Request::get_arg( 'meta_type' ), WPR_API_Request::get_arg( 'object_id' ), WPR_API_Request::get_arg( 'meta_key' ) ); 485 | 486 | break; 487 | 488 | case 'get_comments': 489 | 490 | $arg_keys = array( 491 | 'status', 492 | 'orderby', 493 | 'order', 494 | 'post_id', 495 | ); 496 | $args = array(); 497 | foreach( $arg_keys as $arg_key ) { 498 | // Note: get_comments() supports validation / sanitization 499 | if ( null !== ( $value = WPR_API_Request::get_arg( $arg_key ) ) ) 500 | $args[$arg_key] = $value; 501 | } 502 | $actions[$action] = get_comments( $args ); 503 | 504 | break; 505 | 506 | case 'get_comment': 507 | case 'delete_comment': 508 | 509 | $comment_id = (int)WPR_API_Request::get_arg( 'comment_id' ); 510 | $comment = get_comment( $comment_id ); 511 | 512 | if ( ! $comment ) { 513 | $actions[$action] = new WP_Error( 'missing-comment', __( "No comment found.", 'wpremote' ) ); 514 | break; 515 | } 516 | 517 | if ( 'get_comment' == $action ) { 518 | 519 | $actions[$action] = $comment; 520 | 521 | } else if ( 'delete_comment' == $action ) { 522 | 523 | $actions[$action] = wp_delete_comment( $comment_id ); 524 | 525 | } 526 | 527 | break; 528 | 529 | case 'create_comment': 530 | case 'update_comment': 531 | 532 | $arg_keys = array( 533 | 'comment_post_ID', 534 | 'comment_author', 535 | 'comment_author_email', 536 | 'comment_author_url', 537 | 'comment_date', 538 | 'comment_date_gmt', 539 | 'comment_content', 540 | 'comment_approved', 541 | 'comment_type', 542 | 'comment_parent', 543 | 'user_id' 544 | ); 545 | $args = array(); 546 | foreach( $arg_keys as $arg_key ) { 547 | // Note: wp_update_comment() supports validation / sanitization 548 | if ( null !== ( $value = WPR_API_Request::get_arg( $arg_key ) ) ) 549 | $args[$arg_key] = $value; 550 | } 551 | 552 | if ( 'create_comment' == $action ) { 553 | 554 | if ( $comment_id = wp_insert_comment( $args ) ) 555 | $actions[$action] = get_comment( $comment_id ); 556 | else 557 | $actions[$action] = new WP_Error( 'create-comment', __( "Error creating comment.", 'wpremote' ) ); 558 | 559 | } else if ( 'update_comment' == $action ) { 560 | 561 | $args['comment_ID'] = (int)WPR_API_Request::get_arg( 'comment_id' ); 562 | 563 | if ( ! get_comment( $args['comment_ID'] ) ) { 564 | $actions[$action] = new WP_Error( 'missing-comment', __( "No comment found.", 'wpremote' ) ); 565 | break; 566 | } 567 | 568 | if ( wp_update_comment( $args ) ) 569 | $actions[$action] = get_comment( $args['comment_ID'] ); 570 | else 571 | $actions[$action] = new WP_Error( 'update-comment', __( "Error updating comment.", 'wpremote' ) ); 572 | 573 | } 574 | 575 | break; 576 | 577 | case 'get_users': 578 | 579 | $arg_keys = array( 580 | 'include', 581 | 'exclude', 582 | 'search', 583 | 'orderby', 584 | 'order', 585 | 'offset', 586 | 'number', 587 | ); 588 | $args = array(); 589 | foreach( $arg_keys as $arg_key ) { 590 | // Note: get_users() supports validation / sanitization 591 | if ( $value = WPR_API_Request::get_arg( $arg_key ) ) 592 | $args[$arg_key] = $value; 593 | } 594 | 595 | $users = array_map( 'wprp_format_user_obj', get_users( $args ) ); 596 | $actions[$action] = $users; 597 | 598 | break; 599 | 600 | case 'get_user': 601 | case 'update_user': 602 | case 'delete_user': 603 | 604 | $user_id = (int)WPR_API_Request::get_arg( 'user_id' ); 605 | $user = get_user_by( 'id', $user_id ); 606 | 607 | if ( ! $user ) { 608 | $actions[$action] = new WP_Error( 'missing-user', "No user found." ); 609 | break; 610 | } 611 | 612 | require_once ABSPATH . '/wp-admin/includes/user.php'; 613 | 614 | if ( 'get_user' == $action ) { 615 | 616 | $actions[$action] = wprp_format_user_obj( $user ); 617 | 618 | } else if ( 'update_user' == $action ) { 619 | 620 | $fields = array( 621 | 'user_email', 622 | 'display_name', 623 | 'first_name', 624 | 'last_name', 625 | 'user_nicename', 626 | 'user_pass', 627 | 'user_url', 628 | 'description' 629 | ); 630 | $args = array(); 631 | foreach( $fields as $field ) { 632 | // Note: wp_update_user() handles sanitization / validation 633 | if ( null !== ( $value = WPR_API_Request::get_arg( $field ) ) ) 634 | $args[$field] = $value; 635 | } 636 | $args['ID'] = $user->ID; 637 | $ret = wp_update_user( $args ); 638 | if ( is_wp_error( $ret ) ) 639 | $actions[$action] = $ret; 640 | else 641 | $actions[$action] = wprp_format_user_obj( get_user_by( 'id', $ret ) ); 642 | 643 | } else if ( 'delete_user' == $action ) { 644 | 645 | $actions[$action] = wp_delete_user( $user->ID ); 646 | 647 | } 648 | 649 | 650 | break; 651 | 652 | case 'create_user': 653 | 654 | $args = array( 655 | // Note: wp_insert_user() handles sanitization / validation 656 | 'user_login' => WPR_API_Request::get_arg( 'user_login' ), 657 | 'user_email' => WPR_API_Request::get_arg( 'user_email' ), 658 | 'role' => get_option('default_role'), 659 | 'user_pass' => false, 660 | 'user_registered' => strftime( "%F %T", time() ), 661 | 'display_name' => false, 662 | ); 663 | foreach( $args as $key => $value ) { 664 | // Note: wp_insert_user() handles sanitization / validation 665 | if ( null !== ( $new_value = WPR_API_Request::get_arg( $key ) ) ) 666 | $args[$key] = $new_value; 667 | } 668 | 669 | if ( ! $args['user_pass'] ) { 670 | $args['user_pass'] = wp_generate_password(); 671 | } 672 | 673 | $user_id = wp_insert_user( $args ); 674 | 675 | if ( is_wp_error( $user_id ) ) { 676 | $actions[$action] = array( 'status' => 'error', 'error' => $user_id->get_error_message() ); 677 | } else { 678 | $actions[$action] = wprp_format_user_obj( get_user_by( 'id', $user_id ) ); 679 | } 680 | 681 | break; 682 | 683 | case 'enable_log' : 684 | update_option( 'wprp_enable_log', true ); 685 | $actions[$action] = true; 686 | break; 687 | 688 | case 'disable_log' : 689 | delete_option( 'wprp_enable_log' ); 690 | $actions[$action] = true; 691 | break; 692 | 693 | case 'get_log' : 694 | 695 | if ( class_exists( 'WPRP_Log' ) ) { 696 | $actions[$action] = WPRP_Log::get_instance()->get_items(); 697 | WPRP_Log::get_instance()->delete_items(); 698 | } else { 699 | $actions[$action] = new WP_Error( 'log-not-enabled', 'Logging is not enabled' ); 700 | } 701 | 702 | break; 703 | 704 | default : 705 | 706 | $actions[$action] = 'not-implemented'; 707 | 708 | break; 709 | 710 | } 711 | 712 | } 713 | 714 | foreach ( $actions as $key => $action ) { 715 | 716 | if ( is_wp_error( $action ) ) { 717 | 718 | $actions[$key] = (object) array( 719 | 'errors' => $action->errors 720 | ); 721 | } 722 | } 723 | 724 | echo json_encode( $actions ); 725 | 726 | exit; 727 | -------------------------------------------------------------------------------- /wprp.backups.php: -------------------------------------------------------------------------------- 1 | set_path( $this->path() ); 46 | 47 | // Set the excludes 48 | if ( class_exists( 'WPR_API_Request' ) && WPR_API_Request::get_arg( 'backup_excludes' ) ) 49 | $backup_excludes = WPR_API_Request::get_arg( 'backup_excludes' ); 50 | else if ( isset( $_GET['backup_excludes'] ) ) 51 | $backup_excludes = $_GET['backup_excludes']; 52 | 53 | if ( ! empty( $backup_excludes ) ) 54 | $this->set_excludes( apply_filters( 'wprp_backup_excludes', $backup_excludes ) ); 55 | 56 | $this->filesize_transient = 'wprp_' . '_' . $this->get_type() . '_' . substr( md5( $this->exclude_string() ), 20 ) . '_filesize'; 57 | 58 | } 59 | 60 | /** 61 | * Perform a backup of the site 62 | * @return bool|WP_Error 63 | */ 64 | public function do_backup() { 65 | 66 | @ignore_user_abort( true ); 67 | 68 | $this->set_status( 'Starting backup...' ); 69 | 70 | $this->set_start_timestamp(); 71 | 72 | $this->backup(); 73 | 74 | if ( ! file_exists( $this->get_archive_filepath() ) ) { 75 | 76 | $errors = $this->get_errors(); 77 | if ( ! empty( $errors ) ) 78 | return new WP_Error( 'backup-failed', implode( ', ', $errors ) ); 79 | else 80 | return new WP_Error( 'backup-failed', __( 'Backup file is missing.', 'wpremote' ) ); 81 | 82 | } 83 | 84 | return true; 85 | 86 | } 87 | 88 | /** 89 | * Get the backup once it has run, will return status running as a WP Error 90 | * 91 | * @return WP_Error|string 92 | */ 93 | public function get_backup() { 94 | 95 | global $is_apache; 96 | 97 | // Restore the start timestamp to global scope so HM Backup recognizes the proper archive file 98 | $this->restore_start_timestamp(); 99 | 100 | if ( $status = $this->get_status() ) { 101 | 102 | if ( $this->is_backup_still_running() ) 103 | return new WP_Error( 'error-status', $status ); 104 | else 105 | return new WP_Error( 'backup-process-killed', __( 'Backup process failed or was killed.', 'wpremote' ) ); 106 | } 107 | 108 | $backup = $this->get_archive_filepath(); 109 | 110 | if ( file_exists( $backup ) ) { 111 | 112 | // Append the secret key on apache servers 113 | if ( $is_apache && $this->key() ) { 114 | 115 | $backup = add_query_arg( 'key', $this->key(), $backup ); 116 | 117 | // Force the .htaccess to be rebuilt 118 | if ( file_exists( $this->get_path() . '/.htaccess' ) ) 119 | unlink( $this->get_path() . '/.htaccess' ); 120 | 121 | $this->path(); 122 | 123 | } 124 | 125 | $response = new stdClass; 126 | $response->url = str_replace( parent::conform_dir( WP_CONTENT_DIR ), WP_CONTENT_URL, $backup ); 127 | $response->seconds_elapsed = time() - $this->start_timestamp; 128 | return $response; 129 | 130 | } 131 | 132 | return new WP_Error( 'backup-failed', __( 'No backup was found', 'wpremote' ) ); 133 | 134 | } 135 | 136 | /** 137 | * Remove the backups directoy and everything it contains 138 | * 139 | * @access public 140 | * @return void 141 | */ 142 | public function cleanup() { 143 | 144 | $this->rmdir_recursive( $this->get_path() ); 145 | 146 | delete_option( 'wprp_backup_path' ); 147 | 148 | } 149 | 150 | /** 151 | * Cleanup old ZipArchive partials that may have been left by old processes 152 | */ 153 | public function cleanup_ziparchive_partials() { 154 | 155 | foreach( glob( $this->get_path() . '/*.zip.*' ) as $ziparchive_partial ) { 156 | unlink( $ziparchive_partial ); 157 | } 158 | 159 | } 160 | 161 | /** 162 | * Get the estimated size of the sites files and database 163 | * 164 | * If the size hasn't been calculated yet then it fires an API request 165 | * to calculate the size and returns string 'Calculating' 166 | * 167 | * @access public 168 | * @return string $size|Calculating 169 | */ 170 | public function get_estimate_size() { 171 | 172 | // Check the cache 173 | if ( $size = get_transient( $this->filesize_transient ) ) { 174 | 175 | // If we have a number, format it and return 176 | if ( is_numeric( $size ) ) 177 | return size_format( $size, null, '%01u %s' ); 178 | 179 | // Otherwise the filesize must still be calculating 180 | else 181 | return __( 'Calculating', 'wpremote' ); 182 | 183 | } 184 | 185 | // we dont know the size yet, fire off a remote request to get it for later 186 | // it can take some time so we have a small timeout then return "Calculating" 187 | global $wprp_noauth_nonce; 188 | wp_remote_get( add_query_arg( array( 'action' => 'wprp_calculate_backup_size', 'backup_excludes' => $this->get_excludes() ), add_query_arg( '_wpnonce', $wprp_noauth_nonce, admin_url( 'admin-ajax.php' ) ) ), array( 'timeout' => 0.1, 'sslverify' => false ) ); 189 | 190 | return __( 'Calculating', 'wpremote' ); 191 | 192 | } 193 | 194 | /** 195 | * Hook into the actions fired in HM Backup and set the status 196 | * 197 | * @param $action 198 | */ 199 | protected function do_action( $action ) { 200 | 201 | $this->update_heartbeat_timestamp(); 202 | 203 | switch ( $action ) : 204 | 205 | case 'hmbkp_backup_started': 206 | 207 | $this->save_backup_process_id(); 208 | 209 | break; 210 | 211 | case 'hmbkp_mysqldump_started' : 212 | 213 | $this->set_status( sprintf( __( 'Dumping Database %s', 'wpremote' ), '(' . $this->get_mysqldump_method() . ')' ) ); 214 | 215 | break; 216 | 217 | case 'hmbkp_mysqldump_verify_started' : 218 | 219 | $this->set_status( sprintf( __( 'Verifying Database Dump %s', 'wpremote' ), '(' . $this->get_mysqldump_method() . ')' ) ); 220 | 221 | break; 222 | 223 | case 'hmbkp_archive_started' : 224 | 225 | if ( $this->is_using_file_manifest() ) 226 | $status = sprintf( __( '%d files remaining to archive %s', 'wpremote' ), $this->file_manifest_remaining, '(' . $this->get_archive_method() . ')' ); 227 | else 228 | $status = sprintf( __( 'Creating zip archive %s', 'wpremote' ), '(' . $this->get_archive_method() . ')' ); 229 | 230 | $this->set_status( $status ); 231 | 232 | break; 233 | 234 | case 'hmbkp_archive_verify_started' : 235 | 236 | $this->set_status( sprintf( __( 'Verifying Zip Archive %s', 'wpremote' ), '(' . $this->get_archive_method() . ')' ) ); 237 | 238 | break; 239 | 240 | case 'hmbkp_backup_complete' : 241 | 242 | if ( file_exists( $this->get_schedule_running_path() ) ) 243 | unlink( $this->get_schedule_running_path() ); 244 | 245 | $this->clear_backup_process_id(); 246 | 247 | break; 248 | 249 | case 'hmbkp_error' : 250 | 251 | if ( $this->get_errors() ) { 252 | 253 | $file = $this->get_path() . '/.backup_errors'; 254 | 255 | if ( file_exists( $file ) ) 256 | unlink( $file ); 257 | 258 | if ( ! $handle = @fopen( $file, 'w' ) ) 259 | return; 260 | 261 | fwrite( $handle, json_encode( $this->get_errors() ) ); 262 | 263 | fclose( $handle ); 264 | 265 | } 266 | 267 | break; 268 | 269 | case 'hmbkp_warning' : 270 | 271 | if ( $this->get_warnings() ) { 272 | 273 | $file = $this->get_path() . '/.backup_warnings'; 274 | 275 | if ( file_exists( $file ) ) 276 | unlink( $file ); 277 | 278 | if ( ! $handle = @fopen( $file, 'w' ) ) 279 | return; 280 | 281 | fwrite( $handle, json_encode( $this->get_warnings() ) ); 282 | 283 | fclose( $handle ); 284 | 285 | } 286 | 287 | break; 288 | 289 | endswitch; 290 | 291 | } 292 | 293 | /** 294 | * Get the path to the backups directory 295 | * 296 | * Will try to create it if it doesn't exist 297 | * and will fallback to default if a custom dir 298 | * isn't writable. 299 | * 300 | * @access private 301 | * @see default_path() 302 | * @return string $path 303 | */ 304 | private function path() { 305 | 306 | global $is_apache; 307 | 308 | $path = get_option( 'wprp_backup_path' ); 309 | 310 | // If the dir doesn't exist or isn't writable then use the default path instead instead 311 | if ( ! $path || ( is_dir( $path ) && ! is_writable( $path ) ) || ( ! is_dir( $path ) && ! is_writable( dirname( $path ) ) ) ) 312 | $path = $this->path_default(); 313 | 314 | // Create the backups directory if it doesn't exist 315 | if ( ! is_dir( $path ) && is_writable( dirname( $path ) ) ) 316 | mkdir( $path, 0755 ); 317 | 318 | // If the path has changed then cache it 319 | if ( get_option( 'wprp_backup_path' ) !== $path ) 320 | update_option( 'wprp_backup_path', $path ); 321 | 322 | // Protect against directory browsing by including a index.html file 323 | $index = $path . '/index.html'; 324 | 325 | if ( ! file_exists( $index ) && is_writable( $path ) ) 326 | file_put_contents( $index, '' ); 327 | 328 | $htaccess = $path . '/.htaccess'; 329 | 330 | // Protect the directory with a .htaccess file on Apache servers 331 | if ( $is_apache && function_exists( 'insert_with_markers' ) && ! file_exists( $htaccess ) && is_writable( $path ) ) { 332 | 333 | $contents[] = '# ' . sprintf( __( 'This %s file ensures that other people cannot download your backup files.', 'wpremote' ), '.htaccess' ); 334 | $contents[] = ''; 335 | $contents[] = ''; 336 | $contents[] = 'RewriteEngine On'; 337 | $contents[] = 'RewriteCond %{QUERY_STRING} !key=' . $this->key(); 338 | $contents[] = 'RewriteRule (.*) - [F]'; 339 | $contents[] = ''; 340 | $contents[] = ''; 341 | 342 | insert_with_markers( $htaccess, __( 'WP Remote Backup', 'wpremote' ), $contents ); 343 | 344 | } 345 | 346 | return parent::conform_dir( $path ); 347 | 348 | } 349 | 350 | /** 351 | * Return the default backup path 352 | * 353 | * @access private 354 | * @return string $path 355 | */ 356 | private function path_default() { 357 | 358 | $dirname = substr( $this->key(), 0, 10 ) . '-wprbackups'; 359 | $path = parent::conform_dir( trailingslashit( WP_CONTENT_DIR ) . $dirname ); 360 | 361 | $upload_dir = wp_upload_dir(); 362 | 363 | // If the backups dir can't be created in WP_CONTENT_DIR then fallback to uploads 364 | if ( ( ( ! is_dir( $path ) && ! is_writable( dirname( $path ) ) ) || ( is_dir( $path ) && ! is_writable( $path ) ) ) && strpos( $path, $upload_dir['basedir'] ) === false ) 365 | $path = parent::conform_dir( trailingslashit( $upload_dir['basedir'] ) . $dirname ); 366 | 367 | return $path; 368 | } 369 | 370 | /** 371 | * Calculate and generate the private key 372 | * 373 | * @access private 374 | * @return string $key 375 | */ 376 | private function key() { 377 | 378 | if ( ! empty( $this->key ) ) 379 | return $this->key; 380 | 381 | $key = array( ABSPATH, time() ); 382 | 383 | foreach ( array( 'AUTH_KEY', 'SECURE_AUTH_KEY', 'LOGGED_IN_KEY', 'NONCE_KEY', 'AUTH_SALT', 'SECURE_AUTH_SALT', 'LOGGED_IN_SALT', 'NONCE_SALT', 'SECRET_KEY' ) as $constant ) 384 | if ( defined( $constant ) ) 385 | $key[] = constant( $constant ); 386 | 387 | shuffle( $key ); 388 | 389 | $this->key = md5( serialize( $key ) ); 390 | return $this->key; 391 | } 392 | 393 | /** 394 | * Get the status of the running backup. 395 | * 396 | * @access public 397 | * @return string 398 | */ 399 | private function get_status() { 400 | 401 | if ( ! file_exists( $this->get_schedule_running_path() ) ) 402 | return ''; 403 | 404 | $status = file_get_contents( $this->get_schedule_running_path() ); 405 | 406 | return $status; 407 | 408 | } 409 | 410 | /** 411 | * Get the path to the backup running file that stores the running backup status 412 | * 413 | * @access private 414 | * @return string 415 | */ 416 | private function get_schedule_running_path() { 417 | return $this->get_path() . '/.backup-running'; 418 | } 419 | 420 | /** 421 | * Set the status of the running backup 422 | * 423 | * @access public 424 | * @param string $message 425 | * @return void 426 | */ 427 | private function set_status( $message ) { 428 | 429 | if ( ! $handle = fopen( $this->get_schedule_running_path(), 'w' ) ) 430 | return; 431 | 432 | fwrite( $handle, $message ); 433 | 434 | fclose( $handle ); 435 | 436 | } 437 | 438 | /** 439 | * Set the start timestamp for the backup 440 | */ 441 | private function set_start_timestamp() { 442 | $this->start_timestamp = current_time( 'timestamp' ); 443 | file_put_contents( $this->get_path() . '/.start-timestamp', $this->start_timestamp ); 444 | } 445 | 446 | /** 447 | * Restore the start timestamp for the backup 448 | */ 449 | private function restore_start_timestamp() { 450 | if ( $start_timestamp = file_get_contents( $this->get_path() . '/.start-timestamp' ) ) 451 | $this->start_timestamp = (int) $start_timestamp; 452 | } 453 | 454 | /** 455 | * Update the heartbeat timestamp to the current time. 456 | */ 457 | private function update_heartbeat_timestamp() { 458 | file_put_contents( $this->get_path() . '/.heartbeat-timestamp', time() ); 459 | } 460 | 461 | /** 462 | * Get the heartbeat timestamp. 463 | */ 464 | private function get_heartbeat_timestamp() { 465 | 466 | $heartbeat = $this->get_path() . '/.heartbeat-timestamp'; 467 | 468 | if ( file_exists( $heartbeat ) ) 469 | return (int) file_get_contents( $heartbeat ); 470 | 471 | return false; 472 | } 473 | 474 | /** 475 | * Get the file path to the backup process ID log 476 | * 477 | * @access private 478 | */ 479 | private function get_backup_process_id_path() { 480 | return $this->get_path() . '/.backup-process-id'; 481 | } 482 | 483 | /** 484 | * Get the current backup process ID 485 | * 486 | * @access private 487 | */ 488 | private function get_backup_process_id() { 489 | $file = $this->get_backup_process_id_path(); 490 | if ( file_exists( $file ) ) 491 | return (int) trim( file_get_contents( $file ) ); 492 | else 493 | return false; 494 | } 495 | 496 | /** 497 | * Save this current backup process ID in case 498 | * we need to check later whether it was killed in action 499 | * 500 | * @access private 501 | */ 502 | private function save_backup_process_id() { 503 | 504 | if ( ! $handle = fopen( $this->get_backup_process_id_path(), 'w' ) ) 505 | return; 506 | 507 | fwrite( $handle, getmypid() ); 508 | 509 | fclose( $handle ); 510 | 511 | } 512 | 513 | /** 514 | * Clear the backup process ID 515 | * 516 | * @access private 517 | */ 518 | private function clear_backup_process_id() { 519 | 520 | if ( file_exists( $this->get_backup_process_id_path() ) ) 521 | unlink( $this->get_backup_process_id_path() ); 522 | } 523 | 524 | /** 525 | * Whether or not a backup appears to be in progress 526 | * 527 | * @access private 528 | */ 529 | private function is_backup_still_running( $context = 'get_backup' ) { 530 | 531 | // Check whether there's supposed to be a backup in progress 532 | if ( false === ( $process_id = $this->get_backup_process_id() ) ) 533 | return false; 534 | 535 | // When safe mode is enabled, WPRP can't modify max_execution_time 536 | if ( self::is_safe_mode_active() && ini_get( 'max_execution_time' ) ) 537 | $time_to_wait = ini_get( 'max_execution_time' ); 538 | else 539 | $time_to_wait = 90; 540 | 541 | // Give heartbeat requests a little bit of time to restart 542 | if ( 'get_backup' == $context ) 543 | $time_to_wait += 15; 544 | 545 | // If the heartbeat has been modified in the last 90 seconds, we might not be dead 546 | if ( ( time() - $this->get_heartbeat_timestamp() ) < $time_to_wait ) 547 | return true; 548 | 549 | // Check if there's any file being modified. 550 | $backup_file_dirs = array( $this->get_path() ); 551 | 552 | if ( $this->is_using_file_manifest() ) { 553 | $backup_file_dirs[] = $this->get_file_manifest_dirpath(); 554 | } 555 | 556 | foreach ( $backup_file_dirs as $backup_file_dir ) { 557 | $backup_files = glob( $backup_file_dir . '/*' ); 558 | 559 | $file_mtimes = array(); 560 | foreach( $backup_files as $backup_file ) { 561 | $file_mtimes[] = filemtime( $backup_file ); 562 | } 563 | if ( ! empty( $file_mtimes ) ) { 564 | $latest_file_mtime = max( $file_mtimes ); 565 | if ( ( time() - $latest_file_mtime ) < $time_to_wait ) 566 | return true; 567 | } 568 | } 569 | 570 | return false; 571 | 572 | } 573 | 574 | /** 575 | * Check if there's a backup in progress, whether it's running, 576 | * and restart it if it's not running 577 | * 578 | * @todo support checking whether the database should exist 579 | */ 580 | public function backup_heartbeat() { 581 | 582 | // Restore the start timestamp to global scope so HM Backup recognizes the proper archive file 583 | $this->restore_start_timestamp(); 584 | 585 | // No process means no backup in progress 586 | if ( ! $this->get_backup_process_id() ) 587 | return false; 588 | 589 | // No file manifest directory means this wasn't a file manifest approach 590 | if ( ! is_dir( $this->get_file_manifest_dirpath() ) ) 591 | return false; 592 | 593 | // Check whether there's supposed to be a backup in progress 594 | if ( $this->get_backup_process_id() && $this->is_backup_still_running( 'backup_heartbeat' ) ) 595 | return false; 596 | 597 | // Uh oh, needs to be restarted 598 | $this->cleanup_ziparchive_partials(); 599 | 600 | $this->save_backup_process_id(); 601 | 602 | $this->restart_archive(); 603 | 604 | } 605 | 606 | /** 607 | * Calculate the size of the backup 608 | * 609 | * Doesn't account for compression 610 | * 611 | * @access public 612 | * @return string 613 | */ 614 | public function get_filesize() { 615 | 616 | $filesize = 0; 617 | 618 | // Only try to calculate once per hour 619 | set_transient( $this->filesize_transient, 'Calculating', time() + 60 * 60 ); 620 | 621 | // Don't include database if file only 622 | if ( $this->get_type() != 'file' ) { 623 | 624 | global $wpdb; 625 | 626 | $res = $wpdb->get_results( 'SHOW TABLE STATUS FROM `' . DB_NAME . '`', ARRAY_A ); 627 | 628 | foreach ( $res as $r ) 629 | $filesize += (float) $r['Data_length']; 630 | 631 | } 632 | 633 | // Don't include files if database only 634 | if ( $this->get_type() != 'database' ) { 635 | 636 | // Get rid of any cached filesizes 637 | clearstatcache(); 638 | 639 | $excludes = $this->exclude_string( 'regex' ); 640 | 641 | foreach ( $this->get_files() as $file ) { 642 | 643 | // Skip dot files, they should only exist on versions of PHP between 5.2.11 -> 5.3 644 | if ( method_exists( $file, 'isDot' ) && $file->isDot() ) 645 | continue; 646 | 647 | if ( ! @realpath( $file->getPathname() ) || ! $file->isReadable() ) 648 | continue; 649 | 650 | // Excludes 651 | if ( $excludes && preg_match( '(' . $excludes . ')', str_ireplace( trailingslashit( $this->get_root() ), '', parent::conform_dir( $file->getPathname() ) ) ) ) 652 | continue; 653 | 654 | $filesize += (float) $file->getSize(); 655 | 656 | } 657 | 658 | } 659 | 660 | // Cache for a day 661 | set_transient( $this->filesize_transient, $filesize, time() + 60 * 60 * 24 ); 662 | 663 | } 664 | 665 | } 666 | 667 | /* 668 | * Return an array of back meta information 669 | * 670 | * @return array 671 | */ 672 | function _wprp_get_backups_info() { 673 | 674 | $hm_backup = new WPRP_HM_Backup(); 675 | 676 | return array( 677 | 'mysqldump_path' => $hm_backup->get_mysqldump_command_path(), 678 | 'zip_path' => $hm_backup->get_zip_command_path(), 679 | 'estimated_size' => WPRP_Backups::get_instance()->get_estimate_size() 680 | ); 681 | 682 | } 683 | 684 | /** 685 | * Calculate the filesize of the site 686 | * 687 | * The calculated size is stored in a transient 688 | */ 689 | function wprp_ajax_calculate_backup_size() { 690 | 691 | if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'wprp_calculate_backup_size' ) ) 692 | exit; 693 | 694 | WPRP_Backups::get_instance()->get_filesize(); 695 | 696 | exit; 697 | } 698 | add_action( 'wp_ajax_nopriv_wprp_calculate_backup_size', 'wprp_ajax_calculate_backup_size' ); 699 | -------------------------------------------------------------------------------- /wprp.hm.backup.php: -------------------------------------------------------------------------------- 1 | get_path() ) . $this->get_archive_filename(); 319 | 320 | } 321 | 322 | /** 323 | * Get the filename of the archive file 324 | * 325 | * @access public 326 | * @return string 327 | */ 328 | public function get_archive_filename() { 329 | 330 | if ( empty( $this->archive_filename ) ) { 331 | 332 | if ( empty( $this->start_timestamp ) ) 333 | $this->start_timestamp = current_time( 'timestamp' ); 334 | 335 | $this->set_archive_filename( implode( '-', array( sanitize_title( str_ireplace( array( 'http://', 'https://', 'www' ), '', home_url() ) ), 'backup', date( 'Y-m-d-H-i-s', $this->start_timestamp ) ) ) . '.zip' ); 336 | } 337 | 338 | return $this->archive_filename; 339 | 340 | } 341 | 342 | /** 343 | * Set the filename of the archive file 344 | * 345 | * @param string $filename 346 | * @throws Exception 347 | */ 348 | public function set_archive_filename( $filename ) { 349 | 350 | if ( empty( $filename ) || ! is_string( $filename ) ) 351 | throw new Exception( __( 'archive filename must be a non empty string', 'wpremote' ) ); 352 | 353 | if ( pathinfo( $filename, PATHINFO_EXTENSION ) !== 'zip' ) 354 | throw new Exception( __( 'invalid file extension for archive filename', 'wpremote' ) . '' . $filename . '' ); 355 | 356 | $this->archive_filename = strtolower( sanitize_file_name( remove_accents( $filename ) ) ); 357 | 358 | } 359 | 360 | /** 361 | * Get the full filepath to the database dump file. 362 | * 363 | * @access public 364 | * @return string 365 | */ 366 | public function get_database_dump_filepath() { 367 | 368 | return trailingslashit( $this->get_path() ) . $this->get_database_dump_filename(); 369 | 370 | } 371 | 372 | /** 373 | * Get the filename of the database dump file 374 | * 375 | * @access public 376 | * @return string 377 | */ 378 | public function get_database_dump_filename() { 379 | 380 | if ( empty( $this->database_dump_filename ) ) 381 | $this->set_database_dump_filename( 'database_' . DB_NAME . '.sql' ); 382 | 383 | return $this->database_dump_filename; 384 | 385 | } 386 | 387 | /** 388 | * Set the filename of the database dump file 389 | * 390 | * @param string $filename 391 | * @throws Exception 392 | */ 393 | public function set_database_dump_filename( $filename ) { 394 | 395 | if ( empty( $filename ) || ! is_string( $filename ) ) 396 | throw new Exception( __( 'database dump filename must be a non empty string', 'wpremote' ) ); 397 | 398 | if ( pathinfo( $filename, PATHINFO_EXTENSION ) !== 'sql' ) 399 | throw new Exception( __( 'invalid file extension for database dump filename', 'wpremote' ) . '' . $filename . '' ); 400 | 401 | $this->database_dump_filename = strtolower( sanitize_file_name( remove_accents( $filename ) ) ); 402 | 403 | } 404 | 405 | /** 406 | * Get the root directory to backup from 407 | * 408 | * Defaults to the root of the path equivalent of your home_url 409 | * 410 | * @access public 411 | * @return string 412 | */ 413 | public function get_root() { 414 | 415 | if ( empty( $this->root ) ) 416 | $this->set_root( self::conform_dir( self::get_home_path() ) ); 417 | 418 | return $this->root; 419 | 420 | } 421 | 422 | /** 423 | * Set the root directory to backup from 424 | * 425 | * @param string $path 426 | * @throws Exception 427 | */ 428 | public function set_root( $path ) { 429 | 430 | if ( empty( $path ) || ! is_string( $path ) || ! is_dir ( $path ) ) 431 | throw new Exception( sprintf( __( 'Invalid root path %s must be a valid directory path', 'wpremote' ), '' . $path . '' ) ); 432 | 433 | $this->root = self::conform_dir( $path ); 434 | 435 | } 436 | 437 | /** 438 | * Get the directory backups are saved to 439 | * 440 | * @access public 441 | * @return string 442 | */ 443 | public function get_path() { 444 | 445 | if ( empty( $this->path ) ) 446 | $this->set_path( self::conform_dir( self::get_path_default() ) ); 447 | 448 | return $this->path; 449 | 450 | } 451 | 452 | /** 453 | * Get default backup path to save to. 454 | * 455 | * @return string 456 | */ 457 | protected function get_path_default() { 458 | return WP_CONTENT_DIR . '/backups'; 459 | } 460 | 461 | /** 462 | * Set the directory backups are saved to 463 | * 464 | * @param string $path 465 | * @throws Exception 466 | */ 467 | public function set_path( $path ) { 468 | 469 | if ( empty( $path ) || ! is_string( $path ) ) 470 | throw new Exception( sptrinf( __( 'Invalid backup path %s must be a non empty (string)', wpremote ), '' . $path . '' ) ); 471 | 472 | $this->path = self::conform_dir( $path ); 473 | 474 | } 475 | 476 | /** 477 | * Get the archive method that was used for the backup 478 | * 479 | * Will be either zip, ZipArchive or PclZip 480 | * 481 | * @access public 482 | */ 483 | public function get_archive_method() { 484 | return $this->archive_method; 485 | } 486 | 487 | /** 488 | * Get the database dump method that was used for the backup 489 | * 490 | * Will be either mysqldump or mysqldump_fallback 491 | * 492 | * @access public 493 | */ 494 | public function get_mysqldump_method() { 495 | return $this->mysqldump_method; 496 | } 497 | 498 | /** 499 | * Whether or not to use the file manifest 500 | * 501 | * @access public 502 | */ 503 | public function is_using_file_manifest() { 504 | return apply_filters( 'hmbkp_use_file_manifest', (bool)$this->using_file_manifest ); 505 | } 506 | 507 | /** 508 | * Set whether or not to use the file manifest 509 | * 510 | * @access public 511 | */ 512 | public function set_is_using_file_manifest( $val ) { 513 | $this->using_file_manifest = (bool)$val; 514 | } 515 | 516 | /** 517 | * Create a series of file manifests for the backup 518 | * 519 | * @access private 520 | */ 521 | private function create_file_manifests() { 522 | 523 | if ( is_dir( $this->get_file_manifest_dirpath() ) ) 524 | $this->rmdir_recursive( $this->get_file_manifest_dirpath() ); 525 | 526 | mkdir( $this->get_file_manifest_dirpath(), 0755 ); 527 | 528 | // Protect against directory browsing by including a index.html file 529 | $index = $this->get_file_manifest_dirpath() . '/index.html'; 530 | if ( ! file_exists( $index ) && is_writable( $this->get_file_manifest_dirpath() ) ) 531 | file_put_contents( $index, '' ); 532 | 533 | $excludes = $this->exclude_string( 'regex' ); 534 | 535 | $file_manifest = array(); 536 | $this->file_manifest_remaining = 0; 537 | $file_manifest_file_count = 0; 538 | $current_batch = 0; 539 | foreach( $this->get_files() as $file ) { 540 | 541 | // Skip dot files, they should only exist on versions of PHP between 5.2.11 -> 5.3 542 | if ( method_exists( $file, 'isDot' ) && $file->isDot() ) 543 | continue; 544 | 545 | // Skip unreadable files 546 | if ( ! @realpath( $file->getPathname() ) || ! $file->isReadable() ) 547 | continue; 548 | 549 | // Excludes 550 | if ( $excludes && preg_match( '(' . $excludes . ')', str_ireplace( trailingslashit( $this->get_root() ), '', self::conform_dir( $file->getPathname() ) ) ) ) 551 | continue; 552 | 553 | if ( $file->isDir() ) 554 | $line = trailingslashit( str_ireplace( trailingslashit( $this->get_root() ), '', self::conform_dir( $file->getPathname() ) ) ); 555 | 556 | elseif ( $file->isFile() ) 557 | $line = str_ireplace( trailingslashit( $this->get_root() ), '', self::conform_dir( $file->getPathname() ) ); 558 | 559 | // File manifest is full 560 | if ( ! empty( $current_file ) && $current_batch >= $this->file_manifest_per_batch ) { 561 | 562 | @fclose( $current_file ); 563 | $current_file = false; 564 | 565 | } 566 | 567 | // Create a new file manifest 568 | if ( empty( $current_file ) ) { 569 | 570 | $file_manifest_file_count++; 571 | $file_manifest_filename = str_pad( $file_manifest_file_count, 10, "0", STR_PAD_LEFT ); 572 | if ( ! $current_file = @fopen( $this->get_file_manifest_dirpath() . '/' . $file_manifest_filename . '.txt', 'w' ) ) 573 | return false; 574 | 575 | $current_batch = 0; 576 | } 577 | 578 | // Write the line to the file manifest if it isn't empty for some reason 579 | if ( ! empty( $line ) ) { 580 | @fwrite( $current_file, $line . PHP_EOL ); 581 | unset( $line ); 582 | $this->file_manifest_remaining++; 583 | $current_batch++; 584 | } 585 | 586 | } 587 | 588 | @file_put_contents( $this->get_path() . '/.file-manifest-remaining', $this->file_manifest_remaining ); 589 | 590 | return true; 591 | } 592 | 593 | /** 594 | * Delete the current file manifest 595 | * 596 | * @access private 597 | */ 598 | private function delete_current_file_manifest() { 599 | 600 | if ( ! file_exists( $this->current_file_manifest ) ) 601 | return false; 602 | 603 | // Remove the file manifest because it's already been archived 604 | unlink( $this->current_file_manifest ); 605 | 606 | // Update the count of remaining files. 607 | $this->file_manifest_remaining = $this->file_manifest_remaining - count( $this->file_manifest_already_archived ); 608 | if ( $this->file_manifest_remaining < 0 ) 609 | $this->file_manifest_remaining = 0; 610 | file_put_contents( $this->get_path() . '/.file-manifest-remaining', $this->file_manifest_remaining ); 611 | 612 | $this->file_manifest_already_archived = array(); 613 | 614 | } 615 | 616 | 617 | /** 618 | * Get the path to the file manifest directory 619 | * 620 | * @access private 621 | */ 622 | protected function get_file_manifest_dirpath() { 623 | return $this->get_path() . '/.file-manifests'; 624 | } 625 | 626 | /** 627 | * Get batch of files to archive from the file manifest 628 | * Ignore any files that already have been archived 629 | * 630 | * @access private 631 | */ 632 | private function get_next_files_from_file_manifest() { 633 | 634 | if ( ! is_dir( $this->get_file_manifest_dirpath() ) ) 635 | return array(); 636 | 637 | $files = glob( $this->get_file_manifest_dirpath() . '/*.txt' ); 638 | if ( empty( $files ) ) 639 | return array(); 640 | 641 | $this->current_file_manifest = array_shift( $files ); 642 | 643 | $files = file_get_contents( $this->current_file_manifest ); 644 | $files = array_map( 'trim', explode( PHP_EOL, $files ) ); 645 | if ( empty( $files ) ) 646 | return array(); 647 | 648 | $this->file_manifest_remaining = (int)file_get_contents( $this->get_path() . '/.file-manifest-remaining' ); 649 | 650 | return $files; 651 | } 652 | 653 | /** 654 | * Get the backup type 655 | * 656 | * Defaults to complete 657 | * 658 | * @access public 659 | */ 660 | public function get_type() { 661 | 662 | if ( empty( $this->type ) ) 663 | $this->set_type( 'complete' ); 664 | 665 | return $this->type; 666 | 667 | } 668 | 669 | /** 670 | * Set the backup type 671 | * 672 | * $type must be one of complete, database or file 673 | * @param string $type 674 | * @throws Exception 675 | */ 676 | public function set_type( $type ) { 677 | 678 | if ( ! is_string( $type ) || ! in_array( $type, array( 'file', 'database', 'complete' ) ) ) 679 | throw new Exception( sprintf( __( 'Invalid backup type %s must be one of (string) file, database or complete', 'wpremote' ), '' . $type . '' ) ); 680 | 681 | $this->type = $type; 682 | 683 | } 684 | 685 | /** 686 | * Get the path to the mysqldump bin 687 | * 688 | * If not explicitly set will attempt to work 689 | * it out by checking common locations 690 | * 691 | * @access public 692 | * @return string 693 | */ 694 | public function get_mysqldump_command_path() { 695 | 696 | // Check shell_exec is available 697 | if ( ! self::is_shell_exec_available() ) 698 | return ''; 699 | 700 | // Return now if it's already been set 701 | if ( isset( $this->mysqldump_command_path ) ) 702 | return $this->mysqldump_command_path; 703 | 704 | $this->mysqldump_command_path = ''; 705 | 706 | // Does mysqldump work 707 | if ( is_null( shell_exec( 'hash mysqldump 2>&1' ) ) ) { 708 | 709 | // If so store it for later 710 | $this->set_mysqldump_command_path( 'mysqldump' ); 711 | 712 | // And return now 713 | return $this->mysqldump_command_path; 714 | 715 | } 716 | 717 | // List of possible mysqldump locations 718 | $mysqldump_locations = array( 719 | '/usr/local/bin/mysqldump', 720 | '/usr/local/mysql/bin/mysqldump', 721 | '/usr/mysql/bin/mysqldump', 722 | '/usr/bin/mysqldump', 723 | '/opt/local/lib/mysql6/bin/mysqldump', 724 | '/opt/local/lib/mysql5/bin/mysqldump', 725 | '/opt/local/lib/mysql4/bin/mysqldump', 726 | '/xampp/mysql/bin/mysqldump', 727 | '/Program Files/xampp/mysql/bin/mysqldump', 728 | '/Program Files/MySQL/MySQL Server 6.0/bin/mysqldump', 729 | '/Program Files/MySQL/MySQL Server 5.5/bin/mysqldump', 730 | '/Program Files/MySQL/MySQL Server 5.4/bin/mysqldump', 731 | '/Program Files/MySQL/MySQL Server 5.1/bin/mysqldump', 732 | '/Program Files/MySQL/MySQL Server 5.0/bin/mysqldump', 733 | '/Program Files/MySQL/MySQL Server 4.1/bin/mysqldump', 734 | '/opt/local/bin/mysqldump', 735 | ); 736 | 737 | // Find the one which works 738 | foreach ( $mysqldump_locations as $location ) 739 | if ( @is_executable( self::conform_dir( $location ) ) ) 740 | $this->set_mysqldump_command_path( $location ); 741 | 742 | return $this->mysqldump_command_path; 743 | 744 | } 745 | 746 | /** 747 | * Set the path to the mysqldump bin 748 | * 749 | * Setting the path to false will cause the database 750 | * dump to use the php fallback 751 | * 752 | * @access public 753 | * @param mixed $path 754 | */ 755 | public function set_mysqldump_command_path( $path ) { 756 | 757 | $this->mysqldump_command_path = $path; 758 | 759 | } 760 | 761 | /** 762 | * Get the path to the zip bin 763 | * 764 | * If not explicitly set will attempt to work 765 | * it out by checking common locations 766 | * 767 | * @access public 768 | * @return string 769 | */ 770 | public function get_zip_command_path() { 771 | 772 | // Check shell_exec is available 773 | if ( ! self::is_shell_exec_available() ) 774 | return ''; 775 | 776 | // Return now if it's already been set 777 | if ( isset( $this->zip_command_path ) ) 778 | return $this->zip_command_path; 779 | 780 | $this->zip_command_path = ''; 781 | 782 | // Does zip work 783 | if ( is_null( shell_exec( 'hash zip 2>&1' ) ) ) { 784 | 785 | // If so store it for later 786 | $this->set_zip_command_path( 'zip' ); 787 | 788 | // And return now 789 | return $this->zip_command_path; 790 | 791 | } 792 | 793 | // List of possible zip locations 794 | $zip_locations = array( 795 | '/usr/bin/zip', 796 | '/opt/local/bin/zip', 797 | ); 798 | 799 | // Find the one which works 800 | foreach ( $zip_locations as $location ) 801 | if ( @is_executable( self::conform_dir( $location ) ) ) 802 | $this->set_zip_command_path( $location ); 803 | 804 | return $this->zip_command_path; 805 | 806 | } 807 | 808 | /** 809 | * Set the path to the zip bin 810 | * 811 | * Setting the path to false will cause the database 812 | * dump to use the php fallback 813 | * 814 | * @access public 815 | * @param mixed $path 816 | */ 817 | public function set_zip_command_path( $path ) { 818 | 819 | $this->zip_command_path = $path; 820 | 821 | } 822 | 823 | /** 824 | * Set up the ZipArchive instance if ZipArchive is available 825 | */ 826 | protected function &setup_ziparchive() { 827 | 828 | // Instance is already open 829 | if ( ! empty( $this->ziparchive ) ) { 830 | $this->ziparchive->open( $this->get_archive_filepath(), ZIPARCHIVE::CREATE ); 831 | return $this->ziparchive; 832 | } 833 | 834 | $ziparchive = new ZipArchive; 835 | 836 | // Try opening ZipArchive 837 | if ( ! file_exists( $this->get_archive_filepath() ) ) 838 | $ret = $ziparchive->open( $this->get_archive_filepath(), ZIPARCHIVE::CREATE ); 839 | else 840 | $ret = $ziparchive->open( $this->get_archive_filepath() ); 841 | 842 | // File couldn't be opened 843 | if ( ! $ret ) 844 | return false; 845 | 846 | // Try closing ZipArchive 847 | $ret = $ziparchive->close(); 848 | 849 | // File couldn't be closed 850 | if ( ! $ret ) 851 | return false; 852 | 853 | // Open it once more 854 | if ( ! file_exists( $this->get_archive_filepath() ) ) 855 | $ziparchive->open( $this->get_archive_filepath(), ZIPARCHIVE::CREATE ); 856 | else 857 | $ziparchive->open( $this->get_archive_filepath() ); 858 | 859 | $this->ziparchive = $ziparchive; 860 | return $this->ziparchive; 861 | } 862 | 863 | /** 864 | * Set up the PclZip instance 865 | * 866 | * @access protected 867 | */ 868 | protected function &setup_pclzip() { 869 | 870 | if ( empty( $this->pclzip ) ) { 871 | $this->load_pclzip(); 872 | $this->pclzip = new PclZip( $this->get_archive_filepath() ); 873 | } 874 | return $this->pclzip; 875 | } 876 | 877 | protected function do_action( $action ) { 878 | 879 | do_action( $action, $this ); 880 | 881 | } 882 | 883 | /** 884 | * Kick off a backup 885 | * 886 | * @access public 887 | * @return bool 888 | */ 889 | public function backup() { 890 | 891 | $this->do_action( 'hmbkp_backup_started' ); 892 | 893 | // Backup database 894 | if ( $this->get_type() !== 'file' ) 895 | $this->dump_database(); 896 | 897 | // Zip everything up 898 | $this->archive(); 899 | 900 | $this->do_action( 'hmbkp_backup_complete' ); 901 | 902 | } 903 | 904 | /** 905 | * Create the mysql backup 906 | * 907 | * Uses mysqldump if available, falls back to PHP 908 | * if not. 909 | * 910 | * @access public 911 | */ 912 | public function dump_database() { 913 | 914 | if ( $this->get_mysqldump_command_path() ) 915 | $this->mysqldump(); 916 | 917 | if ( empty( $this->mysqldump_verified ) ) 918 | $this->mysqldump_fallback(); 919 | 920 | $this->do_action( 'hmbkp_mysqldump_finished' ); 921 | 922 | } 923 | 924 | public function mysqldump() { 925 | 926 | $this->mysqldump_method = 'mysqldump'; 927 | 928 | $this->do_action( 'hmbkp_mysqldump_started' ); 929 | 930 | $host = explode( ':', DB_HOST ); 931 | 932 | $host = reset( $host ); 933 | $port = strpos( DB_HOST, ':' ) ? end( explode( ':', DB_HOST ) ) : ''; 934 | 935 | // Path to the mysqldump executable 936 | $cmd = escapeshellarg( $this->get_mysqldump_command_path() ); 937 | 938 | // We don't want to create a new DB 939 | $cmd .= ' --no-create-db'; 940 | 941 | // Allow lock-tables to be overridden 942 | if ( ! defined( 'HMBKP_MYSQLDUMP_SINGLE_TRANSACTION' ) || HMBKP_MYSQLDUMP_SINGLE_TRANSACTION !== false ) 943 | $cmd .= ' --single-transaction'; 944 | 945 | // Make sure binary data is exported properly 946 | $cmd .= ' --hex-blob'; 947 | 948 | // Username 949 | $cmd .= ' -u ' . escapeshellarg( DB_USER ); 950 | 951 | // Don't pass the password if it's blank 952 | if ( DB_PASSWORD ) 953 | $cmd .= ' -p' . escapeshellarg( DB_PASSWORD ); 954 | 955 | // Set the host 956 | $cmd .= ' -h ' . escapeshellarg( $host ); 957 | 958 | // Set the port if it was set 959 | if ( ! empty( $port ) && is_numeric( $port ) ) 960 | $cmd .= ' -P ' . $port; 961 | 962 | // The file we're saving too 963 | $cmd .= ' -r ' . escapeshellarg( $this->get_database_dump_filepath() ); 964 | 965 | // The database we're dumping 966 | $cmd .= ' ' . escapeshellarg( DB_NAME ); 967 | 968 | // Pipe STDERR to STDOUT 969 | $cmd .= ' 2>&1'; 970 | 971 | // Store any returned data in an error 972 | $stderr = shell_exec( $cmd ); 973 | 974 | // Skip the new password warning that is output in mysql > 5.6 (@see http://bugs.mysql.com/bug.php?id=66546) 975 | if ( trim( $stderr ) === 'Warning: Using a password on the command line interface can be insecure.' ) { 976 | $stderr = ''; 977 | } 978 | 979 | if ( $stderr ) { 980 | $this->error( $this->get_mysqldump_method(), $stderr ); 981 | } 982 | 983 | $this->verify_mysqldump(); 984 | 985 | } 986 | 987 | /** 988 | * PHP mysqldump fallback functions, exports the database to a .sql file 989 | * 990 | * @access public 991 | */ 992 | public function mysqldump_fallback() { 993 | 994 | $this->errors_to_warnings( $this->get_mysqldump_method() ); 995 | 996 | $this->mysqldump_method = 'mysqldump_fallback'; 997 | 998 | $this->do_action( 'hmbkp_mysqldump_started' ); 999 | 1000 | $this->db = @mysqli_connect( 'p:'.DB_HOST, DB_USER, DB_PASSWORD, DB_NAME ); 1001 | 1002 | if ( ! $this->db ) 1003 | $this->db = mysqli_connect( DB_HOST, DB_USER, DB_PASSWORD, DB_NAME ); 1004 | 1005 | if ( ! $this->db ) 1006 | return; 1007 | 1008 | // mysql_select_db( DB_NAME, $this->db ); 1009 | 1010 | if ( function_exists( 'mysqli_set_charset') ) 1011 | mysqli_set_charset( $this->db, DB_CHARSET ); 1012 | 1013 | // Begin new backup of MySql 1014 | $tables = mysqli_query( $this->db, 'SHOW TABLES' ); 1015 | 1016 | $sql_file = "# WordPress : " . get_bloginfo( 'url' ) . " MySQL database backup\n"; 1017 | $sql_file .= "#\n"; 1018 | $sql_file .= "# Generated: " . date( 'l j. F Y H:i T' ) . "\n"; 1019 | $sql_file .= "# Hostname: " . DB_HOST . "\n"; 1020 | $sql_file .= "# Database: " . $this->sql_backquote( DB_NAME ) . "\n"; 1021 | $sql_file .= "# --------------------------------------------------------\n"; 1022 | 1023 | while ( $row = mysqli_fetch_array($tables) ) { 1024 | $curr_table = $row[0]; 1025 | 1026 | // Create the SQL statements 1027 | $sql_file .= "# --------------------------------------------------------\n"; 1028 | $sql_file .= "# Table: " . $this->sql_backquote( $curr_table ) . "\n"; 1029 | $sql_file .= "# --------------------------------------------------------\n"; 1030 | 1031 | $this->make_sql( $sql_file, $curr_table ); 1032 | 1033 | } 1034 | 1035 | } 1036 | 1037 | /** 1038 | * Zip up all the files. 1039 | * 1040 | * Attempts to use the shell zip command, if 1041 | * thats not available then it falls back to 1042 | * PHP ZipArchive and finally PclZip. 1043 | * 1044 | * @access public 1045 | */ 1046 | public function archive() { 1047 | 1048 | // If using a manifest, perform the backup in chunks 1049 | if ( 'database' !== $this->get_type() 1050 | && $this->is_using_file_manifest() 1051 | && $this->create_file_manifests() ) { 1052 | 1053 | $this->archive_via_file_manifest(); 1054 | 1055 | } else { 1056 | 1057 | $this->archive_via_single_request(); 1058 | 1059 | } 1060 | 1061 | } 1062 | 1063 | /** 1064 | * Archive with a file manifest 1065 | * 1066 | * @access private 1067 | */ 1068 | private function archive_via_file_manifest() { 1069 | 1070 | $errors = array(); 1071 | 1072 | // Back up files from the file manifest in chunks 1073 | $next_files = $this->get_next_files_from_file_manifest(); 1074 | do { 1075 | 1076 | $this->do_action( 'hmbkp_archive_started' ); 1077 | 1078 | // `zip` is the most performant archive method 1079 | if ( $this->get_zip_command_path() ) { 1080 | $this->archive_method = 'zip_files'; 1081 | $error = $this->zip_files( $next_files ); 1082 | } 1083 | 1084 | // ZipArchive is also pretty fast for chunked backups 1085 | else if ( class_exists( 'ZipArchive' ) && empty( $this->skip_zip_archive ) ) { 1086 | $this->archive_method = 'zip_archive_files'; 1087 | 1088 | $ret = $this->zip_archive_files( $next_files ); 1089 | if ( ! $ret ) { 1090 | $this->skip_zip_archive = true; 1091 | continue; 1092 | } 1093 | } 1094 | 1095 | // Last opportunity 1096 | else { 1097 | $this->archive_method = 'pcl_zip_files'; 1098 | $error = $this->pcl_zip_files( $next_files ); 1099 | } 1100 | 1101 | if ( ! empty( $error ) ) { 1102 | $errors[] = $error; 1103 | unset( $error ); 1104 | } 1105 | 1106 | // Update the file manifest with these files that were archived 1107 | $this->file_manifest_already_archived = array_merge( $this->file_manifest_already_archived, $next_files ); 1108 | $this->delete_current_file_manifest(); 1109 | 1110 | // Get the next set of files to archive 1111 | $next_files = $this->get_next_files_from_file_manifest(); 1112 | 1113 | } while( ! empty( $next_files ) ); 1114 | 1115 | // If the database should be included in the backup, it's included last 1116 | if ( 'file' !== $this->get_type() && file_exists( $this->get_database_dump_filepath() ) ) { 1117 | 1118 | switch ( $this->archive_method ) { 1119 | 1120 | case 'zip_archive_files': 1121 | 1122 | $zip = &$this->setup_ziparchive(); 1123 | 1124 | $zip->addFile( $this->get_database_dump_filepath(), $this->get_database_dump_filename() ); 1125 | 1126 | $zip->close(); 1127 | 1128 | break; 1129 | 1130 | case 'zip_files': 1131 | 1132 | $error = shell_exec( 'cd ' . escapeshellarg( $this->get_path() ) . ' && ' . escapeshellcmd( $this->get_zip_command_path() ) . ' -uq ' . escapeshellarg( $this->get_archive_filepath() ) . ' ' . escapeshellarg( $this->get_database_dump_filename() ) . ' 2>&1' ); 1133 | 1134 | break; 1135 | 1136 | case 'pcl_zip_files': 1137 | 1138 | $pclzip = &$this->setup_pclzip(); 1139 | 1140 | if ( ! $pclzip->add( $this->get_database_dump_filepath(), PCLZIP_OPT_REMOVE_PATH, $this->get_path() ) ) 1141 | $this->warning( $this->get_archive_method(), $pclzip->errorInfo( true ) ); 1142 | 1143 | break; 1144 | } 1145 | 1146 | if ( ! empty( $error ) ) { 1147 | $errors[] = $error; 1148 | unset( $error ); 1149 | } 1150 | } 1151 | 1152 | // If the methods produced any errors, log them 1153 | if ( ! empty( $errors ) ) 1154 | $this->warning( $this->get_archive_method(), implode( ', ', $errors ) ); 1155 | 1156 | // ZipArchive has some special reporting requirements 1157 | if ( ! empty( $this->ziparchive ) ) { 1158 | 1159 | if ( $this->ziparchive->status ) 1160 | $this->warning( $this->get_archive_method(), $this->ziparchive->status ); 1161 | 1162 | if ( $this->ziparchive->statusSys ) 1163 | $this->warning( $this->get_archive_method(), $this->ziparchive->statusSys ); 1164 | 1165 | } 1166 | 1167 | // Verify and remove if errors 1168 | $this->verify_archive(); 1169 | 1170 | // Remove the file manifest 1171 | if ( is_dir( $this->get_file_manifest_dirpath() ) ) 1172 | $this->rmdir_recursive( $this->get_file_manifest_dirpath() ); 1173 | 1174 | // Delete the database dump file 1175 | if ( file_exists( $this->get_database_dump_filepath() ) ) 1176 | unlink( $this->get_database_dump_filepath() ); 1177 | 1178 | $this->do_action( 'hmbkp_archive_finished' ); 1179 | 1180 | } 1181 | 1182 | /** 1183 | * Archive using our traditional method of one request 1184 | * 1185 | * @access private 1186 | */ 1187 | private function archive_via_single_request() { 1188 | 1189 | // Do we have the path to the zip command 1190 | if ( $this->get_zip_command_path() ) 1191 | $this->zip(); 1192 | 1193 | // If not or if the shell zip failed then use ZipArchive 1194 | if ( empty( $this->archive_verified ) && class_exists( 'ZipArchive' ) && empty( $this->skip_zip_archive ) ) 1195 | $this->zip_archive(); 1196 | 1197 | // If ZipArchive is unavailable or one of the above failed 1198 | if ( empty( $this->archive_verified ) ) 1199 | $this->pcl_zip(); 1200 | 1201 | // Delete the database dump file 1202 | if ( file_exists( $this->get_database_dump_filepath() ) ) 1203 | unlink( $this->get_database_dump_filepath() ); 1204 | 1205 | $this->do_action( 'hmbkp_archive_finished' ); 1206 | 1207 | } 1208 | 1209 | /** 1210 | * Restart a failed archive process 1211 | * 1212 | * @access public 1213 | */ 1214 | public function restart_archive() { 1215 | 1216 | if ( $this->is_using_file_manifest() ) { 1217 | 1218 | $this->archive_via_file_manifest(); 1219 | 1220 | } else { 1221 | 1222 | $this->archive_via_single_request(); 1223 | 1224 | } 1225 | 1226 | $this->do_action( 'hmbkp_backup_complete' ); 1227 | } 1228 | 1229 | /** 1230 | * Zip using the native zip command 1231 | * 1232 | * @access public 1233 | */ 1234 | public function zip() { 1235 | 1236 | $this->archive_method = 'zip'; 1237 | 1238 | $this->do_action( 'hmbkp_archive_started' ); 1239 | 1240 | // Zip up $this->root with excludes 1241 | if ( $this->get_type() !== 'database' && $this->exclude_string( 'zip' ) ) { 1242 | $stderr = shell_exec( 'cd ' . escapeshellarg( $this->get_root() ) . ' && ' . escapeshellcmd( $this->get_zip_command_path() ) . ' -rq ' . escapeshellarg( $this->get_archive_filepath() ) . ' ./' . ' -x ' . $this->exclude_string( 'zip' ) . ' 2>&1' ); 1243 | 1244 | // Zip up $this->root without excludes 1245 | } elseif ( $this->get_type() !== 'database' ) { 1246 | $stderr = shell_exec( 'cd ' . escapeshellarg( $this->get_root() ) . ' && ' . escapeshellcmd( $this->get_zip_command_path() ) . ' -rq ' . escapeshellarg( $this->get_archive_filepath() ) . ' ./' . ' 2>&1' ); 1247 | 1248 | } 1249 | 1250 | // Add the database dump to the archive 1251 | if ( $this->get_type() !== 'file' && file_exists( $this->get_database_dump_filepath() ) ) 1252 | $stderr = shell_exec( 'cd ' . escapeshellarg( $this->get_path() ) . ' && ' . escapeshellcmd( $this->get_zip_command_path() ) . ' -uq ' . escapeshellarg( $this->get_archive_filepath() ) . ' ' . escapeshellarg( $this->get_database_dump_filename() ) . ' 2>&1' ); 1253 | 1254 | if ( ! empty( $stderr ) ) 1255 | $this->warning( $this->get_archive_method(), $stderr ); 1256 | 1257 | $this->verify_archive(); 1258 | } 1259 | 1260 | /** 1261 | * Zip one or more files 1262 | * 1263 | * @access private 1264 | */ 1265 | private function zip_files( $files ) { 1266 | 1267 | // Not necessary to include directories when using `zip` 1268 | foreach( $files as $key => $file ) { 1269 | 1270 | if ( ! is_dir( $file ) ) 1271 | continue; 1272 | 1273 | unset( $files[$key] ); 1274 | } 1275 | 1276 | return shell_exec( 'cd ' . escapeshellarg( $this->get_root() ) . ' && ' . escapeshellcmd( $this->get_zip_command_path() ) . ' ' . escapeshellarg( $this->get_archive_filepath() ) . ' ' . implode( ' ', $files ) . ' -q 2>&1' ); 1277 | } 1278 | 1279 | /** 1280 | * Fallback for creating zip archives if zip command is 1281 | * unavailable. 1282 | */ 1283 | public function zip_archive() { 1284 | 1285 | $this->errors_to_warnings( $this->get_archive_method() ); 1286 | $this->archive_method = 'ziparchive'; 1287 | 1288 | $this->do_action( 'hmbkp_archive_started' ); 1289 | 1290 | if ( false === ( $zip = &$this->setup_ziparchive() ) ) 1291 | return; 1292 | 1293 | $excludes = $this->exclude_string( 'regex' ); 1294 | 1295 | if ( $this->get_type() !== 'database' ) { 1296 | 1297 | $files_added = 0; 1298 | 1299 | foreach ( $this->get_files() as $file ) { 1300 | 1301 | // Skip dot files, they should only exist on versions of PHP between 5.2.11 -> 5.3 1302 | if ( method_exists( $file, 'isDot' ) && $file->isDot() ) 1303 | continue; 1304 | 1305 | // Skip unreadable files 1306 | if ( ! @realpath( $file->getPathname() ) || ! $file->isReadable() ) 1307 | continue; 1308 | 1309 | // Excludes 1310 | if ( $excludes && preg_match( '(' . $excludes . ')', str_ireplace( trailingslashit( $this->get_root() ), '', self::conform_dir( $file->getPathname() ) ) ) ) 1311 | continue; 1312 | 1313 | if ( $file->isDir() ) 1314 | $zip->addEmptyDir( trailingslashit( str_ireplace( trailingslashit( $this->get_root() ), '', self::conform_dir( $file->getPathname() ) ) ) ); 1315 | 1316 | elseif ( $file->isFile() ) 1317 | $zip->addFile( $file->getPathname(), str_ireplace( trailingslashit( $this->get_root() ), '', self::conform_dir( $file->getPathname() ) ) ); 1318 | 1319 | if ( ++$files_added % 500 === 0 ) 1320 | if ( ! $zip->close() || ! $zip->open( $this->get_archive_filepath(), ZIPARCHIVE::CREATE ) ) 1321 | return; 1322 | 1323 | } 1324 | 1325 | } 1326 | 1327 | // Add the database 1328 | if ( $this->get_type() !== 'file' && file_exists( $this->get_database_dump_filepath() ) ) 1329 | $zip->addFile( $this->get_database_dump_filepath(), $this->get_database_dump_filename() ); 1330 | 1331 | if ( $zip->status ) 1332 | $this->warning( $this->get_archive_method(), $zip->status ); 1333 | 1334 | if ( $zip->statusSys ) 1335 | $this->warning( $this->get_archive_method(), $zip->statusSys ); 1336 | 1337 | $zip->close(); 1338 | 1339 | $this->verify_archive(); 1340 | 1341 | } 1342 | 1343 | /** 1344 | * Zip Archive one or more files 1345 | * 1346 | * @access private 1347 | */ 1348 | private function zip_archive_files( $files ) { 1349 | 1350 | if ( false === ( $zip = &$this->setup_ziparchive() ) ) 1351 | return false; 1352 | 1353 | foreach( $files as $file ) { 1354 | 1355 | $full_path = trailingslashit( $this->get_root() ) . $file; 1356 | if ( is_dir( $full_path ) ) 1357 | $zip->addEmptyDir( $file ); 1358 | else 1359 | $zip->addFile( $full_path, $file ); 1360 | 1361 | } 1362 | 1363 | // Avoid limitations in ZipArchive by making sure we save each batch to disk 1364 | $zip->close(); 1365 | return true; 1366 | } 1367 | 1368 | /** 1369 | * Fallback for creating zip archives if zip command and ZipArchive are 1370 | * unavailable. 1371 | * 1372 | * Uses the PclZip library that ships with WordPress 1373 | */ 1374 | public function pcl_zip() { 1375 | 1376 | $this->errors_to_warnings( $this->get_archive_method() ); 1377 | $this->archive_method = 'pclzip'; 1378 | 1379 | $this->do_action( 'hmbkp_archive_started' ); 1380 | 1381 | global $_wprp_hmbkp_exclude_string; 1382 | 1383 | $_wprp_hmbkp_exclude_string = $this->exclude_string( 'regex' ); 1384 | 1385 | $archive = &$this->setup_pclzip(); 1386 | 1387 | // Zip up everything 1388 | if ( $this->get_type() !== 'database' ) 1389 | if ( ! $archive->add( $this->get_root(), PCLZIP_OPT_REMOVE_PATH, $this->get_root(), PCLZIP_CB_PRE_ADD, 'wprp_hmbkp_pclzip_callback' ) ) 1390 | $this->warning( $this->get_archive_method(), $archive->errorInfo( true ) ); 1391 | 1392 | // Add the database 1393 | if ( $this->get_type() !== 'file' && file_exists( $this->get_database_dump_filepath() ) ) 1394 | if ( ! $archive->add( $this->get_database_dump_filepath(), PCLZIP_OPT_REMOVE_PATH, $this->get_path() ) ) 1395 | $this->warning( $this->get_archive_method(), $archive->errorInfo( true ) ); 1396 | 1397 | unset( $GLOBALS['_wprp_hmbkp_exclude_string'] ); 1398 | 1399 | $this->verify_archive(); 1400 | 1401 | } 1402 | 1403 | /** 1404 | * Use PclZip to archive batches of files 1405 | */ 1406 | private function pcl_zip_files( $files ) { 1407 | 1408 | $this->errors_to_warnings( $this->get_archive_method() ); 1409 | 1410 | $pclzip = &$this->setup_pclzip(); 1411 | 1412 | foreach( $files as $file ) { 1413 | 1414 | $full_path = trailingslashit( $this->get_root() ) . $file; 1415 | if ( is_dir( $full_path ) ) 1416 | continue; 1417 | 1418 | if ( ! $pclzip->add( $full_path, PCLZIP_OPT_REMOVE_PATH, $this->get_root() ) ) 1419 | $this->warning( $this->get_archive_method(), $pclzip->errorInfo( true ) ); 1420 | 1421 | } 1422 | 1423 | } 1424 | 1425 | public function verify_mysqldump() { 1426 | 1427 | $this->do_action( 'hmbkp_mysqldump_verify_started' ); 1428 | 1429 | // If we've already passed then no need to check again 1430 | if ( ! empty( $this->mysqldump_verified ) ) 1431 | return true; 1432 | 1433 | // If there are mysqldump errors delete the database dump file as mysqldump will still have written one 1434 | if ( $this->get_errors( $this->get_mysqldump_method() ) && file_exists( $this->get_database_dump_filepath() ) ) 1435 | unlink( $this->get_database_dump_filepath() ); 1436 | 1437 | // If we have an empty file delete it 1438 | if ( @filesize( $this->get_database_dump_filepath() ) === 0 ) 1439 | unlink( $this->get_database_dump_filepath() ); 1440 | 1441 | // If the file still exists then it must be good 1442 | if ( file_exists( $this->get_database_dump_filepath() ) ) 1443 | return $this->mysqldump_verified = true; 1444 | 1445 | return false; 1446 | 1447 | 1448 | } 1449 | 1450 | /** 1451 | * Verify that the archive is valid and contains all the files it should contain. 1452 | * 1453 | * @access public 1454 | * @return bool 1455 | */ 1456 | public function verify_archive() { 1457 | 1458 | $this->do_action( 'hmbkp_archive_verify_started' ); 1459 | 1460 | // If we've already passed then no need to check again 1461 | if ( ! empty( $this->archive_verified ) ) 1462 | return true; 1463 | 1464 | // If there are errors delete the backup file. 1465 | if ( $this->get_errors( $this->get_archive_method() ) && file_exists( $this->get_archive_filepath() ) ) 1466 | unlink( $this->get_archive_filepath() ); 1467 | 1468 | // If the archive file still exists assume it's good 1469 | if ( file_exists( $this->get_archive_filepath() ) ) 1470 | return $this->archive_verified = true; 1471 | 1472 | return false; 1473 | 1474 | } 1475 | 1476 | /** 1477 | * Return an array of all files in the filesystem 1478 | * 1479 | * @access public 1480 | * @return array 1481 | */ 1482 | public function get_files() { 1483 | 1484 | if ( ! empty( $this->files ) ) 1485 | return $this->files; 1486 | 1487 | $this->files = array(); 1488 | 1489 | // We only want to use the RecursiveDirectoryIterator if the FOLLOW_SYMLINKS flag is available 1490 | if ( defined( 'RecursiveDirectoryIterator::FOLLOW_SYMLINKS' ) ) { 1491 | 1492 | $this->files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $this->get_root(), RecursiveDirectoryIterator::FOLLOW_SYMLINKS ), RecursiveIteratorIterator::SELF_FIRST, RecursiveIteratorIterator::CATCH_GET_CHILD ); 1493 | 1494 | // Skip dot files if the SKIP_Dots flag is available 1495 | if ( defined( 'RecursiveDirectoryIterator::SKIP_DOTS' ) ) 1496 | $this->files->setFlags( RecursiveDirectoryIterator::SKIP_DOTS + RecursiveDirectoryIterator::FOLLOW_SYMLINKS ); 1497 | 1498 | 1499 | // If RecursiveDirectoryIterator::FOLLOW_SYMLINKS isn't available then fallback to a less memory efficient method 1500 | } else { 1501 | 1502 | $this->files = $this->get_files_fallback( $this->get_root() ); 1503 | 1504 | } 1505 | 1506 | return $this->files; 1507 | 1508 | } 1509 | 1510 | /** 1511 | * Fallback function for generating a filesystem 1512 | * array 1513 | * 1514 | * Used if RecursiveDirectoryIterator::FOLLOW_SYMLINKS isn't available 1515 | * 1516 | * @access private 1517 | * @param string $dir 1518 | * @param array $files. (default: array()) 1519 | * @return array 1520 | */ 1521 | private function get_files_fallback( $dir, $files = array() ) { 1522 | 1523 | $handle = opendir( $dir ); 1524 | 1525 | $excludes = $this->exclude_string( 'regex' ); 1526 | 1527 | while ( $file = readdir( $handle ) ) : 1528 | 1529 | // Ignore current dir and containing dir 1530 | if ( $file === '.' || $file === '..' ) 1531 | continue; 1532 | 1533 | $filepath = self::conform_dir( trailingslashit( $dir ) . $file ); 1534 | $file = str_ireplace( trailingslashit( $this->get_root() ), '', $filepath ); 1535 | 1536 | $files[] = new SplFileInfo( $filepath ); 1537 | 1538 | if ( is_dir( $filepath ) ) 1539 | $files = $this->get_files_fallback( $filepath, $files ); 1540 | 1541 | endwhile; 1542 | 1543 | return $files; 1544 | 1545 | } 1546 | 1547 | /** 1548 | * Returns an array of files that will be included in the backup. 1549 | * 1550 | * @access public 1551 | * @return array 1552 | */ 1553 | public function get_included_files() { 1554 | 1555 | if ( ! empty( $this->included_files ) ) 1556 | return $this->included_files; 1557 | 1558 | $this->included_files = array(); 1559 | 1560 | $excludes = $this->exclude_string( 'regex' ); 1561 | 1562 | foreach ( $this->get_files() as $file ) { 1563 | 1564 | // Skip dot files, they should only exist on versions of PHP between 5.2.11 -> 5.3 1565 | if ( method_exists( $file, 'isDot' ) && $file->isDot() ) 1566 | continue; 1567 | 1568 | // Skip unreadable files 1569 | if ( ! @realpath( $file->getPathname() ) || ! $file->isReadable() ) 1570 | continue; 1571 | 1572 | // Excludes 1573 | if ( $excludes && preg_match( '(' . $excludes . ')', str_ireplace( trailingslashit( $this->get_root() ), '', self::conform_dir( $file->getPathname() ) ) ) ) 1574 | continue; 1575 | 1576 | $this->included_files[] = $file; 1577 | 1578 | } 1579 | 1580 | return $this->included_files; 1581 | 1582 | } 1583 | 1584 | /** 1585 | * Return the number of files included in the backup 1586 | * 1587 | * @access public 1588 | * @return array 1589 | */ 1590 | public function get_included_file_count() { 1591 | 1592 | if ( ! empty( $this->included_file_count ) ) 1593 | return $this->included_file_count; 1594 | 1595 | $this->included_file_count = 0; 1596 | 1597 | $excludes = $this->exclude_string( 'regex' ); 1598 | 1599 | foreach ( $this->get_files() as $file ) { 1600 | 1601 | // Skip dot files, they should only exist on versions of PHP between 5.2.11 -> 5.3 1602 | if ( method_exists( $file, 'isDot' ) && $file->isDot() ) 1603 | continue; 1604 | 1605 | // Skip unreadable files 1606 | if ( ! @realpath( $file->getPathname() ) || ! $file->isReadable() ) 1607 | continue; 1608 | 1609 | // Excludes 1610 | if ( $excludes && preg_match( '(' . $excludes . ')', str_ireplace( trailingslashit( $this->get_root() ), '', self::conform_dir( $file->getPathname() ) ) ) ) 1611 | continue; 1612 | 1613 | $this->included_file_count++; 1614 | 1615 | } 1616 | 1617 | return $this->included_file_count; 1618 | 1619 | } 1620 | 1621 | /** 1622 | * Returns an array of files that match the exclude rules. 1623 | * 1624 | * @access public 1625 | * @return array 1626 | */ 1627 | public function get_excluded_files() { 1628 | 1629 | if ( ! empty( $this->excluded_files ) ) 1630 | return $this->excluded_files; 1631 | 1632 | $this->excluded_files = array(); 1633 | 1634 | $excludes = $this->exclude_string( 'regex' ); 1635 | 1636 | foreach ( $this->get_files() as $file ) { 1637 | 1638 | // Skip dot files, they should only exist on versions of PHP between 5.2.11 -> 5.3 1639 | if ( method_exists( $file, 'isDot' ) && $file->isDot() ) 1640 | continue; 1641 | 1642 | // Skip unreadable files 1643 | if ( ! @realpath( $file->getPathname() ) || ! $file->isReadable() ) 1644 | continue; 1645 | 1646 | // Excludes 1647 | if ( $excludes && preg_match( '(' . $excludes . ')', str_ireplace( trailingslashit( $this->get_root() ), '', self::conform_dir( $file->getPathname() ) ) ) ) 1648 | $this->excluded_files[] = $file; 1649 | 1650 | } 1651 | 1652 | return $this->excluded_files; 1653 | 1654 | } 1655 | 1656 | /** 1657 | * Return the number of files excluded from the backup 1658 | * 1659 | * @access public 1660 | * @return array 1661 | */ 1662 | public function get_excluded_file_count() { 1663 | 1664 | if ( ! empty( $this->excluded_file_count ) ) 1665 | return $this->excluded_file_count; 1666 | 1667 | $this->excluded_file_count = 0; 1668 | 1669 | $excludes = $this->exclude_string( 'regex' ); 1670 | 1671 | foreach ( $this->get_files() as $file ) { 1672 | 1673 | // Skip dot files, they should only exist on versions of PHP between 5.2.11 -> 5.3 1674 | if ( method_exists( $file, 'isDot' ) && $file->isDot() ) 1675 | continue; 1676 | 1677 | // Skip unreadable files 1678 | if ( ! @realpath( $file->getPathname() ) || ! $file->isReadable() ) 1679 | continue; 1680 | 1681 | // Excludes 1682 | if ( $excludes && preg_match( '(' . $excludes . ')', str_ireplace( trailingslashit( $this->get_root() ), '', self::conform_dir( $file->getPathname() ) ) ) ) 1683 | $this->excluded_file_count++; 1684 | 1685 | } 1686 | 1687 | return $this->excluded_file_count; 1688 | 1689 | } 1690 | 1691 | /** 1692 | * Returns an array of unreadable files. 1693 | * 1694 | * @access public 1695 | * @return array 1696 | */ 1697 | public function get_unreadable_files() { 1698 | 1699 | if ( ! empty( $this->unreadable_files ) ) 1700 | return $this->unreadable_files; 1701 | 1702 | $this->unreadable_files = array(); 1703 | 1704 | foreach ( $this->get_files() as $file ) { 1705 | 1706 | // Skip dot files, they should only exist on versions of PHP between 5.2.11 -> 5.3 1707 | if ( method_exists( $file, 'isDot' ) && $file->isDot() ) 1708 | continue; 1709 | 1710 | if ( ! @realpath( $file->getPathname() ) || ! $file->isReadable() ) 1711 | $this->unreadable_files[] = $file; 1712 | 1713 | } 1714 | 1715 | return $this->unreadable_files; 1716 | 1717 | } 1718 | 1719 | /** 1720 | * Return the number of unreadable files. 1721 | * 1722 | * @access public 1723 | * @return array 1724 | */ 1725 | public function get_unreadable_file_count() { 1726 | 1727 | if ( ! empty( $this->get_unreadable_file_count ) ) 1728 | return $this->get_unreadable_file_count; 1729 | 1730 | $this->get_unreadable_file_count = 0; 1731 | 1732 | foreach ( $this->get_files() as $file ) { 1733 | 1734 | // Skip dot files, they should only exist on versions of PHP between 5.2.11 -> 5.3 1735 | if ( method_exists( $file, 'isDot' ) && $file->isDot() ) 1736 | continue; 1737 | 1738 | if ( ! @realpath( $file->getPathname() ) || ! $file->isReadable() ) 1739 | $this->get_unreadable_file_count++; 1740 | 1741 | } 1742 | 1743 | return $this->get_unreadable_file_count; 1744 | 1745 | } 1746 | 1747 | private function load_pclzip() { 1748 | 1749 | // Load PclZip 1750 | if ( ! defined( 'PCLZIP_TEMPORARY_DIR' ) ) 1751 | define( 'PCLZIP_TEMPORARY_DIR', trailingslashit( $this->get_path() ) ); 1752 | 1753 | require_once( ABSPATH . 'wp-admin/includes/class-pclzip.php' ); 1754 | 1755 | } 1756 | 1757 | /** 1758 | * Get an array of exclude rules 1759 | * 1760 | * The backup path is automatically excluded 1761 | * 1762 | * @access public 1763 | * @return array 1764 | */ 1765 | public function get_excludes() { 1766 | 1767 | $excludes = array(); 1768 | 1769 | if ( isset( $this->excludes ) ) 1770 | $excludes = $this->excludes; 1771 | 1772 | // If path() is inside root(), exclude it 1773 | if ( strpos( $this->get_path(), $this->get_root() ) !== false ) 1774 | array_unshift( $excludes, trailingslashit( $this->get_path() ) ); 1775 | 1776 | return array_unique( $excludes ); 1777 | 1778 | } 1779 | 1780 | /** 1781 | * Set the excludes, expects and array 1782 | * 1783 | * @access public 1784 | * @param Array $excludes 1785 | * @param Bool $append 1786 | */ 1787 | public function set_excludes( $excludes, $append = false ) { 1788 | 1789 | if ( is_string( $excludes ) ) 1790 | $excludes = explode( ',', $excludes ); 1791 | 1792 | if ( $append ) 1793 | $excludes = array_merge( $this->excludes, $excludes ); 1794 | 1795 | $this->excludes = array_filter( array_unique( array_map( 'trim', $excludes ) ) ); 1796 | 1797 | } 1798 | 1799 | /** 1800 | * Generate the exclude param string for the zip backup 1801 | * 1802 | * Takes the exclude rules and formats them for use with either 1803 | * the shell zip command or pclzip 1804 | * 1805 | * @access public 1806 | * @param string $context. (default: 'zip') 1807 | * @return string 1808 | */ 1809 | public function exclude_string( $context = 'zip' ) { 1810 | 1811 | // Return a comma separated list by default 1812 | $separator = ', '; 1813 | $wildcard = ''; 1814 | 1815 | // The zip command 1816 | if ( $context === 'zip' ) { 1817 | $wildcard = '*'; 1818 | $separator = ' -x '; 1819 | 1820 | // The PclZip fallback library 1821 | } elseif ( $context === 'regex' ) { 1822 | $wildcard = '([\s\S]*?)'; 1823 | $separator = '|'; 1824 | 1825 | } 1826 | 1827 | $excludes = $this->get_excludes(); 1828 | 1829 | foreach( $excludes as $key => &$rule ) { 1830 | 1831 | $file = $absolute = $fragment = false; 1832 | 1833 | // Files don't end with / 1834 | if ( ! in_array( substr( $rule, -1 ), array( '\\', '/' ) ) ) 1835 | $file = true; 1836 | 1837 | // If rule starts with a / then treat as absolute path 1838 | elseif ( in_array( substr( $rule, 0, 1 ), array( '\\', '/' ) ) ) 1839 | $absolute = true; 1840 | 1841 | // Otherwise treat as dir fragment 1842 | else 1843 | $fragment = true; 1844 | 1845 | // Strip $this->root and conform 1846 | $rule = str_ireplace( $this->get_root(), '', untrailingslashit( self::conform_dir( $rule ) ) ); 1847 | 1848 | // Strip the preceeding slash 1849 | if ( in_array( substr( $rule, 0, 1 ), array( '\\', '/' ) ) ) 1850 | $rule = substr( $rule, 1 ); 1851 | 1852 | // Escape string for regex 1853 | if ( $context === 'regex' ) 1854 | $rule = str_replace( '.', '\.', $rule ); 1855 | 1856 | // Convert any existing wildcards 1857 | if ( $wildcard !== '*' && strpos( $rule, '*' ) !== false ) 1858 | $rule = str_replace( '*', $wildcard, $rule ); 1859 | 1860 | // Wrap directory fragments and files in wildcards for zip 1861 | if ( $context === 'zip' && ( $fragment || $file ) ) 1862 | $rule = $wildcard . $rule . $wildcard; 1863 | 1864 | // Add a wildcard to the end of absolute url for zips 1865 | if ( $context === 'zip' && $absolute ) 1866 | $rule .= $wildcard; 1867 | 1868 | // Add and end carrot to files for pclzip but only if it doesn't end in a wildcard 1869 | if ( $file && $context === 'regex' ) 1870 | $rule .= '$'; 1871 | 1872 | // Add a start carrot to absolute urls for pclzip 1873 | if ( $absolute && $context === 'regex' ) 1874 | $rule = '^' . $rule; 1875 | 1876 | } 1877 | 1878 | // Escape shell args for zip command 1879 | if ( $context === 'zip' ) 1880 | $excludes = array_map( 'escapeshellarg', array_unique( $excludes ) ); 1881 | 1882 | return implode( $separator, $excludes ); 1883 | 1884 | } 1885 | 1886 | /** 1887 | * Add backquotes to tables and db-names in SQL queries. Taken from phpMyAdmin. 1888 | * 1889 | * @param $a_name 1890 | * @return array|string 1891 | */ 1892 | private function sql_backquote( $a_name ) { 1893 | 1894 | if ( ! empty( $a_name ) && $a_name !== '*' ) { 1895 | 1896 | if ( is_array( $a_name ) ) { 1897 | 1898 | $result = array(); 1899 | 1900 | reset( $a_name ); 1901 | 1902 | while ( list( $key, $val ) = each( $a_name ) ) 1903 | $result[$key] = '`' . $val . '`'; 1904 | 1905 | return $result; 1906 | 1907 | } else { 1908 | 1909 | return '`' . $a_name . '`'; 1910 | 1911 | } 1912 | 1913 | } else { 1914 | 1915 | return $a_name; 1916 | 1917 | } 1918 | 1919 | } 1920 | 1921 | /** 1922 | * Reads the Database table in $table and creates 1923 | * SQL Statements for recreating structure and data 1924 | * Taken partially from phpMyAdmin and partially from 1925 | * Alain Wolf, Zurich - Switzerland 1926 | * Website: http://restkultur.ch/personal/wolf/scripts/db_backup/ 1927 | * 1928 | * @access private 1929 | * @param string $sql_file 1930 | * @param string $table 1931 | */ 1932 | private function make_sql( $sql_file, $table ) { 1933 | 1934 | // Add SQL statement to drop existing table 1935 | $sql_file .= "\n"; 1936 | $sql_file .= "\n"; 1937 | $sql_file .= "#\n"; 1938 | $sql_file .= "# Delete any existing table " . $this->sql_backquote( $table ) . "\n"; 1939 | $sql_file .= "#\n"; 1940 | $sql_file .= "\n"; 1941 | $sql_file .= "DROP TABLE IF EXISTS " . $this->sql_backquote( $table ) . ";\n"; 1942 | 1943 | /* Table Structure */ 1944 | 1945 | // Comment in SQL-file 1946 | $sql_file .= "\n"; 1947 | $sql_file .= "\n"; 1948 | $sql_file .= "#\n"; 1949 | $sql_file .= "# Table structure of table " . $this->sql_backquote( $table ) . "\n"; 1950 | $sql_file .= "#\n"; 1951 | $sql_file .= "\n"; 1952 | 1953 | // Get table structure 1954 | $query = 'SHOW CREATE TABLE ' . $this->sql_backquote( $table ); 1955 | $result = mysqli_query( $this->db, $query ); 1956 | 1957 | if ( $result ) { 1958 | 1959 | if ( mysqli_num_rows( $result ) > 0 ) { 1960 | $sql_create_arr = mysqli_fetch_array( $result ); 1961 | $sql_file .= $sql_create_arr[1]; 1962 | } 1963 | 1964 | mysqli_free_result( $result ); 1965 | $sql_file .= ' ;'; 1966 | 1967 | } 1968 | 1969 | /* Table Contents */ 1970 | 1971 | // Get table contents 1972 | $query = 'SELECT * FROM ' . $this->sql_backquote( $table ); 1973 | $result = mysqli_query( $this->db, $query ); 1974 | 1975 | if ( $result ) { 1976 | $fields_cnt = mysqli_num_fields( $result ); 1977 | $rows_cnt = mysqli_num_rows( $result ); 1978 | } 1979 | 1980 | // Comment in SQL-file 1981 | $sql_file .= "\n"; 1982 | $sql_file .= "\n"; 1983 | $sql_file .= "#\n"; 1984 | $sql_file .= "# Data contents of table " . $table . " (" . $rows_cnt . " records)\n"; 1985 | $sql_file .= "#\n"; 1986 | 1987 | // Checks whether the field is an integer or not 1988 | for ( $j = 0; $j < $fields_cnt; $j++ ) { 1989 | 1990 | $field_obj = mysqli_fetch_field_direct( $result, $j ); 1991 | 1992 | $field_set[$j] = $this->sql_backquote( $field_obj->name ); 1993 | $type = $field_obj->type; 1994 | 1995 | if ( $type === 'tinyint' || $type === 'smallint' || $type === 'mediumint' || $type === 'int' || $type === 'bigint' ) 1996 | $field_num[$j] = true; 1997 | 1998 | else 1999 | $field_num[$j] = false; 2000 | 2001 | } 2002 | 2003 | // Sets the scheme 2004 | $entries = 'INSERT INTO ' . $this->sql_backquote( $table ) . ' VALUES ('; 2005 | $search = array( '\x00', '\x0a', '\x0d', '\x1a' ); //\x08\\x09, not required 2006 | $replace = array( '\0', '\n', '\r', '\Z' ); 2007 | $current_row = 0; 2008 | $batch_write = 0; 2009 | 2010 | while ( $row = mysqli_fetch_row( $result ) ) { 2011 | 2012 | $current_row++; 2013 | 2014 | // build the statement 2015 | for ( $j = 0; $j < $fields_cnt; $j++ ) { 2016 | 2017 | if ( ! isset($row[$j] ) ) { 2018 | $values[] = 'NULL'; 2019 | 2020 | } elseif ( $row[$j] === '0' || $row[$j] !== '' ) { 2021 | 2022 | // a number 2023 | if ( $field_num[$j] ) 2024 | $values[] = $row[$j]; 2025 | 2026 | else 2027 | $values[] = "'" . str_replace( $search, $replace, $this->sql_addslashes( $row[$j] ) ) . "'"; 2028 | 2029 | } else { 2030 | $values[] = "''"; 2031 | 2032 | } 2033 | 2034 | } 2035 | 2036 | $sql_file .= " \n" . $entries . implode( ', ', $values ) . ") ;"; 2037 | 2038 | // write the rows in batches of 100 2039 | if ( $batch_write === 100 ) { 2040 | $batch_write = 0; 2041 | $this->write_sql( $sql_file ); 2042 | $sql_file = ''; 2043 | } 2044 | 2045 | $batch_write++; 2046 | 2047 | unset( $values ); 2048 | 2049 | } 2050 | 2051 | mysqli_free_result( $result ); 2052 | 2053 | // Create footer/closing comment in SQL-file 2054 | $sql_file .= "\n"; 2055 | $sql_file .= "#\n"; 2056 | $sql_file .= "# End of data contents of table " . $table . "\n"; 2057 | $sql_file .= "# --------------------------------------------------------\n"; 2058 | $sql_file .= "\n"; 2059 | 2060 | $this->write_sql( $sql_file ); 2061 | 2062 | } 2063 | 2064 | /** 2065 | * Better addslashes for SQL queries. 2066 | * Taken from phpMyAdmin. 2067 | * 2068 | * @param string $a_string 2069 | * @param bool $is_like 2070 | * @return mixed 2071 | */ 2072 | private function sql_addslashes( $a_string = '', $is_like = false ) { 2073 | 2074 | if ( $is_like ) 2075 | $a_string = str_replace( '\\', '\\\\\\\\', $a_string ); 2076 | 2077 | else 2078 | $a_string = str_replace( '\\', '\\\\', $a_string ); 2079 | 2080 | $a_string = str_replace( '\'', '\\\'', $a_string ); 2081 | 2082 | return $a_string; 2083 | } 2084 | 2085 | /** 2086 | * Write the SQL file 2087 | * @param string $sql 2088 | * @return bool 2089 | */ 2090 | private function write_sql( $sql ) { 2091 | 2092 | $sqlname = $this->get_database_dump_filepath(); 2093 | 2094 | // Actually write the sql file 2095 | if ( is_writable( $sqlname ) || ! file_exists( $sqlname ) ) { 2096 | 2097 | if ( ! $handle = @fopen( $sqlname, 'a' ) ) 2098 | return; 2099 | 2100 | if ( ! @fwrite( $handle, $sql ) ) 2101 | return; 2102 | 2103 | @fclose( $handle ); 2104 | 2105 | return true; 2106 | 2107 | } 2108 | 2109 | } 2110 | 2111 | /** 2112 | * Get the errors 2113 | * 2114 | * @access public 2115 | */ 2116 | public function get_errors( $context = null ) { 2117 | 2118 | if ( ! empty( $context ) ) 2119 | return isset( $this->errors[$context] ) ? $this->errors[$context] : array(); 2120 | 2121 | return $this->errors; 2122 | 2123 | } 2124 | 2125 | /** 2126 | * Add an error to the errors stack 2127 | * 2128 | * @access private 2129 | * @param string $context 2130 | * @param mixed $error 2131 | */ 2132 | public function error( $context, $error ) { 2133 | 2134 | if ( empty( $context ) || empty( $error ) ) 2135 | return; 2136 | 2137 | $this->errors[$context][$_key = md5( implode( ':' , (array) $error ) )] = $error; 2138 | 2139 | $this->do_action( 'hmbkp_error' ); 2140 | 2141 | } 2142 | 2143 | /** 2144 | * Migrate errors to warnings 2145 | * 2146 | * @access private 2147 | * @param string $context. (default: null) 2148 | */ 2149 | private function errors_to_warnings( $context = null ) { 2150 | 2151 | $errors = empty( $context ) ? $this->get_errors() : array( $context => $this->get_errors( $context ) ); 2152 | 2153 | if ( empty( $errors ) ) 2154 | return; 2155 | 2156 | foreach ( $errors as $error_context => $context_errors ) 2157 | foreach( $context_errors as $error ) 2158 | $this->warning( $error_context, $error ); 2159 | 2160 | if ( $context ) 2161 | unset( $this->errors[$context] ); 2162 | 2163 | else 2164 | $this->errors = array(); 2165 | 2166 | } 2167 | 2168 | /** 2169 | * Get the warnings 2170 | * 2171 | * @access public 2172 | */ 2173 | public function get_warnings( $context = null ) { 2174 | 2175 | if ( ! empty( $context ) ) 2176 | return isset( $this->warnings[$context] ) ? $this->warnings[$context] : array(); 2177 | 2178 | return $this->warnings; 2179 | 2180 | } 2181 | 2182 | /** 2183 | * Add an warning to the warnings stack 2184 | * 2185 | * @access private 2186 | * @param string $context 2187 | * @param mixed $warning 2188 | */ 2189 | private function warning( $context, $warning ) { 2190 | 2191 | if ( empty( $context ) || empty( $warning ) ) 2192 | return; 2193 | 2194 | $this->do_action( 'hmbkp_warning' ); 2195 | 2196 | $this->warnings[$context][$_key = md5( implode( ':' , (array) $warning ) )] = $warning; 2197 | 2198 | } 2199 | 2200 | /** 2201 | * Custom error handler for catching php errors 2202 | * 2203 | * @param string $type 2204 | * @return bool 2205 | */ 2206 | public function error_handler( $type ) { 2207 | 2208 | // Skip strict & deprecated warnings 2209 | if ( ( defined( 'E_DEPRECATED' ) && $type === E_DEPRECATED ) || ( defined( 'E_STRICT' ) && $type === E_STRICT ) || error_reporting() === 0 ) 2210 | return false; 2211 | 2212 | $args = func_get_args(); 2213 | 2214 | array_shift( $args ); 2215 | 2216 | $this->warning( 'php', implode( ', ', array_splice( $args, 0, 3 ) ) ); 2217 | 2218 | return false; 2219 | 2220 | } 2221 | 2222 | /** 2223 | * Recursively delete a directory including 2224 | * all the files and sub-directories. 2225 | * 2226 | * @param string $dir 2227 | * @return bool 2228 | */ 2229 | public static function rmdir_recursive( $dir ) { 2230 | 2231 | if ( is_file( $dir ) ) 2232 | @unlink( $dir ); 2233 | 2234 | if ( ! is_dir( $dir ) ) 2235 | return false; 2236 | 2237 | $files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $dir ), RecursiveIteratorIterator::CHILD_FIRST, RecursiveIteratorIterator::CATCH_GET_CHILD ); 2238 | 2239 | foreach ( $files as $file ) { 2240 | 2241 | if ( $file->isDir() ) 2242 | @rmdir( $file->getPathname() ); 2243 | 2244 | else 2245 | @unlink( $file->getPathname() ); 2246 | 2247 | } 2248 | 2249 | @rmdir( $dir ); 2250 | 2251 | } 2252 | 2253 | } 2254 | 2255 | /** 2256 | * Add file callback for PclZip, excludes files 2257 | * and sets the database dump to be stored in the root 2258 | * of the zip 2259 | * 2260 | * @access private 2261 | * @param string $event 2262 | * @param array &$file 2263 | * @return bool 2264 | */ 2265 | function wprp_hmbkp_pclzip_callback( $event, &$file ) { 2266 | 2267 | global $_wprp_hmbkp_exclude_string; 2268 | 2269 | // Don't try to add unreadable files. 2270 | if ( ! is_readable( $file['filename'] ) || ! file_exists( $file['filename'] ) ) 2271 | return false; 2272 | 2273 | // Match everything else past the exclude list 2274 | elseif ( $_wprp_hmbkp_exclude_string && preg_match( '(' . $_wprp_hmbkp_exclude_string . ')', $file['stored_filename'] ) ) 2275 | return false; 2276 | 2277 | return true; 2278 | 2279 | } 2280 | --------------------------------------------------------------------------------