1<?php 2/** 3 * 2007-2016 PrestaShop 4 * 5 * thirty bees is an extension to the PrestaShop e-commerce software developed by PrestaShop SA 6 * Copyright (C) 2017-2018 thirty bees 7 * 8 * NOTICE OF LICENSE 9 * 10 * This source file is subject to the Open Software License (OSL 3.0) 11 * that is bundled with this package in the file LICENSE.txt. 12 * It is also available through the world-wide-web at this URL: 13 * http://opensource.org/licenses/osl-3.0.php 14 * If you did not receive a copy of the license and are unable to 15 * obtain it through the world-wide-web, please send an email 16 * to license@thirtybees.com so we can send you a copy immediately. 17 * 18 * DISCLAIMER 19 * 20 * Do not edit or add to this file if you wish to upgrade PrestaShop to newer 21 * versions in the future. If you wish to customize PrestaShop for your 22 * needs please refer to https://www.thirtybees.com for more information. 23 * 24 * @author thirty bees <contact@thirtybees.com> 25 * @author PrestaShop SA <contact@prestashop.com> 26 * @copyright 2017-2018 thirty bees 27 * @copyright 2007-2016 PrestaShop SA 28 * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) 29 * PrestaShop is an internationally registered trademark & property of PrestaShop SA 30 */ 31 32/** 33 * Class PrestaShopBackupCore 34 * 35 * @since 1.0.0 36 */ 37class PrestaShopBackupCore 38{ 39 // @codingStandardsIgnoreStart 40 /** @var string default backup directory. */ 41 public static $backupDir = '/backups/'; 42 /** @var int Object id */ 43 public $id; 44 /** @var string Last error messages */ 45 public $error; 46 /** @var string custom backup directory. */ 47 public $customBackupDir = null; 48 /** @var bool|string $psBackupAll */ 49 public $psBackupAll = true; 50 /** @var bool|string $psBackupDropTable */ 51 public $psBackupDropTable = true; 52 // @codingStandardsIgnoreEnd 53 54 /** 55 * Creates a new backup object 56 * 57 * @param string $filename Filename of the backup file 58 * 59 * @since 1.0.0 60 * @version 1.0.0 Initial version 61 * @throws PrestaShopException 62 */ 63 public function __construct($filename = null) 64 { 65 if ($filename) { 66 $this->id = $this->getRealBackupPath($filename); 67 } 68 69 $psBackupAll = Configuration::get('PS_BACKUP_ALL'); 70 $psBackupDropTable = Configuration::get('PS_BACKUP_DROP_TABLE'); 71 $this->psBackupAll = $psBackupAll !== false ? $psBackupAll : true; 72 $this->psBackupDropTable = $psBackupDropTable !== false ? $psBackupDropTable : true; 73 } 74 75 /** 76 * get the path to use for backup (customBackupDir if specified, or default) 77 * 78 * @param string $filename filename to use 79 * 80 * @return string full path 81 * 82 * @since 1.0.0 83 * @version 1.0.0 Initial version 84 */ 85 public function getRealBackupPath($filename = null) 86 { 87 $backupDir = static::getBackupPath($filename); 88 if (!empty($this->customBackupDir)) { 89 $backupDir = str_replace( 90 _PS_ADMIN_DIR_.static::$backupDir, 91 _PS_ADMIN_DIR_.$this->customBackupDir, 92 $backupDir 93 ); 94 95 if (strrpos($backupDir, DIRECTORY_SEPARATOR)) { 96 $backupDir .= DIRECTORY_SEPARATOR; 97 } 98 } 99 100 return $backupDir; 101 } 102 103 /** 104 * Get the full path of the backup file 105 * 106 * @param string $filename prefix of the backup file (datetime will be the second part) 107 * 108 * @return string The full path of the backup file, or false if the backup file does not exists 109 * 110 * @since 1.0.0 111 * @version 1.0.0 Initial version 112 */ 113 public static function getBackupPath($filename = '') 114 { 115 $backupdir = realpath(_PS_ADMIN_DIR_.static::$backupDir); 116 117 if ($backupdir === false) { 118 die(Tools::displayError('"Backup" directory does not exist.')); 119 } 120 121 // Check the realpath so we can validate the backup file is under the backup directory 122 if (!empty($filename)) { 123 $backupfile = realpath($backupdir.DIRECTORY_SEPARATOR.$filename); 124 } else { 125 $backupfile = $backupdir.DIRECTORY_SEPARATOR; 126 } 127 128 if ($backupfile === false || strncmp($backupdir, $backupfile, strlen($backupdir)) != 0) { 129 die(Tools::displayError()); 130 } 131 132 return $backupfile; 133 } 134 135 /** 136 * Check if a backup file exist 137 * 138 * @param string $filename prefix of the backup file (datetime will be the second part) 139 * 140 * @return bool true if backup file exist 141 * 142 * @since 1.0.0 143 * @version 1.0.0 Initial version 144 */ 145 public static function backupExist($filename) 146 { 147 $backupdir = realpath(_PS_ADMIN_DIR_.static::$backupDir); 148 149 if ($backupdir === false) { 150 die(Tools::displayError('"Backup" directory does not exist.')); 151 } 152 153 return @filemtime($backupdir.DIRECTORY_SEPARATOR.$filename); 154 } 155 156 /** 157 * you can set a different path with that function 158 * 159 * @TODO include the prefix name 160 * 161 * @param string $dir 162 * 163 * @return bool bo 164 * 165 * @since 1.0.0 166 * @version 1.0.0 Initial version 167 */ 168 public function setCustomBackupPath($dir) 169 { 170 $customDir = DIRECTORY_SEPARATOR.trim($dir, '/').DIRECTORY_SEPARATOR; 171 if (is_dir(_PS_ADMIN_DIR_.$customDir)) { 172 $this->customBackupDir = $customDir; 173 } else { 174 return false; 175 } 176 177 return true; 178 } 179 180 /** 181 * Get the URL used to retrieve this backup file 182 * 183 * @return string The url used to request the backup file 184 * 185 * @since 1.0.0 186 * @version 1.0.0 Initial version 187 */ 188 public function getBackupURL() 189 { 190 return __PS_BASE_URI__.basename(_PS_ADMIN_DIR_).'/backup.php?filename='.basename($this->id); 191 } 192 193 /** 194 * Deletes a range of backup files 195 * 196 * @return bool True on success 197 * 198 * @since 1.0.0 199 * @version 1.0.0 Initial version 200 */ 201 public function deleteSelection($list) 202 { 203 foreach ($list as $file) { 204 $backup = new self($file); 205 if (!$backup->delete()) { 206 $this->error = $backup->error; 207 208 return false; 209 } 210 } 211 212 return true; 213 } 214 215 /** 216 * Creates a new backup file 217 * 218 * @return bool true on successful backup 219 * 220 * @throws PrestaShopDatabaseException 221 * @throws PrestaShopException 222 * @since 1.0.0 223 * @version 1.0.0 Initial version 224 */ 225 public function add() 226 { 227 if (!$this->psBackupAll) { 228 $ignoreInsertTable = [ 229 _DB_PREFIX_.'connections', 230 _DB_PREFIX_.'connections_page', 231 _DB_PREFIX_.'connections_source', 232 _DB_PREFIX_.'guest', 233 _DB_PREFIX_.'statssearch', 234 ]; 235 } else { 236 $ignoreInsertTable = []; 237 } 238 239 // Generate some random number, to make it extra hard to guess backup file names 240 $rand = dechex(mt_rand(0, min(0xffffffff, mt_getrandmax()))); 241 $date = time(); 242 $backupfile = $this->getRealBackupPath().$date.'-'.$rand.'.sql'; 243 244 // Figure out what compression is available and open the file 245 if (function_exists('bzopen')) { 246 $backupfile .= '.bz2'; 247 $fp = @bzopen($backupfile, 'w'); 248 } elseif (function_exists('gzopen')) { 249 $backupfile .= '.gz'; 250 $fp = @gzopen($backupfile, 'w'); 251 } else { 252 $fp = @fopen($backupfile, 'w'); 253 } 254 255 if ($fp === false) { 256 echo Tools::displayError('Unable to create backup file').' "'.addslashes($backupfile).'"'; 257 258 return false; 259 } 260 261 $this->id = realpath($backupfile); 262 263 fwrite($fp, '/* Backup for '.Tools::getHttpHost(false, false).__PS_BASE_URI__."\n * at ".date($date)."\n */\n"); 264 fwrite($fp, "\n".'SET NAMES \'utf8\';'."\n\n"); 265 266 // Find all tables 267 $tables = Db::getInstance()->executeS('SHOW TABLES'); 268 $found = 0; 269 foreach ($tables as $table) { 270 $table = current($table); 271 272 // Skip tables which do not start with _DB_PREFIX_ 273 if (strlen($table) < strlen(_DB_PREFIX_) || strncmp($table, _DB_PREFIX_, strlen(_DB_PREFIX_)) != 0) { 274 continue; 275 } 276 277 // Export the table schema 278 $schema = Db::getInstance()->executeS('SHOW CREATE TABLE `'.$table.'`'); 279 280 if (count($schema) != 1 || !isset($schema[0]['Table']) || !isset($schema[0]['Create Table'])) { 281 fclose($fp); 282 $this->delete(); 283 echo Tools::displayError('An error occurred while backing up. Unable to obtain the schema of').' "'.$table; 284 285 return false; 286 } 287 288 fwrite($fp, '/* Scheme for table '.$schema[0]['Table']." */\n"); 289 290 if ($this->psBackupDropTable) { 291 fwrite($fp, 'DROP TABLE IF EXISTS `'.$schema[0]['Table'].'`;'."\n"); 292 } 293 294 fwrite($fp, $schema[0]['Create Table'].";\n\n"); 295 296 if (!in_array($schema[0]['Table'], $ignoreInsertTable)) { 297 $data = Db::getInstance()->query('SELECT * FROM `'.$schema[0]['Table'].'`'); 298 $sizeof = DB::getInstance()->NumRows(); 299 $lines = explode("\n", $schema[0]['Create Table']); 300 301 if ($data && $sizeof > 0) { 302 // Export the table data 303 fwrite($fp, 'INSERT INTO `'.$schema[0]['Table']."` VALUES\n"); 304 $i = 1; 305 while ($row = DB::getInstance()->nextRow($data)) { 306 $s = '('; 307 308 foreach ($row as $field => $value) { 309 $tmp = "'".pSQL($value, true)."',"; 310 if ($tmp != "'',") { 311 $s .= $tmp; 312 } else { 313 foreach ($lines as $line) { 314 if (strpos($line, '`'.$field.'`') !== false) { 315 if (preg_match('/(.*NOT NULL.*)/Ui', $line)) { 316 $s .= "'',"; 317 } else { 318 $s .= 'NULL,'; 319 } 320 break; 321 } 322 } 323 } 324 } 325 $s = rtrim($s, ','); 326 327 if ($i % 200 == 0 && $i < $sizeof) { 328 $s .= ");\nINSERT INTO `".$schema[0]['Table']."` VALUES\n"; 329 } elseif ($i < $sizeof) { 330 $s .= "),\n"; 331 } else { 332 $s .= ");\n"; 333 } 334 335 fwrite($fp, $s); 336 ++$i; 337 } 338 } 339 } 340 $found++; 341 } 342 343 fclose($fp); 344 if ($found == 0) { 345 $this->delete(); 346 echo Tools::displayError('No valid tables were found to backup.'); 347 348 return false; 349 } 350 351 return true; 352 } 353 354 /** 355 * Delete the current backup file 356 * 357 * @return bool Deletion result, true on success 358 * 359 * @since 1.0.0 360 * @version 1.0.0 Initial version 361 */ 362 public function delete() 363 { 364 if (!$this->id || !unlink($this->id)) { 365 $this->error = Tools::displayError('Error deleting').' '.($this->id ? '"'.$this->id.'"' : 366 Tools::displayError('Invalid ID')); 367 368 return false; 369 } 370 371 return true; 372 } 373} 374