├── composer.json ├── readme.md ├── .gitignore ├── readme.txt └── db-checkpoint.php /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "binarygary/db-checkpoint", 3 | "description": "Create quick db snapshots for development purposes.", 4 | "type": "wp-cli-package", 5 | "keywords": ["wp-cli", "git", "deploy", "dump", "mysql", "uploads", "wordpress", "db snapshot"], 6 | "homepage": "https://github.com/binarygary/db-checkpoint", 7 | "license": "MIT", 8 | "version": "0.2.1", 9 | "authors": [ 10 | { 11 | "name": "Gary Kovar", 12 | "email": "plugins@binarygary.com", 13 | "homepage": "https://www.binarygary.com" 14 | } 15 | ], 16 | "require": { 17 | "php": ">=5.4" 18 | }, 19 | "autoload": { 20 | "files": [ "db-checkpoint.php" ] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # wp-cli db-checkpoint 2 | 3 | WP-CLI command for quick db snapshots. Currently supported commands: 4 | 5 | * `wp dbsnap` -- Create a db snapshot. 6 | * `wp dbsnapback` -- Restore a db snapshot. 7 | * `wp dbsnap test` -- Create a db snapshot of a specific name. 8 | * `wp dbsnapback test` -- Restore a db snapshot of specific name. 9 | 10 | ## Why doesn't this do _x_? 11 | 12 | Because I haven't built it yet. I'm filling in commands as I need them, which means that they are largely developer-focused. I'll fill in more commands as I need them. Pull requests will be enthusiastically received. 13 | 14 | ## System Requirements 15 | 16 | * PHP >=5.3 17 | 18 | ## Setup 19 | 20 | * Install [wp-cli](https://wp-cli.org) 21 | * Install this package with this command: `wp package install binarygary/db-checkpoint` 22 | * Inside of a WP installation, type `wp`. You should see a list of available commands. 23 | 24 | ## Changelog 25 | 26 | ### 0.2.2 ### 27 | * Adds flag --dev which prompts. 28 | 29 | ### 0.2.1 ### 30 | * Adds flag --dumplog which removes the debug.log file. 31 | 32 | ### 0.2.0 33 | 34 | * Added a method to install the helper plugin. 35 | * Helper plugin adds a restore option to the admin bar. 36 | 37 | ### 0.1.0 38 | 39 | * Initial release 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### OSX ### 2 | .DS_Store 3 | .AppleDouble 4 | .LSOverride 5 | 6 | # Icon must end with two \r 7 | Icon 8 | 9 | 10 | # Thumbnails 11 | ._* 12 | 13 | # Files that might appear on external disk 14 | .Spotlight-V100 15 | .Trashes 16 | 17 | # Directories potentially created on remote AFP share 18 | .AppleDB 19 | .AppleDesktop 20 | Network Trash Folder 21 | Temporary Items 22 | .apdisk 23 | 24 | 25 | ### Bower ### 26 | bower_components 27 | .bower-cache 28 | .bower-registry 29 | .bower-tmp 30 | 31 | 32 | ### Node ### 33 | # Logs 34 | logs 35 | *.log 36 | 37 | # Runtime data 38 | pids 39 | *.pid 40 | *.seed 41 | 42 | # Directory for instrumented libs generated by jscoverage/JSCover 43 | lib-cov 44 | 45 | # Coverage directory used by tools like istanbul 46 | coverage 47 | 48 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 49 | .grunt 50 | 51 | # node-waf configuration 52 | .lock-wscript 53 | 54 | # Compiled binary addons (http://nodejs.org/api/addons.html) 55 | build/Release 56 | 57 | # Dependency directory 58 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 59 | node_modules 60 | 61 | 62 | 63 | # Commit your application's lock file http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file 64 | # You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file 65 | # composer.lock 66 | 67 | ### Sass ### 68 | .sass-cache 69 | 70 | release 71 | 72 | .idea 73 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | # DB Snapshot # 2 | **Contributors: binarygary 3 | **Donate link: https://bethematch.org/ 4 | **Tags: database, wp-cli 5 | **Requires at least: 4.4 6 | **Tested up to: 4.8 7 | **Stable tag: 0.2.2 8 | **License: GPLv2 9 | **License URI: http://www.gnu.org/licenses/gpl-2.0.html 10 | 11 | Extends WP-CLI to include a db checkpoint for development purposes. 12 | 13 | ## Description ## 14 | 15 | If [WP-CLI](http://wp-cli.org/ "WP-CLI") is available this plugin adds 2 new commands. 16 | `wp dbsnap` 17 | creates a snapshot of your database. No fuss, no muss... 18 | `wp dbsnapback` 19 | restores the snapshot. That simple... 20 | 21 | If you need 2 different checkpoints you can name them 22 | `wp dbsnap db-before-i-do-something-sketchy` 23 | `wp dbsnapback db-before-i-do-something-sketchy` 24 | 25 | ## Installation ## 26 | 27 | ### Manual Installation ### 28 | 29 | 1. Upload the entire `/db-checkpoint` directory to the `/wp-content/plugins/` directory. 30 | 2. Activate DB CheckPoint through the 'Plugins' menu in WordPress. 31 | 32 | ## Frequently Asked Questions ## 33 | = Where are the db exports stored? = 34 | wp-content/checkpoint-storage/ 35 | 36 | ## Changelog ## 37 | 38 | ### 0.2.2 ### 39 | * Adds flag --dev which prompts 40 | 41 | ### 0.2.1 ### 42 | * Adds flag --dumplog which removes the debug.log file. 43 | 44 | ### 0.2.0 ### 45 | * Added a method to install the helper plugin. 46 | * Helper plugin adds a restore option to the admin bar. 47 | 48 | ### 0.1.1 ### 49 | * Fixed a function name in the plugin class. 50 | * Fixed readme.txt formatting. 51 | 52 | ### 0.1.0 ### 53 | * First release 54 | -------------------------------------------------------------------------------- /db-checkpoint.php: -------------------------------------------------------------------------------- 1 | 'Creates a simple checkpoint image of the database.', 64 | 'synopsis' => array( 65 | array( 66 | 'type' => 'positional', 67 | 'name' => 'name', 68 | 'optional' => true, 69 | 'multiple' => false, 70 | ), 71 | ), 72 | 'when' => 'after_wp_load', 73 | ); 74 | } 75 | 76 | /** 77 | * Returns the array of configuration setup info for dbsnapback command. 78 | * 79 | * @author Gary Kovar 80 | * 81 | * @since 0.1.0 82 | * 83 | * @return array 84 | * 85 | */ 86 | public function get_checkpoint_restore_args() { 87 | return array( 88 | 'shortdesc' => 'Restores the checkpoint image of the database.', 89 | 'synopsis' => array( 90 | array( 91 | 'type' => 'positional', 92 | 'name' => 'name', 93 | 'optional' => true, 94 | 'multiple' => false, 95 | ), 96 | array( 97 | 'type' => 'flag', 98 | 'name' => 'dumplog', 99 | 'optional' => true, 100 | 'default' => false, 101 | ), 102 | array( 103 | 'type' => 'flag', 104 | 'name' => 'dev', 105 | 'optional' => true, 106 | 'default' => false, 107 | ), 108 | ), 109 | 'when' => 'after_wp_load', 110 | ); 111 | } 112 | 113 | public function get_install_plugin_args() { 114 | return array( 115 | 'shortdesc' => 'Installs a plugin for 1-click restore from the admin bar..', 116 | 'synopsis' => array(), 117 | 'when' => 'after_wp_load', 118 | ); 119 | } 120 | 121 | /** 122 | * Saves a checkpoint of the db. 123 | * 124 | * @author Gary Kovar 125 | * 126 | * @since 0.1.0 127 | */ 128 | public function checkpoint_save( $args ) { 129 | 130 | if ( ! $this->check_requirements() ) { 131 | exit; 132 | } 133 | 134 | $snapshot_name = $this->get_snapshot_name( $args ); 135 | 136 | $this->maybe_nuke_checkpoints( $snapshot_name ); 137 | 138 | $upload_dir = wp_upload_dir(); 139 | 140 | $location = $upload_dir['basedir'] . '/checkpoint-storage/' . $snapshot_name . '.' . $this->human_timestamp() . '.sql'; 141 | $args[0] = $location; 142 | 143 | $db = new DB_Command; 144 | $db->export( $args, null ); 145 | 146 | WP_CLI::success( "Checkpoint Saved!" ); 147 | } 148 | 149 | /** 150 | * Restores the most recent checkpoint of the db. 151 | * 152 | * @author Gary Kovar 153 | * 154 | * @since 0.1.0 155 | */ 156 | public function checkpoint_restore( $args, $assoc_args ) { 157 | 158 | if ( ! $this->check_requirements() ) { 159 | exit; 160 | } 161 | 162 | // Check if the dev flag is set to true OR site is *.dev, otherwise prompt. 163 | if ( ! $this->is_dev() || ( key_exists( 'dev', $assoc_args ) && ! $assoc_args['dev'] ) ) { 164 | WP_CLI::confirm( "This is a destructive operation, are you sure?" ); 165 | } 166 | 167 | $snapshot_name = $this->get_snapshot_name( $args ); 168 | 169 | $upload_dir = wp_upload_dir(); 170 | 171 | if ( $restore_file = $this->get_most_recent_file( $snapshot_name ) ) { 172 | $location = $upload_dir['basedir'] . '/checkpoint-storage/' . $this->get_most_recent_file( $snapshot_name ); 173 | } else { 174 | WP_CLI::error( 'No checkpoint found associated with ' . $snapshot_name ); 175 | } 176 | 177 | $args[0] = $location; 178 | 179 | $db = new DB_Command; 180 | $db->reset( $args, null ); 181 | $db->import( $args, null ); 182 | 183 | // If the dumplog flag is set, clear the log file. 184 | if ( key_exists( 'dumplog', $assoc_args ) && $assoc_args['dumplog'] ) { 185 | $this->dump_log(); 186 | } 187 | 188 | WP_CLI::success( "Checkpoint Restored!" ); 189 | } 190 | 191 | /** 192 | * Delete the debug.log. 193 | * 194 | * @author Gary Kovar 195 | * 196 | * @since 0.2.2 197 | * 198 | * @return null 199 | */ 200 | public function dump_log() { 201 | if (file_exists(WP_CONTENT_DIR . '/debug.log' ) ){ 202 | unlink( WP_CONTENT_DIR . '/debug.log' ); 203 | } 204 | 205 | WP_CLI::success( "debug.log removed" ); 206 | } 207 | 208 | /** 209 | * Check if this is a .dev site. 210 | * 211 | * @author Gary Kovar 212 | * 213 | * @since 0.2.2 214 | */ 215 | public function is_dev() { 216 | if ( '.dev' === substr(get_site_url(),-4)){ 217 | return true; 218 | } 219 | return false; 220 | } 221 | 222 | /** 223 | * Get the name of the most recent backup file. 224 | * 225 | * @author Gary Kovar 226 | * 227 | * @since 0.1.0 228 | * 229 | * @param $backup_name 230 | * 231 | * @return bool 232 | */ 233 | public function get_most_recent_file( $backup_name ) { 234 | 235 | $upload_dir = wp_upload_dir(); 236 | $backupsdir = scandir( $upload_dir['basedir'] . '/checkpoint-storage/', SCANDIR_SORT_DESCENDING ); 237 | foreach ( $backupsdir as $backup ) { 238 | if ( strpos( $backup, $backup_name ) === 0 ) { 239 | return $backup; 240 | } 241 | } 242 | 243 | return false; 244 | } 245 | 246 | /** 247 | * Figure out what name to use with this file. 248 | * 249 | * @author Gary Kovar 250 | * 251 | * @since 0.1.0 252 | * 253 | * @param $args 254 | * 255 | * @return string 256 | */ 257 | public function get_snapshot_name( $args ) { 258 | 259 | if ( key_exists( 0, $args ) ) { 260 | return $args[0]; 261 | } 262 | 263 | return sanitize_title( get_option( 'blogname', 'shruggy' ) ); 264 | } 265 | 266 | /** 267 | * Check to see if the checkpoint name matches the site name, if so remove any checkpoints of the same name. 268 | * 269 | * @author Gary Kovar 270 | * 271 | * @since 0.1.0 272 | * 273 | * @param $checkpoint_name 274 | */ 275 | public function maybe_nuke_checkpoints( $checkpoint_name ) { 276 | $this->nuke_checkpoints( $checkpoint_name ); 277 | } 278 | 279 | /** 280 | * Deletes all previous checkpoints under the same name. 281 | * 282 | * @author Gary Kovar 283 | * 284 | * @since 0.1.0 285 | * 286 | * @param $checkpoint_name 287 | */ 288 | public function nuke_checkpoints( $checkpoint_name ) { 289 | $upload_dir = wp_upload_dir(); 290 | $backupsdir = scandir( $upload_dir['basedir'] . '/checkpoint-storage/', SCANDIR_SORT_DESCENDING ); 291 | 292 | // Make sure we have a list of file before trying to process them. 293 | if ( is_array( $backupsdir ) ) { 294 | foreach ( $backupsdir as $backup ) { 295 | if ( strpos( $backup, $checkpoint_name ) === 0 ) { 296 | unlink( $upload_dir['basedir'] . '/checkpoint-storage/' . $backup ); 297 | } 298 | } 299 | } 300 | } 301 | 302 | /** 303 | * Return a pretty human readable time. 304 | * 305 | * @author Gary Kovar 306 | * 307 | * @since 0.1.0 308 | * 309 | * @return false|string 310 | */ 311 | public function human_timestamp() { 312 | return date( "Ymd-Hi", time() ); 313 | } 314 | 315 | public function install_plugin() { 316 | $args = array( 'plugin', 'install', 'db-snapshot' ); 317 | $assoc_args = array( 'activate' => 'activate' ); 318 | WP_CLI::run_command( $args, $assoc_args ); 319 | } 320 | 321 | } 322 | 323 | /** 324 | * Kick off! 325 | * 326 | * @return DB_CheckPoint 327 | */ 328 | function db_checkpoint() { 329 | return new DB_CheckPoint; 330 | } 331 | 332 | $checkpoint = db_checkpoint(); 333 | 334 | /** 335 | * Add dbsnap as a WP CLI command. 336 | */ 337 | WP_CLI::add_command( 'dbsnap', array( 338 | $checkpoint, 339 | 'checkpoint_save', 340 | ), $checkpoint->get_checkpoint_save_args() ); 341 | 342 | /** 343 | * Add dbsnapback as a WP CLI command. 344 | */ 345 | WP_CLI::add_command( 'dbsnapback', array( 346 | $checkpoint, 347 | 'checkpoint_restore', 348 | ), $checkpoint->get_checkpoint_restore_args() ); 349 | 350 | /** 351 | * Add dbsnap plugin as a WP CLI command. 352 | */ 353 | WP_CLI::add_command( 'dbsnapplug', array( 354 | $checkpoint, 355 | 'install_plugin', 356 | ), $checkpoint->get_install_plugin_args() ); 357 | } 358 | } 359 | 360 | 361 | if ( ! defined( 'WP_CLI' ) ) { 362 | if ( ! class_exists( 'DB_CheckPoint_Plugin' ) ) { 363 | class DB_CheckPoint_Plugin { 364 | 365 | /** 366 | * The return from wp_upload_dir(); 367 | * 368 | * @var string 369 | */ 370 | private $upload_dir; 371 | 372 | /** 373 | * Initiate the class and set some of the regular variables. 374 | * 375 | * @author Gary Kovar 376 | * 377 | * @since 0.2.0 378 | */ 379 | public function init() { 380 | $this->upload_dir = wp_upload_dir(); 381 | $this->hooks(); 382 | } 383 | 384 | /** 385 | * Hook to add functions to WP 386 | * 387 | * @author Gary Kovar 388 | * 389 | * @since 0.2.0 390 | */ 391 | public function hooks() { 392 | $this->does_upload_folder_exist(); 393 | 394 | if ( $this->should_show_dbsnapback_in_admin_menu() ) { 395 | add_action( 'admin_bar_menu', array( $this, 'toolbar_dbsnapback' ), 999 ); 396 | add_action( 'admin_bar_menu', array( $this, 'add_dbsnapback_child_nodes' ), 999 ); 397 | } else { 398 | add_action( 'admin_bar_menu', array( $this, 'toolbar_dbsnap' ), 999 ); 399 | } 400 | 401 | if ( key_exists( 'snpackback_restore', $_GET ) ) { 402 | if ( current_user_can( 'manage_options' ) ) { 403 | add_action( 'init', array( $this, 'restore' ) ); 404 | } 405 | } 406 | 407 | if ( key_exists( 'create_snap', $_GET ) ) { 408 | if ( current_user_can( 'manage_options' ) ) { 409 | add_action( 'init', array( $this, 'backup' ) ); 410 | } 411 | } 412 | } 413 | 414 | /** 415 | * Check if the upload folder exists and if not create it. 416 | * 417 | * @author Gary Kovar 418 | * 419 | * @since 0.2.0 420 | */ 421 | public function does_upload_folder_exist() { 422 | if ( ! file_exists( $this->upload_dir['basedir'] . '/checkpoint-storage' ) ) { 423 | mkdir( $this->upload_dir['basedir'] . '/checkpoint-storage' ); 424 | } 425 | } 426 | 427 | /** 428 | * Check and see if we should show the admin bar by counting how many files are in the backup dir. 429 | * 430 | * @author Gary Kovar 431 | * 432 | * @since 0.2.0 433 | * 434 | * @return bool 435 | */ 436 | public function should_show_dbsnapback_in_admin_menu() { 437 | 438 | // If we count more than 2 files (. , ..) then we have some backups. 439 | if ( count( scandir( $this->upload_dir['basedir'] . '/checkpoint-storage' ) ) > 2 ) { 440 | return true; 441 | } 442 | 443 | return false; 444 | } 445 | 446 | /** 447 | * Get the list of snaps and add a node for each. 448 | * 449 | * @author Gary Kovar 450 | * 451 | * @since 0.2.0 452 | * 453 | */ 454 | public function add_dbsnapback_child_nodes( $wp_admin_bar ) { 455 | $files = $this->get_snaps(); 456 | 457 | foreach ( $files as $file ) { 458 | 459 | $args = array( 460 | 'id' => $file[0], 461 | 'title' => $file[0], 462 | 'href' => '?snpackback_restore=' . $file[0] . '.' . $file[1] . '.sql', 463 | 'parent' => 'dbsnapback', 464 | 'meta' => array( 465 | 'class' => 'dbsnapback', 466 | ), 467 | ); 468 | $wp_admin_bar->add_node( $args ); 469 | } 470 | } 471 | 472 | /** 473 | * Get the existing snaps. 474 | * 475 | * @author Gary Kovar 476 | * 477 | * @since 0.2.0 478 | */ 479 | public function get_snaps() { 480 | $backupsdir = scandir( $this->upload_dir['basedir'] . '/checkpoint-storage/', SCANDIR_SORT_DESCENDING ); 481 | foreach ( $backupsdir as $backup ) { 482 | $file_exploded = explode( '.', $backup ); 483 | if ( $file_exploded[0] != '' ) { 484 | $list[] = $file_exploded; 485 | } 486 | } 487 | 488 | return $list; 489 | } 490 | 491 | /** 492 | * Add restore link to toolbar. 493 | * 494 | * @author Gary Kovar 495 | * 496 | * @since 0.2.0 497 | */ 498 | public function toolbar_dbsnapback( $wp_admin_bar ) { 499 | $args = array( 500 | 'id' => 'dbsnapback', 501 | 'title' => 'DBSnapBack', 502 | 'href' => '#', 503 | 'meta' => array( 504 | 'class' => 'dbsnapback', 505 | ), 506 | ); 507 | $wp_admin_bar->add_node( $args ); 508 | } 509 | 510 | /** 511 | * Add snapshot link to toolbar. 512 | * 513 | * @author Gary Kovar 514 | * 515 | * @since 0.2.0 516 | */ 517 | public function toolbar_dbsnap( $wp_admin_bar ) { 518 | $args = array( 519 | 'id' => 'dbsnap', 520 | 'title' => 'DBSnap', 521 | 'href' => '?create_snap', 522 | 'meta' => array( 523 | 'class' => 'dbsnap', 524 | ), 525 | ); 526 | $wp_admin_bar->add_node( $args ); 527 | } 528 | 529 | public function backup() { 530 | $command = 'wp dbsnap'; 531 | exec( $command ); 532 | } 533 | 534 | /** 535 | * Restores the selected snapshot. 536 | * 537 | * @author Gary Kovar 538 | * 539 | * @since 0.2.0 540 | */ 541 | public function restore() { 542 | $filename = $_GET['snpackback_restore']; 543 | $command = 'wp db import ' . $this->upload_dir['basedir'] . '/checkpoint-storage/' . $filename; 544 | exec( $command ); 545 | } 546 | 547 | } 548 | 549 | /** 550 | * Kick Off! 551 | * 552 | * @return DB_CheckPoint_Plugin 553 | */ 554 | function db_checkpoint_plugin() { 555 | return new DB_CheckPoint_Plugin(); 556 | } 557 | 558 | add_action( 'plugins_loaded', array( db_checkpoint_plugin(), 'init' ) ); 559 | } 560 | } 561 | --------------------------------------------------------------------------------