BackupRollback.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Framework\Setup;
  7. use Magento\Framework\App\Filesystem\DirectoryList;
  8. use Magento\Framework\Backup\Exception\NotEnoughPermissions;
  9. use Magento\Framework\Backup\Factory;
  10. use Magento\Framework\Backup\Filesystem\Helper;
  11. use Magento\Framework\Exception\LocalizedException;
  12. use Magento\Framework\Filesystem\Driver\File;
  13. use Magento\Framework\ObjectManagerInterface;
  14. use Magento\Framework\Phrase;
  15. /**
  16. * Class to deal with backup and rollback functionality for database and Code base
  17. *
  18. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  19. */
  20. class BackupRollback
  21. {
  22. /**
  23. * Default backup directory
  24. */
  25. const DEFAULT_BACKUP_DIRECTORY = 'backups';
  26. /**
  27. * Path to backup folder
  28. *
  29. * @var string
  30. */
  31. private $backupsDir;
  32. /**
  33. * Object Manager
  34. *
  35. * @var ObjectManagerInterface
  36. */
  37. private $objectManager;
  38. /**
  39. * Logger
  40. *
  41. * @var LoggerInterface
  42. */
  43. private $log;
  44. /**
  45. * Filesystem Directory List
  46. *
  47. * @var DirectoryList
  48. */
  49. private $directoryList;
  50. /**
  51. * File
  52. *
  53. * @var File
  54. */
  55. private $file;
  56. /**
  57. * Filesystem Helper
  58. *
  59. * @var Helper
  60. */
  61. private $fsHelper;
  62. /**
  63. * Constructor
  64. *
  65. * @param ObjectManagerInterface $objectManager
  66. * @param LoggerInterface $log
  67. * @param DirectoryList $directoryList
  68. * @param File $file
  69. * @param Helper $fsHelper
  70. */
  71. public function __construct(
  72. ObjectManagerInterface $objectManager,
  73. LoggerInterface $log,
  74. DirectoryList $directoryList,
  75. File $file,
  76. Helper $fsHelper
  77. ) {
  78. $this->objectManager = $objectManager;
  79. $this->log = $log;
  80. $this->directoryList = $directoryList;
  81. $this->file = $file;
  82. $this->fsHelper = $fsHelper;
  83. $this->backupsDir = $this->directoryList->getPath(DirectoryList::VAR_DIR)
  84. . '/' . self::DEFAULT_BACKUP_DIRECTORY;
  85. }
  86. /**
  87. * Take backup for code base
  88. *
  89. * @param int $time
  90. * @param string $type
  91. * @return string
  92. * @throws LocalizedException
  93. */
  94. public function codeBackup($time, $type = Factory::TYPE_FILESYSTEM)
  95. {
  96. /** @var \Magento\Framework\Backup\Filesystem $fsBackup */
  97. $fsBackup = $this->objectManager->create(\Magento\Framework\Backup\Filesystem::class);
  98. $fsBackup->setRootDir($this->directoryList->getRoot());
  99. if ($type === Factory::TYPE_FILESYSTEM) {
  100. $fsBackup->addIgnorePaths($this->getCodeBackupIgnorePaths());
  101. $granularType = 'Code';
  102. $fsBackup->setName('code');
  103. } elseif ($type === Factory::TYPE_MEDIA) {
  104. $fsBackup->addIgnorePaths($this->getMediaBackupIgnorePaths());
  105. $granularType = 'Media';
  106. $fsBackup->setName('media');
  107. } else {
  108. throw new LocalizedException(new Phrase("This backup type \'$type\' is not supported."));
  109. }
  110. if (!$this->file->isExists($this->backupsDir)) {
  111. $this->file->createDirectory($this->backupsDir);
  112. }
  113. $fsBackup->setBackupsDir($this->backupsDir);
  114. $fsBackup->setBackupExtension('tgz');
  115. $fsBackup->setTime($time);
  116. $this->log->log($granularType . ' backup is starting...');
  117. $fsBackup->create();
  118. $this->log->log(
  119. $granularType . ' backup filename: ' . $fsBackup->getBackupFilename()
  120. . ' (The archive can be uncompressed with 7-Zip on Windows systems)'
  121. );
  122. $this->log->log($granularType . ' backup path: ' . $fsBackup->getBackupPath());
  123. $this->log->logSuccess($granularType . ' backup completed successfully.');
  124. return $fsBackup->getBackupPath();
  125. }
  126. /**
  127. * Roll back code base
  128. *
  129. * @param string $rollbackFile
  130. * @param string $type
  131. * @param boolean $keepSourceFile
  132. * @return void
  133. * @throws LocalizedException
  134. */
  135. public function codeRollback($rollbackFile, $type = Factory::TYPE_FILESYSTEM, $keepSourceFile = false)
  136. {
  137. if (preg_match('/[0-9]_(filesystem)_(code|media)\.(tgz)$/', $rollbackFile) !== 1) {
  138. throw new LocalizedException(new Phrase('The rollback file is invalid. Verify the file and try again.'));
  139. }
  140. if (!$this->file->isExists($this->backupsDir . '/' . $rollbackFile)) {
  141. throw new LocalizedException(new Phrase("The rollback file doesn't exist. Verify the file and try again."));
  142. }
  143. /** @var \Magento\Framework\Backup\Filesystem $fsRollback */
  144. $fsRollback = $this->objectManager->create(\Magento\Framework\Backup\Filesystem::class);
  145. if ($type === Factory::TYPE_FILESYSTEM) {
  146. $ignorePaths = $this->getCodeBackupIgnorePaths();
  147. $granularType = 'Code';
  148. $fsRollback->setName('code');
  149. } elseif ($type === Factory::TYPE_MEDIA) {
  150. $ignorePaths = $this->getMediaBackupIgnorePaths();
  151. $granularType = 'Media';
  152. $fsRollback->setName('media');
  153. } else {
  154. throw new LocalizedException(new Phrase("This backup type '$type' is not supported."));
  155. }
  156. $filesInfo = $this->fsHelper->getInfo(
  157. $this->directoryList->getRoot(),
  158. Helper::INFO_WRITABLE,
  159. $ignorePaths
  160. );
  161. if (!$filesInfo['writable']) {
  162. throw new NotEnoughPermissions(
  163. new Phrase("The rollback can't be executed because not all files are writable.")
  164. );
  165. }
  166. $fsRollback->setRootDir($this->directoryList->getRoot());
  167. $fsRollback->addIgnorePaths($ignorePaths);
  168. $fsRollback->setBackupsDir($this->backupsDir);
  169. $fsRollback->setBackupExtension('tgz');
  170. $time = explode('_', $rollbackFile);
  171. $fsRollback->setKeepSourceFile($keepSourceFile);
  172. $fsRollback->setTime($time[0]);
  173. $this->log->log($granularType . ' rollback is starting ...');
  174. $fsRollback->rollback();
  175. $this->log->log($granularType . ' rollback filename: ' . $fsRollback->getBackupFilename());
  176. $this->log->log($granularType . ' rollback file path: ' . $fsRollback->getBackupPath());
  177. $this->log->logSuccess($granularType . ' rollback completed successfully.');
  178. }
  179. /**
  180. * Take backup for database
  181. *
  182. * @param int $time
  183. * @return string
  184. */
  185. public function dbBackup($time)
  186. {
  187. /** @var \Magento\Framework\Backup\Db $dbBackup */
  188. $dbBackup = $this->objectManager->create(\Magento\Framework\Backup\Db::class);
  189. $dbBackup->setRootDir($this->directoryList->getRoot());
  190. if (!$this->file->isExists($this->backupsDir)) {
  191. $this->file->createDirectory($this->backupsDir);
  192. }
  193. $dbBackup->setBackupsDir($this->backupsDir);
  194. $dbBackup->setBackupExtension('sql');
  195. $dbBackup->setTime($time);
  196. $this->log->log('DB backup is starting...');
  197. $dbBackup->create();
  198. $this->log->log('DB backup filename: ' . $dbBackup->getBackupFilename());
  199. $this->log->log('DB backup path: ' . $dbBackup->getBackupPath());
  200. $this->log->logSuccess('DB backup completed successfully.');
  201. return $dbBackup->getBackupPath();
  202. }
  203. /**
  204. * Roll back database
  205. *
  206. * @param string $rollbackFile
  207. * @param boolean $keepSourceFile
  208. * @return void
  209. * @throws LocalizedException
  210. */
  211. public function dbRollback($rollbackFile, $keepSourceFile = false)
  212. {
  213. if (preg_match('/[0-9]_(db)(.*?).(sql)$/', $rollbackFile) !== 1) {
  214. throw new LocalizedException(new Phrase('The rollback file is invalid. Verify the file and try again.'));
  215. }
  216. if (!$this->file->isExists($this->backupsDir . '/' . $rollbackFile)) {
  217. throw new LocalizedException(new Phrase("The rollback file doesn't exist. Verify the file and try again."));
  218. }
  219. /** @var \Magento\Framework\Backup\Db $dbRollback */
  220. $dbRollback = $this->objectManager->create(\Magento\Framework\Backup\Db::class);
  221. $dbRollback->setRootDir($this->directoryList->getRoot());
  222. $dbRollback->setBackupsDir($this->backupsDir);
  223. $dbRollback->setBackupExtension('sql');
  224. $time = explode('_', $rollbackFile);
  225. if (count($time) === 3) {
  226. $thirdPart = explode('.', $time[2]);
  227. $dbRollback->setName($thirdPart[0]);
  228. }
  229. $dbRollback->setTime($time[0]);
  230. $this->log->log('DB rollback is starting...');
  231. $dbRollback->setKeepSourceFile($keepSourceFile);
  232. $dbRollback->setResourceModel($this->objectManager->create(\Magento\Backup\Model\ResourceModel\Db::class));
  233. if ($dbRollback->getBackupFilename() !== $rollbackFile) {
  234. $correctName = $this->getCorrectFileNameWithoutPrefix($dbRollback, $rollbackFile);
  235. $dbRollback->setName($correctName);
  236. }
  237. $dbRollback->rollback();
  238. $this->log->log('DB rollback filename: ' . $dbRollback->getBackupFilename());
  239. $this->log->log('DB rollback path: ' . $dbRollback->getBackupPath());
  240. $this->log->logSuccess('DB rollback completed successfully.');
  241. }
  242. /**
  243. * Get paths that should be excluded during iterative searches for locations for code backup only
  244. *
  245. * @return array
  246. */
  247. private function getCodeBackupIgnorePaths()
  248. {
  249. return [
  250. $this->directoryList->getPath(DirectoryList::MEDIA),
  251. $this->directoryList->getPath(DirectoryList::STATIC_VIEW),
  252. $this->directoryList->getPath(DirectoryList::VAR_DIR),
  253. $this->directoryList->getRoot() . '/update',
  254. $this->directoryList->getRoot() . '/node_modules',
  255. $this->directoryList->getRoot() . '/.grunt',
  256. $this->directoryList->getRoot() . '/.idea',
  257. $this->directoryList->getRoot() . '/.svn',
  258. $this->directoryList->getRoot() . '/.git'
  259. ];
  260. }
  261. /**
  262. * Get paths that should be excluded during iterative searches for locations for media backup only
  263. *
  264. * @return array
  265. */
  266. private function getMediaBackupIgnorePaths()
  267. {
  268. $ignorePaths = [];
  269. foreach (new \DirectoryIterator($this->directoryList->getRoot()) as $item) {
  270. if (!$item->isDot() && ($this->directoryList->getPath(DirectoryList::PUB) !== $item->getPathname())) {
  271. $ignorePaths[] = str_replace('\\', '/', $item->getPathname());
  272. }
  273. }
  274. foreach (new \DirectoryIterator($this->directoryList->getPath(DirectoryList::PUB)) as $item) {
  275. if (!$item->isDot() && ($this->directoryList->getPath(DirectoryList::MEDIA) !== $item->getPathname())) {
  276. $ignorePaths[] = str_replace('\\', '/', $item->getPathname());
  277. }
  278. }
  279. return $ignorePaths;
  280. }
  281. /**
  282. * Get disk availability for filesystem backup
  283. *
  284. * @param string $type
  285. * @return int
  286. * @throws LocalizedException
  287. */
  288. public function getFSDiskSpace($type = Factory::TYPE_FILESYSTEM)
  289. {
  290. $filesystemSize = 0;
  291. if ($type === Factory::TYPE_FILESYSTEM) {
  292. $ignorePaths = $this->getCodeBackupIgnorePaths();
  293. } elseif ($type === Factory::TYPE_MEDIA) {
  294. $ignorePaths = $this->getMediaBackupIgnorePaths();
  295. } else {
  296. throw new LocalizedException(new Phrase("This backup type '$type' is not supported."));
  297. }
  298. $filesInfo = $this->fsHelper->getInfo(
  299. $this->directoryList->getRoot(),
  300. Helper::INFO_SIZE,
  301. $ignorePaths
  302. );
  303. if ($filesInfo['size']) {
  304. $filesystemSize = $filesInfo['size'];
  305. }
  306. return $filesystemSize;
  307. }
  308. /**
  309. * Get disk availability for database backup
  310. *
  311. * @return int
  312. * @throws LocalizedException
  313. */
  314. public function getDBDiskSpace()
  315. {
  316. /** @var \Magento\Framework\Backup\Db $dbBackup */
  317. $dbBackup = $this->objectManager->create(\Magento\Framework\Backup\Db::class);
  318. return $dbBackup->getDBSize();
  319. }
  320. /**
  321. * Get correct file name without prefix.
  322. *
  323. * @param \Magento\Framework\Backup\Db $dbRollback
  324. * @param string $rollbackFile
  325. *
  326. * @return string
  327. */
  328. private function getCorrectFileNameWithoutPrefix(\Magento\Framework\Backup\Db $dbRollback, $rollbackFile)
  329. {
  330. $namePrefix = $dbRollback->getTime() . '_' . $dbRollback->getType();
  331. //delete prefix.
  332. $fileNameWithoutPrefix = str_replace($namePrefix, '', $rollbackFile);
  333. //change '_' to ' '.
  334. $fileNameWithoutPrefix = str_replace('_', ' ', $fileNameWithoutPrefix);
  335. //delete file extension.
  336. $fileNameWithoutPrefix = pathinfo($fileNameWithoutPrefix, PATHINFO_FILENAME);
  337. return $fileNameWithoutPrefix;
  338. }
  339. }