This commit introduces a more reliable and consistent approach to setting up the development environment using backups: - Add setup-from-backup.sh script for environment setup from existing backups - Standardize script naming and organization - Move obsolete scripts to bin/obsolete directory - Update documentation with new workflow instructions - Create migration guide for transitioning to new workflow - Update Memory Bank with workflow improvements The new workflow provides: - More reliable environment setup - Faster setup process - Offline development capability - Consistent development environments across team members Breaking changes: - setup-dev.sh is replaced by setup-from-backup.sh - sync-and-setup.sh is replaced by separate scripts - verify-with-wpcli.sh is no longer used Migration path is documented in MIGRATION_GUIDE.md
367 lines
No EOL
11 KiB
PHP
367 lines
No EOL
11 KiB
PHP
<?php
|
|
/**
|
|
* Define database parameters here
|
|
*/
|
|
$upload_dir = wp_upload_dir();
|
|
$backup_dirname = $upload_dir['basedir'].'/wp-file-manager-pro/fm_backup';
|
|
define("BACKUP_DIR", $backup_dirname); // Comment this line to use same script's directory ('.')
|
|
define("TABLES", '*'); // Full backup
|
|
define("CHARSET", 'utf8');
|
|
define("GZIP_BACKUP_FILE", true); // Set to false if you want plain SQL backup files (not gzipped)
|
|
define("DISABLE_FOREIGN_KEY_CHECKS", true); // Set to true if you are having foreign key constraint fails
|
|
define("BATCH_SIZE", 1000); // Batch size when selecting rows from database in order to not exhaust system memory
|
|
// Also number of rows per INSERT statement in backup file
|
|
/**
|
|
* The Backup_Database class
|
|
*/
|
|
class Backup_Database {
|
|
/**
|
|
* Host where the database is located
|
|
*/
|
|
var $host;
|
|
|
|
/**
|
|
* Username used to connect to database
|
|
*/
|
|
var $username;
|
|
|
|
/**
|
|
* Password used to connect to database
|
|
*/
|
|
var $passwd;
|
|
|
|
/**
|
|
* Database to backup
|
|
*/
|
|
var $dbName;
|
|
|
|
/**
|
|
* Database charset
|
|
*/
|
|
var $charset;
|
|
|
|
/**
|
|
* Database connection
|
|
*/
|
|
var $conn;
|
|
|
|
/**
|
|
* Backup directory where backup files are stored
|
|
*/
|
|
var $backupDir;
|
|
|
|
/**
|
|
* Output backup file
|
|
*/
|
|
var $backupFile;
|
|
|
|
/**
|
|
* Use gzip compression on backup file
|
|
*/
|
|
var $gzipBackupFile;
|
|
|
|
/**
|
|
* Content of standard output
|
|
*/
|
|
var $output;
|
|
|
|
/**
|
|
* Disable foreign key checks
|
|
*/
|
|
var $disableForeignKeyChecks;
|
|
|
|
/**
|
|
* Batch size, number of rows to process per iteration
|
|
*/
|
|
var $batchSize;
|
|
|
|
/**
|
|
* Constructor initializes database
|
|
*/
|
|
public function __construct($filename) {
|
|
$this->host = DB_HOST;
|
|
$this->username = DB_USER;
|
|
$this->passwd = DB_PASSWORD;
|
|
$this->dbName = DB_NAME;
|
|
$this->charset = DB_CHARSET;
|
|
$this->conn = $this->initializeDatabase();
|
|
$this->backupDir = BACKUP_DIR ? BACKUP_DIR : '.';
|
|
$this->backupFile = $filename.'-db.sql';
|
|
$this->gzipBackupFile = defined('GZIP_BACKUP_FILE') ? GZIP_BACKUP_FILE : true;
|
|
$this->disableForeignKeyChecks = defined('DISABLE_FOREIGN_KEY_CHECKS') ? DISABLE_FOREIGN_KEY_CHECKS : true;
|
|
$this->batchSize = defined('BATCH_SIZE') ? BATCH_SIZE : 1000; // default 1000 rows
|
|
$this->output = '';
|
|
}
|
|
|
|
protected function initializeDatabase() {
|
|
try {
|
|
$conn = mysqli_connect($this->host, $this->username, $this->passwd, $this->dbName);
|
|
if (mysqli_connect_errno()) {
|
|
throw new Exception('ERROR connecting database: ' . mysqli_connect_error());
|
|
die();
|
|
}
|
|
if (!mysqli_set_charset($conn, $this->charset)) {
|
|
mysqli_query($conn, 'SET NAMES '.$this->charset);
|
|
}
|
|
} catch (Exception $e) {
|
|
print_r($e->getMessage());
|
|
die();
|
|
}
|
|
|
|
return $conn;
|
|
}
|
|
|
|
/**
|
|
* Backup the whole database or just some tables
|
|
* Use '*' for whole database or 'table1 table2 table3...'
|
|
* @param string $tables
|
|
*/
|
|
public function backupTables($tables = '*', $bkpDir="") {
|
|
try {
|
|
/**
|
|
* Tables to export
|
|
*/
|
|
if($tables == '*') {
|
|
$tables = array();
|
|
$result = mysqli_query($this->conn, 'SHOW TABLES');
|
|
while($row = mysqli_fetch_row($result)) {
|
|
$tables[] = $row[0];
|
|
}
|
|
} else {
|
|
$tables = is_array($tables) ? $tables : explode(',', str_replace(' ', '', $tables));
|
|
}
|
|
|
|
$sql = 'CREATE DATABASE IF NOT EXISTS `'.$this->dbName."`;\n\n";
|
|
$sql .= 'USE `'.$this->dbName."`;\n\n";
|
|
|
|
/**
|
|
* Disable foreign key checks
|
|
*/
|
|
if ($this->disableForeignKeyChecks === true) {
|
|
$sql .= "SET foreign_key_checks = 0;\n\n";
|
|
}
|
|
|
|
/**
|
|
* Iterate tables
|
|
*/
|
|
foreach($tables as $table) {
|
|
$this->obfPrint("Backing up `".$table."` table...".str_repeat('.', 50-strlen($table)), 0, 0);
|
|
|
|
/**
|
|
* CREATE TABLE
|
|
*/
|
|
$sql .= 'DROP TABLE IF EXISTS `'.$table.'`;';
|
|
$row = mysqli_fetch_row(mysqli_query($this->conn, 'SHOW CREATE TABLE `'.$table.'`'));
|
|
$sql .= "\n\n".$row[1].";\n\n";
|
|
|
|
/**
|
|
* INSERT INTO
|
|
*/
|
|
|
|
$row = mysqli_fetch_row(mysqli_query($this->conn, 'SELECT COUNT(*) FROM `'.$table.'`'));
|
|
$numRows = $row[0];
|
|
|
|
// Split table in batches in order to not exhaust system memory
|
|
$numBatches = intval($numRows / $this->batchSize) + 1; // Number of while-loop calls to perform
|
|
|
|
for ($b = 1; $b <= $numBatches; $b++) {
|
|
|
|
$query = 'SELECT * FROM `' . $table . '` LIMIT ' . ($b * $this->batchSize - $this->batchSize) . ',' . $this->batchSize;
|
|
$result = mysqli_query($this->conn, $query);
|
|
$realBatchSize = mysqli_num_rows ($result); // Last batch size can be different from $this->batchSize
|
|
$numFields = mysqli_num_fields($result);
|
|
|
|
if ($realBatchSize !== 0) {
|
|
$sql .= 'INSERT INTO `'.$table.'` VALUES ';
|
|
|
|
for ($i = 0; $i < $numFields; $i++) {
|
|
$rowCount = 1;
|
|
while($row = mysqli_fetch_row($result)) {
|
|
$sql.='(';
|
|
for($j=0; $j<$numFields; $j++) {
|
|
if (isset($row[$j])) {
|
|
$row[$j] = addslashes($row[$j]);
|
|
$row[$j] = str_replace("\n","\\n",$row[$j]);
|
|
$row[$j] = str_replace("\r","\\r",$row[$j]);
|
|
$row[$j] = str_replace("\f","\\f",$row[$j]);
|
|
$row[$j] = str_replace("\t","\\t",$row[$j]);
|
|
$row[$j] = str_replace("\v","\\v",$row[$j]);
|
|
$row[$j] = str_replace("\a","\\a",$row[$j]);
|
|
$row[$j] = str_replace("\b","\\b",$row[$j]);
|
|
if (preg_match('/^-?[0-9]+$/', $row[$j]) or $row[$j] == 'NULL' or $row[$j] == 'null') {
|
|
$sql .= $row[$j];
|
|
} else {
|
|
$sql .= '"'.$row[$j].'"' ;
|
|
}
|
|
} else {
|
|
$sql.= 'NULL';
|
|
}
|
|
|
|
if ($j < ($numFields-1)) {
|
|
$sql .= ',';
|
|
}
|
|
}
|
|
|
|
if ($rowCount == $realBatchSize) {
|
|
$rowCount = 0;
|
|
$sql.= ");\n"; //close the insert statement
|
|
} else {
|
|
$sql.= "),\n"; //close the row
|
|
}
|
|
|
|
$rowCount++;
|
|
}
|
|
}
|
|
|
|
$this->saveFile($sql);
|
|
$sql = '';
|
|
}
|
|
}
|
|
$sql.="\n\n";
|
|
|
|
$this->obfPrint('OK');
|
|
}
|
|
|
|
/**
|
|
* Re-enable foreign key checks
|
|
*/
|
|
if ($this->disableForeignKeyChecks === true) {
|
|
$sql .= "SET foreign_key_checks = 1;\n";
|
|
}
|
|
|
|
$this->saveFile($sql);
|
|
|
|
if ($this->gzipBackupFile) {
|
|
$this->gzipBackupFile();
|
|
} else {
|
|
$this->obfPrint('Backup file succesfully saved to ' . $this->backupDir.'/'.$this->backupFile, 1, 1);
|
|
}
|
|
} catch (Exception $e) {
|
|
print_r($e->getMessage());
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Save SQL to file
|
|
* @param string $sql
|
|
*/
|
|
protected function saveFile(&$sql) {
|
|
if (!$sql) return false;
|
|
|
|
try {
|
|
|
|
if (!file_exists($this->backupDir)) {
|
|
mkdir($this->backupDir, 0777, true);
|
|
}
|
|
|
|
file_put_contents($this->backupDir.'/'.$this->backupFile, $sql, FILE_APPEND | LOCK_EX);
|
|
|
|
} catch (Exception $e) {
|
|
print_r($e->getMessage());
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Gzip backup file
|
|
*
|
|
* @param integer $level GZIP compression level (default: 9)
|
|
* @return string New filename (with .gz appended) if success, or false if operation fails
|
|
*/
|
|
protected function gzipBackupFile($level = 9) {
|
|
if (!$this->gzipBackupFile) {
|
|
return true;
|
|
}
|
|
|
|
$source = $this->backupDir . '/' . $this->backupFile;
|
|
$dest = $source . '.gz';
|
|
|
|
$this->obfPrint('Gzipping backup file to ' . $dest . '... ', 1, 0);
|
|
|
|
$mode = 'wb' . $level;
|
|
if ($fpOut = gzopen($dest, $mode)) {
|
|
if ($fpIn = fopen($source,'rb')) {
|
|
while (!feof($fpIn)) {
|
|
gzwrite($fpOut, fread($fpIn, 1024 * 256));
|
|
}
|
|
fclose($fpIn);
|
|
} else {
|
|
return false;
|
|
}
|
|
gzclose($fpOut);
|
|
if(!unlink($source)) {
|
|
return false;
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
$this->obfPrint('OK');
|
|
return $dest;
|
|
}
|
|
|
|
/**
|
|
* Prints message forcing output buffer flush
|
|
*
|
|
*/
|
|
public function obfPrint ($msg = '', $lineBreaksBefore = 0, $lineBreaksAfter = 1) {
|
|
if (!$msg) {
|
|
return false;
|
|
}
|
|
|
|
if ($msg != 'OK' and $msg != 'KO') {
|
|
$msg = date("Y-m-d H:i:s") . ' - ' . $msg;
|
|
}
|
|
$output = '';
|
|
|
|
if (php_sapi_name() != "cli") {
|
|
$lineBreak = "<br />";
|
|
} else {
|
|
$lineBreak = "\n";
|
|
}
|
|
|
|
if ($lineBreaksBefore > 0) {
|
|
for ($i = 1; $i <= $lineBreaksBefore; $i++) {
|
|
$output .= $lineBreak;
|
|
}
|
|
}
|
|
|
|
$output .= $msg;
|
|
|
|
if ($lineBreaksAfter > 0) {
|
|
for ($i = 1; $i <= $lineBreaksAfter; $i++) {
|
|
$output .= $lineBreak;
|
|
}
|
|
}
|
|
|
|
|
|
// Save output for later use
|
|
$this->output .= str_replace('<br />', '\n', $output);
|
|
|
|
return $output;
|
|
|
|
|
|
if (php_sapi_name() != "cli") {
|
|
if( ob_get_level() > 0 ) {
|
|
ob_flush();
|
|
}
|
|
}
|
|
|
|
$this->output .= " ";
|
|
|
|
flush();
|
|
}
|
|
|
|
/**
|
|
* Returns full execution output
|
|
*
|
|
*/
|
|
public function getOutput() {
|
|
return $this->output;
|
|
}
|
|
} |