├── .editorconfig ├── CHANGELOG.md ├── LICENSE ├── README.md └── phpmybackup.php /.editorconfig: -------------------------------------------------------------------------------- 1 | # For more information about the properties used in 2 | # this file, please see the EditorConfig documentation: 3 | # http://editorconfig.org/ 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | indent_size = 4 9 | indent_style = space 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | 16 | [*.yml] 17 | indent_size = 2 18 | indent_style = space -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | Notable changes to this project will be documented in this file. 4 | 5 | ## [1.0.0] 6 | 7 | - Switch to MYSQLi 8 | - Support for empty tables. `SHOW TABLE CREATE` checksum is now used in the repository 9 | - Support for specifying a subset of databases to back up (supports wildcards) 10 | - Update docs & license 11 | 12 | 13 | ## [0.9] 14 | 15 | - PSR2, password via env 16 | 17 | 18 | ## [0.8] 19 | 20 | - Wildcard support 21 | 22 | 23 | ## [0.7] 24 | 25 | - Switch to using mysqldump client 26 | 27 | 28 | ## [0.6] 29 | 30 | - Add bin2hex option (default) for blob fields 31 | 32 | 33 | # [0.5] 34 | 35 | - Table caching (local copy) 36 | 37 | 38 | ## [0.4] 39 | 40 | - Fix bug where unescaped table names potentially caused issues (`Group`) 41 | 42 | 43 | ## [0.3] 44 | 45 | - Add UFT-8 encoding for MySQL connection & file writing 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2018 Ralph Slooten 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## **Note**: This software has been superseded by a completely different server/client backup called [MyBack](https://github.com/axllent/myback). 2 | 3 | MyBack does not require an open MySQL port to the backup sever, and is a 4 | far more robust & actively-maintained backup system. 5 | 6 | Please consider using MyBack instead, as PHPMyBackup is no longer maintained. 7 | 8 | https://github.com/axllent/myback 9 | 10 | --- 11 | 12 | PHPMyBackup - A PHP MySQL differential backup script 13 | ===================================================== 14 | 15 | 16 | A PHP MySQL differential backup script 17 | --------------------------------------- 18 | 19 | PHPMyBackup is a PHP script designed for backing up an entire MySQL 20 | server on the commandline. What makes it unique is it only uses use 21 | **differential** methods to dump only the changes as it keeps a local 22 | copy of all the synced databases & tables. 23 | 24 | 25 | Software features 26 | ----------------- 27 | 28 | - Only download changed/altered tables (checksum) 29 | - Allows specifying subset of databases for backups (supports wildcard) 30 | - Allows skipping of specified databases from backups (supports wildcard) 31 | - Allows skipping of specified tables or table-data (supports wildcard) 32 | - Integrates with `mysqldump` client for individual SQL dumps 33 | - Backup rotation 34 | 35 | 36 | Limitations 37 | ----------- 38 | 39 | - No database locking during backup. because a separate \`mysqldump\` 40 | is called for every table download, only table locking is used. 41 | - This has been tested in several environments, but your own full testing 42 | is always advised! 43 | 44 | Requirements 45 | ------------ 46 | 47 | - A MySQL user on the server with ‘SELECT’ & ‘LOCK TABLES’ permissions 48 | - `tar` with `xz` support (used for compressing backups) 49 | - PHP CLI on backup host with **MySQLi** support 50 | - `mysqldump` (used for actual table dumping) 51 | 52 | Usage Example 53 | ------------- 54 | 55 | require('phpmybackup.php'); 56 | $db = new MYSQL_DUMP; 57 | $db->dbhost = 'server.com'; 58 | $db->dbuser = 'backup-user'; 59 | $db->dbpwd = 'backup-password'; 60 | $db->backupsToKeep = 30; 61 | $db->showDebug = false; 62 | $db->backupDir = '/mnt/backups/mysql/'; 63 | $db->ignoreDatabases = ['test','unimportant_db']; 64 | $db->emptyTables = ['largedb.large_table1','largedb.cachetable']; 65 | $db->dumpDatabases(); 66 | 67 | - The above command will dump all databases except for `test` and 68 | `unimportant_db` from `server.com` 69 | - The data from two tables, `large_table1` & `cachetable`, (found in 70 | database `largedb`) will be ignored, however the table structure will 71 | be backed up. This is especially handy if you have temporary tables 72 | or large tables which contain unimportant / cached data. 73 | - A total of 30-days of backups will be kept. **Note**: Backups are 74 | named by date (yyyy-mm-dd.tar.xz), so a maximum of 1 backup per day 75 | can be kept. If the script is re-run on the same day, the repository 76 | is synced and the existing daily backup overwritten. 77 | 78 | Notes 79 | ----- 80 | 81 | By default all databases are backed up (`$db->includeDatabases = ['*']`). 82 | You can limit this to a subset of your databases if you like. 83 | -------------------------------------------------------------------------------- /phpmybackup.php: -------------------------------------------------------------------------------- 1 | errorMessage('Your PHP has no mysqli support, existing.'); 62 | return false; 63 | } 64 | 65 | date_default_timezone_set($this->timezone); 66 | $this->backupFormat = date('Y-m-d'); 67 | $this->backupDir = rtrim($this->backupDir, '/'); 68 | if (!$this->backupRepository) { 69 | $this->backupRepository = $this->backupDir . '/repo'; 70 | } 71 | $this->liveDatabases = []; 72 | 73 | // export MySQL password 74 | passthru('export MYSQL_PWD="' . $this->dbpwd . '"'); 75 | 76 | $this->con = new mysqli($this->dbhost, $this->dbuser, $this->dbpwd); 77 | if ($this->con->connect_errno) { 78 | $this->errorMessage('Cannot connect to ' . $this->dbhost . ' (' . $this->con->connect_error); 79 | return false; 80 | } 81 | $utf = $this->db_query('SET NAMES utf8'); 82 | 83 | if (!is_dir($this->backupDir) || !is_writable($this->backupDir)) { 84 | $this->errorMessage('The temporary directory you have configured (' . $this->backupDir . ') is either non existant or not writable'); 85 | return false; 86 | } 87 | 88 | if (!is_dir($this->backupRepository)) { 89 | $mr = @mkdir($this->backupRepository, 0755, true); 90 | if (!$mr) { 91 | $this->errorMessage('Cannot create the Repository ' . $this->backupRepository); 92 | return false; 93 | } 94 | } 95 | 96 | if (!is_writable($this->backupRepository)) { 97 | $this->errorMessage('Cannot write to Repository ' . $this->backupRepository); 98 | return false; 99 | } 100 | 101 | if (is_dir($this->backupDir . '/' . $this->backupFormat)) { 102 | $this->recursive_remove_directory($this->backupDir . '/' . $this->backupFormat); 103 | } 104 | 105 | $this->header = '-- PHP-MySql Dump' . $this->lineEnd; 106 | $this->header .= '-- Host: ' . $this->dbhost . $this->lineEnd; 107 | $this->header .= '-- Date: ' . date('F j, Y, g:i a') . $this->lineEnd; 108 | $this->header .= '-- -------------------------------------------------' . $this->lineEnd; 109 | 110 | $sql = $this->db_query('SELECT VERSION()'); 111 | $row = $sql->fetch_array(MYSQLI_NUM); 112 | $this->header .= '-- Server version ' . $row[0] . $this->lineEnd . $this->lineEnd; 113 | 114 | $this->header .= '/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;' . $this->lineEnd; 115 | $this->header .= '/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;' . $this->lineEnd; 116 | $this->header .= '/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;' . $this->lineEnd; 117 | $this->header .= '/*!40101 SET NAMES utf8 */;' . $this->lineEnd; 118 | $this->header .= '/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;' . $this->lineEnd; 119 | $this->header .= '/*!40103 SET TIME_ZONE=\'+00:00\' */;' . $this->lineEnd; 120 | $this->header .= '/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;' . $this->lineEnd; 121 | $this->header .= '/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;' . $this->lineEnd; 122 | $this->header .= '/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE=\'NO_AUTO_VALUE_ON_ZERO\' */;' . $this->lineEnd; 123 | $this->header .= '/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;' . $this->lineEnd; 124 | 125 | array_push($this->ignoreDatabases, 'information_schema'); 126 | array_push($this->emptyTables, 'mysql.general_log'); 127 | array_push($this->emptyTables, 'mysql.slow_log'); 128 | 129 | $tmp = []; 130 | foreach ($this->includeDatabases as $e) { 131 | array_push($tmp, str_replace('_STARREDMATCH_', '(.*)', preg_quote(str_replace('*', '_STARREDMATCH_', $e), '/'))); 132 | } 133 | $this->includeDatabases = '(' . implode($tmp, '|') . ')'; 134 | 135 | $tmp = []; 136 | foreach ($this->emptyTables as $e) { 137 | array_push($tmp, str_replace('_STARREDMATCH_', '(.*)', preg_quote(str_replace('*', '_STARREDMATCH_', $e), '/'))); 138 | } 139 | $this->emptyTables = '(' . implode($tmp, '|') . ')'; 140 | 141 | $tmp = []; 142 | foreach ($this->ignoreTables as $e) { 143 | array_push($tmp, str_replace('_STARREDMATCH_', '(.*)', preg_quote(str_replace('*', '_STARREDMATCH_', $e), '/'))); 144 | } 145 | $this->ignoreTables = '(' . implode($tmp, '|') . ')'; 146 | 147 | $tmp = []; 148 | foreach ($this->ignoreDatabases as $e) { 149 | array_push($tmp, str_replace('_STARREDMATCH_', '(.*)', preg_quote(str_replace('*', '_STARREDMATCH_', $e), '/'))); 150 | } 151 | $this->ignoreDatabases = '(' . implode($tmp, '|') . ')'; 152 | 153 | $q = $this->db_query('SHOW DATABASES'); 154 | while ($row = $q->fetch_array(MYSQLI_NUM)) { 155 | if (preg_match('/^' . $this->includeDatabases . '$/', $row[0])) { 156 | if (preg_match('/^' . $this->ignoreDatabases . '$/', $row[0])) { 157 | $this->debug('- Ignoring database ' . $row[0]); 158 | if (is_dir($this->backupRepository . '/' . $row[0])) { // Remove reposity copy of excluded databases if any 159 | $this->debug('- found old repository of ' . $row[0] . ' - deleting'); 160 | $this->recursive_remove_directory($this->backupRepository . '/' . $row[0]); 161 | } 162 | } else { 163 | $this->liveDatabases[$row[0]] = []; 164 | $this->syncTables($row[0]); 165 | } 166 | } 167 | } 168 | $this->debug('- Closing MySQL connection'); 169 | $this->con->close(); 170 | 171 | // now we remove any old databases 172 | $dir_handle = @opendir($this->backupRepository) or die("Unable to open $path"); 173 | while ($dir = readdir($dir_handle)) { 174 | if ($dir != '.' && $dir != '..' && is_dir($this->backupRepository . '/' . $dir)) { 175 | if (!isset($this->liveDatabases[$dir])) { 176 | $this->debug('- Found old database - deleting ' . $dir); 177 | $this->recursive_remove_directory($this->backupRepository . '/' . $dir); 178 | } 179 | } 180 | } 181 | @closedir($this->dumpDir); 182 | 183 | $this->generateDbDumps(); 184 | 185 | $this->debug('Compressing backups'); 186 | exec('cd ' . $this->backupDir . ' ; ' . $this->tar_binary . ' -Jcf ' . $this->backupDir . '/' . $this->backupFormat . '.tar.xz ' . $this->backupFormat . ' > /dev/null'); 187 | chmod($this->backupDir . '/' . $this->backupFormat . '.tar.xz', $this->savePermissions); 188 | if (!$this->recursive_remove_directory($this->backupDir . '/' . $this->backupFormat)) { 189 | $this->errorMessage('Cannot delete the directory ' . $this->backupDir . '/' . $this->backupFormat); 190 | return false; 191 | } 192 | 193 | $this->debug('Deleting old backups'); 194 | $this->rotateFiles($this->backupDir); 195 | } 196 | 197 | /** 198 | * Sync tables 199 | */ 200 | private function syncTables($db) 201 | { 202 | $this->dumpDir = $this->backupRepository . '/' . $db; 203 | if (!is_dir($this->dumpDir)) { 204 | mkdir($this->dumpDir, 0755); 205 | } 206 | 207 | $d = $this->con->select_db($db); 208 | if (!$d) { 209 | $this->errorMessage('Cannot open database `' . $db . '`'); 210 | return false; 211 | } 212 | $tbls = $this->db_query('SHOW TABLE STATUS FROM `' . $db . '`'); 213 | $existingDBs = []; 214 | 215 | while ($row = $tbls->fetch_array(MYSQLI_ASSOC)) { 216 | $tblName = $row['Name']; 217 | $tblUpdate = $row['Update_time']; 218 | 219 | $cssql = $this->db_query('CHECKSUM TABLE `' . $tblName . '`'); 220 | 221 | while ($csrow = $cssql->fetch_array(MYSQLI_ASSOC)) { 222 | $tblChecksum = $csrow['Checksum']; 223 | } 224 | 225 | if ($tblChecksum == null || preg_match('/^' . $this->emptyTables . '$/', $db . '.' . $tblName)) { 226 | $tblChecksum = 0; 227 | } 228 | 229 | // create create checksum 230 | $create_sql = $this->db_query('SHOW CREATE TABLE `' . $tblName . '`'); 231 | while ($create = $create_sql->fetch_array(MYSQLI_ASSOC)) { 232 | $tblChecksum .= '-' . substr(base_convert(md5($create['Create Table']), 16, 32), 0, 12); 233 | } 234 | 235 | if ($row['Engine'] == null) { 236 | $row['Engine'] = 'View'; 237 | } 238 | 239 | if (preg_match('/^' . $this->ignoreTables . '$/', $db . '.' . $tblName)) { 240 | $this->debug('- Ignoring table ' . $db . '.' . $tblName); 241 | } elseif (is_file($this->dumpDir . '/' . $tblName . '.' . $tblChecksum . '.' . strtolower($row['Engine']) . '.sql')) { 242 | $this->debug('- Repo version of ' . $db . '.' . $tblName . ' is current (' . $row['Engine'] . ')'); 243 | array_push($this->liveDatabases[$db], $tblName . '.' . $tblChecksum . '.' . strtolower($row['Engine'])); 244 | } else { 245 | array_push($this->liveDatabases[$db], $tblName . '.' . $tblChecksum . '.' . strtolower($row['Engine'])); // For later check & delete of missing ones 246 | $this->debug('+ Backing up new version of ' . $db . '.' . $tblName . ' (' . $row['Engine'] . ')'); 247 | 248 | $dump_options = [ 249 | '-C', // compress connection 250 | '-h' . $this->dbhost, // host 251 | '-u' . $this->dbuser, // user 252 | '--compact', // no need for database info for every table 253 | ]; 254 | 255 | if ($this->hex4blob) { 256 | array_push($dump_options, '--hex-blob'); 257 | } 258 | 259 | if (!$this->dropTables) { 260 | array_push($dump_options, '--skip-add-drop-table'); 261 | } 262 | 263 | if (strtolower($row['Engine']) == 'csv') { 264 | $this->debug('- Skipping table locks for CSV table ' . $db . '.' . $tblName); 265 | array_push($dump_options, '--skip-lock-tables'); 266 | } 267 | 268 | if (preg_match('/^' . $this->emptyTables . '$/', $db . '.' . $tblName)) { 269 | $this->debug('- Ignoring data for ' . $db . '.' . $tblName); 270 | array_push($dump_options, '--no-data'); 271 | } elseif (strtolower($row['Engine']) == 'memory') { 272 | $this->debug('- Ignoring data for Memory table ' . $db . '.' . $tblName); 273 | array_push($dump_options, '--no-data'); 274 | } elseif (strtolower($row['Engine']) == 'view') { 275 | $this->debug('- Ignoring data for View table ' . $db . '.' . $tblName); 276 | array_push($dump_options, '--no-data'); 277 | } 278 | 279 | $temp = tempnam(sys_get_temp_dir(), 'sqlbackup-'); 280 | 281 | putenv('MYSQL_PWD=' . $this->dbpwd); 282 | 283 | $exec = passthru($this->mysqldump_binary . ' ' . implode($dump_options, ' ') . ' ' . $db . ' ' . $tblName . ' > ' . $temp); 284 | if ($exec != '') { 285 | @unlink($temp); 286 | $this->errorMessage('Unable to dump file to ' . $temp . ' ' . $exec); 287 | } else { 288 | // make sure only complete files get saved 289 | chmod($temp, $this->savePermissions); 290 | rename($temp, $this->dumpDir . '/' . $row['Name'] . '.' . $tblChecksum . '.' . strtolower($row['Engine']) . '.sql'); 291 | // set the file timestamp if supported 292 | if (!is_null($row['Update_time'])) { 293 | @touch($this->dumpDir . '/' . $row['Name'] . '.' . $tblChecksum . '.' . strtolower($row['Engine']) . '.sql', strtotime($row['Update_time'])); 294 | } 295 | } 296 | } 297 | } 298 | 299 | // delete old tables if existing 300 | $dir_handle = @opendir($this->dumpDir) or die("Unable to open $path\n"); 301 | while ($file = readdir($dir_handle)) { 302 | if ($file != '.' && $file != '..') { 303 | if (!in_array(substr($file, 0, -4), $this->liveDatabases[$db])) { 304 | $this->debug('- Found old table - deleting ' . $file); 305 | unlink($this->dumpDir . '/' . $file); 306 | } 307 | } 308 | } 309 | @closedir($this->dumpDir); 310 | } 311 | 312 | /** 313 | * Generate database dumps 314 | */ 315 | private function generateDbDumps() 316 | { 317 | $mr = @mkdir($this->backupDir . '/' . $this->backupFormat, 0755, true); 318 | if (!$mr) { 319 | $this->errorMessage('Cannot create the backup directory ' . $this->backupFormat); 320 | return false; 321 | } 322 | 323 | $dirs = []; 324 | 325 | $dir_handle = @opendir($this->backupRepository) or die('Unable to open ' . $this->backupRepository); 326 | while ($dir = readdir($dir_handle)) { 327 | if ($dir != '.' && $dir != '..' && is_dir($this->backupRepository . '/' . $dir)) { 328 | array_push($dirs, $dir); 329 | } 330 | } 331 | 332 | closedir($dir_handle); 333 | sort($dirs); 334 | foreach ($dirs as $db) { 335 | $returnSql = $this->header; 336 | if ($this->createDatabase) { 337 | $returnSql .= '/*!40000 DROP DATABASE IF EXISTS `' . $db . '`*/; ' . $this->lineEnd; 338 | $returnSql .= 'CREATE DATABASE `' . $db . '`;' . $this->lineEnd . $this->lineEnd; 339 | $returnSql .= 'USE `' . $db . '`;' . $this->lineEnd . $this->lineEnd; 340 | } 341 | $fp = @fopen($this->backupDir . '/' . $this->backupFormat . '/' . $db . '.sql', 'wb'); 342 | @fwrite($fp, $returnSql); 343 | @fclose($fp); 344 | 345 | $files = scandir($this->backupRepository . '/' . $db); 346 | $viewsql = ''; 347 | $standardsql = ''; 348 | $sqlfiles = []; 349 | $viewfiles = []; 350 | foreach ($files as $file) { 351 | if (preg_match('/^([a-zA-Z0-9_\-]+)\.([0-9]+)\-([a-z0-9]+)\.([a-z0-9]+)\.sql/', $file, $sqlmatch)) { 352 | if ($sqlmatch[3] == 'view') { 353 | array_push($viewfiles, $this->backupRepository . '/' . $db . '/' . $file); 354 | } else { 355 | array_push($sqlfiles, $this->backupRepository . '/' . $db . '/' . $file); 356 | } 357 | } 358 | } 359 | 360 | /* Add all sql dumps in database */ 361 | foreach ($sqlfiles as $f) { 362 | $this->chunked_copy_to($f, $this->backupDir . '/' . $this->backupFormat . '/' . $db . '.sql'); 363 | } 364 | 365 | /* Add View tables after */ 366 | foreach ($viewfiles as $f) { 367 | $this->chunked_copy_to($f, $this->backupDir . '/' . $this->backupFormat . '/' . $db . '.sql'); 368 | } 369 | } 370 | } 371 | 372 | /** 373 | * Chunk copy files to database backups 374 | * To prevent memory overload, fila A is copied 10MB at a time to file B 375 | */ 376 | private function chunked_copy_to($from, $to) 377 | { 378 | $buffer_size = 10485760; // 10 megs at a time, you can adjust this. 379 | $ret = 0; 380 | $fin = fopen($from, 'rb'); 381 | $fout = fopen($to, 'a'); 382 | if (!$fin || !$fout) { 383 | die('Unable to copy ' . $fin . ' to ' . $fout); 384 | } 385 | while (!feof($fin)) { 386 | $ret += fwrite($fout, fread($fin, $buffer_size)); 387 | } 388 | fclose($fin); 389 | fclose($fout); 390 | return $ret; // return number of bytes written 391 | } 392 | 393 | /** 394 | * Rotate Backups 395 | */ 396 | private function rotateFiles($backup_directory) 397 | { 398 | $filelist = []; 399 | if (is_dir($backup_directory)) { 400 | if ($dh = opendir($backup_directory)) { 401 | while (($file = readdir($dh)) !== false) { 402 | if (($file != '.') && ($file != '..') && (filetype($backup_directory . '/' . $file) == 'file')) { 403 | $filelist[] = $file; 404 | } 405 | } 406 | closedir($dh); 407 | sort($filelist); // Make sure it's listed in the correct order 408 | if (count($filelist) > $this->backupsToKeep) { 409 | $too_many = (count($filelist) - $this->backupsToKeep); 410 | for ($j = 0; $j < $too_many; $j++) { 411 | unlink($backup_directory . '/' . $filelist[$j]); 412 | } 413 | } 414 | unset($filelist); // Uset $filelist[] array 415 | } 416 | } 417 | } 418 | 419 | private function errorMessage($msg) 420 | { 421 | echo $msg . $this->lineEnd; 422 | } 423 | 424 | private function debug($msg) 425 | { 426 | if ($this->showDebug) { 427 | echo $msg . $this->lineEnd; 428 | } 429 | } 430 | 431 | private function db_query($query) 432 | { 433 | if ($result = $this->con->query($query)) { 434 | return $result; 435 | } else { 436 | $this->errorMessage($this->con->error); 437 | return false; 438 | } 439 | } 440 | 441 | private function recursive_remove_directory($directory, $empty = false) 442 | { 443 | if (substr($directory, -1) == '/') { 444 | $directory = substr($directory, 0, -1); 445 | } 446 | if (!file_exists($directory) || !is_dir($directory)) { 447 | return false; 448 | } elseif (!is_readable($directory)) { 449 | return false; 450 | } else { 451 | $handle = opendir($directory); 452 | while (false !== ($item = readdir($handle))) { 453 | if ($item != '.' && $item != '..') { 454 | $path = $directory . '/' . $item; 455 | if (is_dir($path)) { 456 | $this->recursive_remove_directory($path); 457 | } else { 458 | unlink($path); 459 | } 460 | } 461 | } 462 | closedir($handle); 463 | if ($empty == false) { 464 | if (!rmdir($directory)) { 465 | return false; 466 | } 467 | } 468 | return true; 469 | } 470 | } 471 | } 472 | --------------------------------------------------------------------------------