main.itop-backup.php 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. <?php
  2. // Copyright (C) 2014-2016 Combodo SARL
  3. //
  4. // This program is free software; you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation; version 3 of the License.
  7. //
  8. // This program is distributed in the hope that it will be useful,
  9. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. // GNU General Public License for more details.
  12. //
  13. // You should have received a copy of the GNU General Public License
  14. // along with this program; if not, write to the Free Software
  15. // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  16. require_once(APPROOT.'setup/setuputils.class.inc.php');
  17. require_once(APPROOT.'setup/backup.class.inc.php');
  18. require_once(APPROOT.'core/mutex.class.inc.php');
  19. define('BACKUP_DEFAULT_FORMAT', '__DB__-%Y-%m-%d_%H_%M');
  20. class BackupHandler extends ModuleHandlerAPI
  21. {
  22. public static function OnMetaModelStarted()
  23. {
  24. try
  25. {
  26. $oRestoreMutex = new iTopMutex('restore.'.utils::GetCurrentEnvironment());
  27. if ($oRestoreMutex->IsLocked())
  28. {
  29. IssueLog::Info(__class__.'::'.__function__.' A user is trying to use iTop while a restore is running. The requested page is in read-only mode.');
  30. MetaModel::GetConfig()->Set('access_mode', ACCESS_READONLY, 'itop-backup');
  31. MetaModel::GetConfig()->Set('access_message', ' - '.dict::S('bkp-restore-running'), 'itop-backup');
  32. }
  33. }
  34. catch(Exception $e)
  35. {
  36. IssueLog::Error(__class__.'::'.__function__.' Failed to check if a backup/restore is running: '.$e->getMessage());
  37. }
  38. }
  39. }
  40. class DBBackupScheduled extends DBBackup
  41. {
  42. protected function LogInfo($sMsg)
  43. {
  44. static $bDebug = null;
  45. if ($bDebug == null)
  46. {
  47. $bDebug = MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'debug', false);
  48. }
  49. if ($bDebug)
  50. {
  51. echo $sMsg."\n";
  52. }
  53. }
  54. protected function LogError($sMsg)
  55. {
  56. static $bDebug = null;
  57. if ($bDebug == null)
  58. {
  59. $bDebug = MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'debug', false);
  60. }
  61. IssueLog::Error($sMsg);
  62. if ($bDebug)
  63. {
  64. echo 'Error: '.$sMsg."\n";
  65. }
  66. }
  67. /**
  68. * List and order by date the backups in the given directory
  69. * Note: the algorithm is currently based on the file modification date... because there is no "creation date" in general
  70. */
  71. public function ListFiles($sBackupDir)
  72. {
  73. $aFiles = array();
  74. $aTimes = array();
  75. foreach(glob($sBackupDir.'*.zip') as $sFilePath)
  76. {
  77. $aFiles[] = $sFilePath;
  78. $aTimes[] = filemtime($sFilePath); // unix time
  79. }
  80. array_multisort($aTimes, $aFiles);
  81. return $aFiles;
  82. }
  83. }
  84. class BackupExec implements iScheduledProcess
  85. {
  86. protected $sBackupDir;
  87. protected $iRetentionCount;
  88. /**
  89. * Constructor
  90. * @param sBackupDir string Target directory, defaults to APPROOT/data/backups/auto
  91. * @param iRetentionCount int Rotation (default to the value given in the configuration file 'retentation_count') set to 0 to disable this feature
  92. */
  93. public function __construct($sBackupDir = null, $iRetentionCount = null)
  94. {
  95. if (is_null($sBackupDir))
  96. {
  97. $this->sBackupDir = APPROOT.'data/backups/auto/';
  98. }
  99. else
  100. {
  101. $this->sBackupDir = $sBackupDir;
  102. }
  103. if (is_null($iRetentionCount))
  104. {
  105. $this->iRetentionCount = MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'retention_count', 5);
  106. }
  107. else
  108. {
  109. $this->iRetentionCount = $iRetentionCount;
  110. }
  111. }
  112. public function Process($iUnixTimeLimit)
  113. {
  114. $oMutex = new iTopMutex('backup.'.utils::GetCurrentEnvironment());
  115. $oMutex->Lock();
  116. try
  117. {
  118. // Make sure the target directory exists
  119. SetupUtils::builddir($this->sBackupDir);
  120. $oBackup = new DBBackupScheduled();
  121. // Eliminate files exceeding the retention setting
  122. //
  123. if ($this->iRetentionCount > 0)
  124. {
  125. $aFiles = $oBackup->ListFiles($this->sBackupDir);
  126. while (count($aFiles) >= $this->iRetentionCount)
  127. {
  128. $sFileToDelete = array_shift($aFiles);
  129. unlink($sFileToDelete);
  130. if (file_exists($sFileToDelete))
  131. {
  132. // Ok, do not loop indefinitely on this
  133. break;
  134. }
  135. }
  136. }
  137. // Do execute the backup
  138. //
  139. $oBackup->SetMySQLBinDir(MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'mysql_bindir', ''));
  140. $sBackupFile = MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'file_name_format', '__DB__-%Y-%m-%d_%H_%M');
  141. $sName = $oBackup->MakeName($sBackupFile);
  142. if ($sName == '')
  143. {
  144. $sName = $oBackup->MakeName(BACKUP_DEFAULT_FORMAT);
  145. }
  146. $sZipFile = $this->sBackupDir.$sName.'.zip';
  147. $sSourceConfigFile = APPCONF.utils::GetCurrentEnvironment().'/'.ITOP_CONFIG_FILE;
  148. $oBackup->CreateZip($sZipFile, $sSourceConfigFile);
  149. }
  150. catch (Exception $e)
  151. {
  152. $oMutex->Unlock();
  153. throw $e;
  154. }
  155. $oMutex->Unlock();
  156. return "Created the backup: $sZipFile";
  157. }
  158. /*
  159. Interpret current setting for the week days
  160. @returns array of int (monday = 1)
  161. */
  162. public function InterpretWeekDays()
  163. {
  164. static $aWEEKDAYTON = array('monday' => 1, 'tuesday' => 2, 'wednesday' => 3, 'thursday' => 4, 'friday' => 5, 'saturday' => 6, 'sunday' => 7);
  165. $aDays = array();
  166. $sWeekDays = MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'week_days', 'monday, tuesday, wednesday, thursday, friday');
  167. if ($sWeekDays != '')
  168. {
  169. $aWeekDaysRaw = explode(',', $sWeekDays);
  170. foreach ($aWeekDaysRaw as $sWeekDay)
  171. {
  172. $sWeekDay = strtolower(trim($sWeekDay));
  173. if (array_key_exists($sWeekDay, $aWEEKDAYTON))
  174. {
  175. $aDays[] = $aWEEKDAYTON[$sWeekDay];
  176. }
  177. else
  178. {
  179. throw new Exception("'itop-backup: wrong format for setting 'week_days' (found '$sWeekDay')");
  180. }
  181. }
  182. }
  183. if (count($aDays) == 0)
  184. {
  185. throw new Exception("'itop-backup: missing setting 'week_days'");
  186. }
  187. $aDays = array_unique($aDays);
  188. sort($aDays);
  189. return $aDays;
  190. }
  191. /*
  192. Gives the exact time at which the process must be run next time
  193. @returns DateTime
  194. */
  195. public function GetNextOccurrence()
  196. {
  197. $bEnabled = MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'enabled', true);
  198. if (!$bEnabled)
  199. {
  200. $oRet = new DateTime('3000-01-01');
  201. }
  202. else
  203. {
  204. // 1st - Interpret the list of days as ordered numbers (monday = 1)
  205. //
  206. $aDays = $this->InterpretWeekDays();
  207. // 2nd - Find the next active week day
  208. //
  209. $sBackupTime = MetaModel::GetConfig()->GetModuleSetting('itop-backup', 'time', '23:30');
  210. if (!preg_match('/[0-2][0-9]:[0-5][0-9]/', $sBackupTime))
  211. {
  212. throw new Exception("'itop-backup: wrong format for setting 'time' (found '$sBackupTime')");
  213. }
  214. $oNow = new DateTime();
  215. $iNextPos = false;
  216. for ($iDay = $oNow->format('N') ; $iDay <= 7 ; $iDay++)
  217. {
  218. $iNextPos = array_search($iDay, $aDays);
  219. if ($iNextPos !== false)
  220. {
  221. if (($iDay > $oNow->format('N')) || ($oNow->format('H:i') < $sBackupTime))
  222. {
  223. break;
  224. }
  225. }
  226. }
  227. // 3rd - Compute the result
  228. //
  229. if ($iNextPos === false)
  230. {
  231. // Jump to the first day within the next week
  232. $iFirstDayOfWeek = $aDays[0];
  233. $iDayMove = $oNow->format('N') - $iFirstDayOfWeek;
  234. $oRet = clone $oNow;
  235. $oRet->modify('-'.$iDayMove.' days');
  236. $oRet->modify('+1 weeks');
  237. }
  238. else
  239. {
  240. $iNextDayOfWeek = $aDays[$iNextPos];
  241. $iMove = $iNextDayOfWeek - $oNow->format('N');
  242. $oRet = clone $oNow;
  243. $oRet->modify('+'.$iMove.' days');
  244. }
  245. list($sHours, $sMinutes) = explode(':', $sBackupTime);
  246. $oRet->setTime((int)$sHours, (int) $sMinutes);
  247. }
  248. return $oRet;
  249. }
  250. }
  251. class ItopBackup extends ModuleHandlerAPI
  252. {
  253. public static function OnMenuCreation()
  254. {
  255. if (UserRights::IsAdministrator())
  256. {
  257. $oAdminMenu = new MenuGroup('AdminTools', 80 /* fRank */);
  258. new WebPageMenuNode('BackupStatus', utils::GetAbsoluteUrlModulePage('itop-backup', 'status.php'), $oAdminMenu->GetIndex(), 15 /* fRank */);
  259. }
  260. }
  261. }