schedules.inc 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862
  1. <?php
  2. /**
  3. * @file
  4. * All of the schedule handling code needed for Backup and Migrate.
  5. */
  6. define('BACKUP_MIGRATE_KEEP_ALL', 0);
  7. define('BACKUP_MIGRATE_STANDARD_DELETE', -1);
  8. define('BACKUP_MIGRATE_SMART_DELETE', -2);
  9. define('BACKUP_MIGRATE_CRON_BUILTIN', 'builtin');
  10. define('BACKUP_MIGRATE_CRON_ELYSIA', 'elysia');
  11. define('BACKUP_MIGRATE_CRON_NONE', 'none');
  12. define('BACKUP_MIGRATE_SMART_KEEP_SUBHOURLY', 3600);
  13. define('BACKUP_MIGRATE_SMART_KEEP_HOURLY', 24);
  14. define('BACKUP_MIGRATE_SMART_KEEP_DAILY', 30);
  15. define('BACKUP_MIGRATE_SMART_KEEP_WEEKLY', PHP_INT_MAX);
  16. define('BACKUP_MIGRATE_KEEP_DEFAULT', 100);
  17. require_once dirname(__FILE__) . '/crud.inc';
  18. /**
  19. * Implements hook_backup_migrate_destination_types().
  20. *
  21. * Get the built in Backup and Migrate destination types.
  22. */
  23. function backup_migrate_backup_migrate_schedule_types() {
  24. $out = array();
  25. $out += array(
  26. 'backup' => array(
  27. 'include' => 'schedule',
  28. 'type_name' => t('Backup Schedule'),
  29. 'class' => 'backup_migrate_schedule',
  30. ),
  31. );
  32. return $out;
  33. }
  34. /**
  35. * Run the preconfigured schedules. Called on cron.
  36. */
  37. function backup_migrate_schedules_cron() {
  38. require_once dirname(__FILE__) . '/profiles.inc';
  39. foreach (backup_migrate_get_schedules() as $schedule) {
  40. $schedule->cron();
  41. }
  42. backup_migrate_cleanup();
  43. }
  44. /**
  45. * Run the preconfigured schedules regardless of scheduled time settings.
  46. */
  47. function backup_migrate_schedules_run() {
  48. require_once dirname(__FILE__) . '/profiles.inc';
  49. foreach (backup_migrate_get_schedules() as $schedule) {
  50. $schedule->run();
  51. }
  52. backup_migrate_cleanup();
  53. }
  54. /**
  55. * Run the preconfigured schedules. Called on cron.
  56. */
  57. function backup_migrate_schedule_run($schedule_id) {
  58. require_once dirname(__FILE__) . '/profiles.inc';
  59. if (($schedule = backup_migrate_get_schedule($schedule_id)) && $schedule->is_enabled()) {
  60. $schedule->run();
  61. }
  62. backup_migrate_cleanup();
  63. }
  64. /**
  65. * Get all the available backup schedules.
  66. */
  67. function backup_migrate_get_schedules() {
  68. $schedules = &drupal_static('backup_migrate_get_schedules');
  69. // Get the list of schedules and cache them locally.
  70. if ($schedules === NULL) {
  71. $schedules = backup_migrate_crud_get_items('schedule');
  72. }
  73. return $schedules;
  74. }
  75. /**
  76. * Get the schedule info for the schedule with the given ID, or NULL if none exists.
  77. */
  78. function backup_migrate_get_schedule($schedule_id) {
  79. $schedules = backup_migrate_get_schedules();
  80. return @$schedules[$schedule_id];
  81. }
  82. /**
  83. * A schedule class for crud operations.
  84. */
  85. class backup_migrate_schedule extends backup_migrate_item {
  86. public $db_table = "backup_migrate_schedules";
  87. public $type_name = 'schedule';
  88. public $singular = 'schedule';
  89. public $plural = 'schedules';
  90. public $title_plural = 'Schedules';
  91. public $title_singular = 'Schedule';
  92. public $default_values = array();
  93. /**
  94. * This function is not supposed to be called. It is just here to help the po extractor out.
  95. */
  96. public function strings() {
  97. // Help the pot extractor find these strings.
  98. t('Schedule');
  99. t('Schedules');
  100. t('schedule');
  101. t('schedules');
  102. }
  103. /**
  104. * Get the default values for this item.
  105. */
  106. public function get_default_values() {
  107. return array(
  108. 'name' => t("Untitled Schedule"),
  109. 'source_id' => 'db',
  110. 'enabled' => 1,
  111. 'keep' => BACKUP_MIGRATE_KEEP_ALL,
  112. 'period' => 60 * 60 * 24,
  113. 'storage' => BACKUP_MIGRATE_STORAGE_NONE,
  114. 'cron' => BACKUP_MIGRATE_CRON_BUILTIN,
  115. 'cron_schedule' => '0 4 * * *',
  116. );
  117. }
  118. /**
  119. * Return as an array of values.
  120. */
  121. public function to_array() {
  122. $out = parent::to_array();
  123. unset($out['last_run']);
  124. return $out;
  125. }
  126. /**
  127. * Get the columns needed to list the type.
  128. */
  129. public function get_list_column_info() {
  130. $out = parent::get_list_column_info();
  131. $out = array(
  132. 'name' => array('title' => t('Name')),
  133. 'destination_name' => array('title' => t('Destinations'), 'html' => TRUE),
  134. 'profile_name' => array('title' => t('Profile'), 'html' => TRUE),
  135. 'frequency_description' => array('title' => t('Frequency')),
  136. 'keep_description' => array('title' => t('Keep')),
  137. 'enabled_description' => array('title' => t('Enabled')),
  138. 'last_run_description' => array('title' => t('Last run')),
  139. ) + $out;
  140. return $out;
  141. }
  142. /**
  143. * Get the columns needed to list the type.
  144. */
  145. public function get_settings_path() {
  146. // Pull the schedules tab up a level to the top.
  147. return BACKUP_MIGRATE_MENU_PATH . '/' . $this->type_name;
  148. }
  149. /**
  150. * Get the menu items for manipulating this type.
  151. */
  152. public function get_menu_items() {
  153. $items = parent::get_menu_items();
  154. $path = $this->get_settings_path();
  155. return $items;
  156. }
  157. /**
  158. * Get a row of data to be used in a list of items of this type.
  159. */
  160. public function get_list_row() {
  161. drupal_add_css(drupal_get_path('module', 'backup_migrate') . '/backup_migrate.css');
  162. $row = parent::get_list_row();
  163. if (!$this->is_enabled()) {
  164. foreach ($row as $key => $field) {
  165. if (!is_array($field)) {
  166. $row[$key] = array('data' => $field, 'class' => 'schedule-list-disabled');
  167. }
  168. elseif (isset($row[$key]['class'])) {
  169. $row[$key]['class'] .= ' schedule-list-disabled';
  170. }
  171. else {
  172. $row[$key]['class'] = 'schedule-list-disabled';
  173. }
  174. }
  175. }
  176. return $row;
  177. }
  178. /**
  179. * Is the schedule enabled and valid.
  180. */
  181. public function is_enabled() {
  182. $destination = $this->get_destination();
  183. $profile = $this->get_profile();
  184. return (!empty($this->enabled) && !empty($destination) && !empty($profile));
  185. }
  186. /**
  187. * Get the destination object of the schedule.
  188. */
  189. public function get_destination() {
  190. $destinations = (array) $this->get_destinations();
  191. return reset($destinations);
  192. }
  193. /**
  194. * Get the destination object of the schedule.
  195. */
  196. public function get_destination_ids() {
  197. $out = array();
  198. foreach (array('destination_id', 'copy_destination_id') as $key) {
  199. if ($id = $this->get($key)) {
  200. $out[$key] = $id;
  201. }
  202. }
  203. return $out;
  204. }
  205. /**
  206. * Get the destination object of the schedule.
  207. */
  208. public function get_destinations() {
  209. require_once dirname(__FILE__) . '/destinations.inc';
  210. $out = array();
  211. foreach ($this->get_destination_ids() as $id) {
  212. if ($dest = backup_migrate_get_destination($id)) {
  213. $out[$id] = $dest;
  214. }
  215. }
  216. return $out;
  217. }
  218. /**
  219. * Get the destination object of the schedule.
  220. */
  221. public function get_destination_remote() {
  222. require_once dirname(__FILE__) . '/destinations.inc';
  223. return backup_migrate_get_destination($this->get('destination_remote_id'));
  224. }
  225. /**
  226. * Get the destination object of the schedule.
  227. */
  228. public function get_destination_local() {
  229. require_once dirname(__FILE__) . '/destinations.inc';
  230. return backup_migrate_get_destination($this->get('destination_local_id'));
  231. }
  232. /**
  233. * Get the name of the destination.
  234. */
  235. public function get_destination_name() {
  236. if ($destinations = $this->get_destinations()) {
  237. $out = array();
  238. foreach ((array) $destinations as $destination) {
  239. $out[] = check_plain($destination->get_name());
  240. }
  241. return implode(', ', $out);
  242. }
  243. return '<div class="row-error">' . t("Missing") . '</div>';
  244. }
  245. /**
  246. * Get the destination of the schedule.
  247. */
  248. public function get_profile() {
  249. require_once dirname(__FILE__) . '/profiles.inc';
  250. if ($settings = backup_migrate_get_profile($this->get('profile_id'))) {
  251. $settings->file_info = empty($settings->file_info) ? array() : $settings->file_info;
  252. $settings->file_info += array(
  253. 'schedule_id' => $this->get_id(),
  254. 'schedule_name' => $this->get('name'),
  255. );
  256. }
  257. return $settings;
  258. }
  259. /**
  260. * Get the name of the source.
  261. */
  262. public function get_profile_name() {
  263. if ($profile = $this->get_profile()) {
  264. return check_plain($profile->get_name());
  265. }
  266. return '<div class="row-error">' . t("Missing") . '</div>';
  267. }
  268. /**
  269. * Format a frequency in human-readable form.
  270. */
  271. public function get_frequency_description() {
  272. $period = $this->get_frequency_period();
  273. $cron = $this->get('cron');
  274. if ($cron == BACKUP_MIGRATE_CRON_BUILTIN) {
  275. $out = format_plural(($this->period / $period['seconds']), $period['singular'], $period['plural']);
  276. }
  277. elseif ($cron == BACKUP_MIGRATE_CRON_ELYSIA) {
  278. $out = $this->get('cron_schedule');
  279. }
  280. else {
  281. $out = t('None');
  282. }
  283. return $out;
  284. }
  285. /**
  286. * Format the number to keep in human-readable form.
  287. */
  288. public function get_keep_description() {
  289. return $this->generate_keep_description($this->keep);
  290. }
  291. /**
  292. * Format a number to keep in human readable from.
  293. */
  294. public function generate_keep_description($keep, $terse = TRUE) {
  295. if ($keep == BACKUP_MIGRATE_KEEP_ALL) {
  296. return t('all backups');
  297. }
  298. elseif ($keep == BACKUP_MIGRATE_SMART_DELETE) {
  299. $keep_hourly = variable_get('backup_migrate_smart_keep_hourly', BACKUP_MIGRATE_SMART_KEEP_HOURLY);
  300. $keep_daily = variable_get('backup_migrate_smart_keep_daily', BACKUP_MIGRATE_SMART_KEEP_DAILY);
  301. $keep_weekly = variable_get('backup_migrate_smart_keep_weekly', BACKUP_MIGRATE_SMART_KEEP_WEEKLY);
  302. if ($terse) {
  303. return t('!hours hourly, !days daily, !weeks weekly backups',
  304. array(
  305. '!hours' => $keep_hourly == PHP_INT_MAX ? t('all') : $keep_hourly,
  306. '!days' => $keep_daily == PHP_INT_MAX ? t('all') : $keep_daily,
  307. '!weeks' => $keep_weekly == PHP_INT_MAX ? t('all') : $keep_weekly,
  308. ));
  309. }
  310. else {
  311. return t('hourly backups !hours, daily backups !days and weekly backups !weeks',
  312. array(
  313. '!hours' => $keep_hourly == PHP_INT_MAX ? t('forever') : format_plural($keep_hourly, 'for 1 hour', 'for the past @count hours'),
  314. '!days' => $keep_daily == PHP_INT_MAX ? t('forever') : format_plural($keep_daily, 'for 1 day', 'for the past @count days'),
  315. '!weeks' => $keep_weekly == PHP_INT_MAX ? t('forever') : format_plural($keep_weekly, 'for 1 week', 'for the past @count weeks'),
  316. )
  317. );
  318. }
  319. }
  320. return format_plural($keep, 'last 1 backup', 'last @count backups');
  321. }
  322. /**
  323. * Format the enabled status in human-readable form.
  324. */
  325. public function get_enabled_description() {
  326. return !empty($this->enabled) ? t('Enabled') : t('Disabled');
  327. }
  328. /**
  329. * Format the enabled status in human-readable form.
  330. */
  331. public function get_last_run_description() {
  332. $last_run = $this->get('last_run');
  333. return !empty($last_run) ? format_date($last_run, 'small') : t('Never');
  334. }
  335. /**
  336. * Get the number of excluded tables.
  337. */
  338. public function get_exclude_tables_count() {
  339. return count($this->exclude_tables) ? count($this->exclude_tables) : t("No tables excluded");
  340. }
  341. /**
  342. * Get the number of excluded tables.
  343. */
  344. public function get_nodata_tables_count() {
  345. return count($this->nodata_tables) ? count($this->nodata_tables) : t("No data omitted");
  346. }
  347. /**
  348. * Get the edit form.
  349. */
  350. public function edit_form() {
  351. require_once dirname(__FILE__) . '/destinations.inc';
  352. require_once dirname(__FILE__) . '/sources.inc';
  353. require_once dirname(__FILE__) . '/profiles.inc';
  354. $form = parent::edit_form();
  355. $form['name'] = array(
  356. "#type" => "textfield",
  357. "#title" => t("Schedule Name"),
  358. "#default_value" => $this->get('name'),
  359. );
  360. $form += _backup_migrate_get_source_form($this->get('source_id'));
  361. $form['profile_id'] = array(
  362. "#type" => "select",
  363. "#title" => t("Settings Profile"),
  364. "#options" => _backup_migrate_get_profile_form_item_options(),
  365. "#default_value" => $this->get('profile_id'),
  366. );
  367. $form['profile_id']['#description'] = ' ' . l(t('Create new profile'), BACKUP_MIGRATE_MENU_PATH . '/settings/profile/add');
  368. if (!$form['profile_id']['#options']) {
  369. $form['profile_id']['#options'] = array('0' => t('-- None Available --'));
  370. }
  371. $period_options = array();
  372. foreach ($this->frequency_periods() as $type => $period) {
  373. $period_options[$type] = $period['title'];
  374. }
  375. $default_period = $this->get_frequency_period();
  376. $default_period_num = $this->get('period') / $default_period['seconds'];
  377. $form['enabled'] = array(
  378. '#type' => "checkbox",
  379. '#title' => t('Enabled'),
  380. '#default_value' => $this->get('enabled'),
  381. );
  382. $form['cron_settings'] = array(
  383. '#type' => 'backup_migrate_dependent',
  384. '#dependencies' => array(
  385. 'enabled' => TRUE,
  386. ),
  387. );
  388. $cron = $this->get('cron');
  389. $form['cron_settings']['cron_builtin'] = array(
  390. "#type" => "radio",
  391. "#title" => t('Run using Drupal\'s cron'),
  392. '#return_value' => BACKUP_MIGRATE_CRON_BUILTIN,
  393. "#description" => t('Run this schedule when !cron runs.', array('!cron' => l(t('your cron task'), 'http://drupal.org/cron'))),
  394. "#default_value" => $cron ? $cron : BACKUP_MIGRATE_CRON_BUILTIN,
  395. '#parents' => array('cron'),
  396. );
  397. $form['cron_settings']['period_settings'] = array(
  398. '#type' => 'backup_migrate_dependent',
  399. '#dependencies' => array(
  400. 'cron' => BACKUP_MIGRATE_CRON_BUILTIN,
  401. ),
  402. );
  403. $form['cron_settings']['period_settings']['period'] = array(
  404. "#type" => "item",
  405. "#title" => t("Backup every"),
  406. "#prefix" => '<div class="container-inline">',
  407. "#suffix" => '</div>',
  408. "#tree" => TRUE,
  409. '#parents' => array('period'),
  410. );
  411. $form['cron_settings']['period_settings']['period']['number'] = array(
  412. "#type" => "textfield",
  413. "#size" => 6,
  414. "#default_value" => $default_period_num,
  415. '#parents' => array('period', 'number'),
  416. );
  417. $form['cron_settings']['period_settings']['period']['type'] = array(
  418. "#type" => "select",
  419. "#options" => $period_options,
  420. "#default_value" => $default_period['type'],
  421. '#parents' => array('period', 'type'),
  422. );
  423. $form['cron_settings']['cron_elysia'] = array(
  424. "#type" => "radio",
  425. "#title" => t('Run using Elysia cron'),
  426. '#return_value' => BACKUP_MIGRATE_CRON_ELYSIA,
  427. "#description" => t('You can specify exactly when this schedule should run using !elysia.', array('!elysia' => l(t('the Elysia Cron module'), 'http://drupal.org/project/elysia_cron'))),
  428. "#default_value" => $cron ? $cron : BACKUP_MIGRATE_CRON_BUILTIN,
  429. '#parents' => array('cron'),
  430. );
  431. if (!module_exists('elysia_cron') && !module_exists('ultimate_cron')) {
  432. $form['cron_settings']['cron_elysia']['#disabled'] = TRUE;
  433. $form['cron_settings']['cron_elysia']['#description'] .= ' ' . t('Install !elysia to enable this option.', array('!elysia' => l(t('Elysia Cron'), 'http://drupal.org/project/elysia_cron')));
  434. }
  435. $form['cron_settings']['cron_schedule_settings'] = array(
  436. '#type' => 'backup_migrate_dependent',
  437. '#dependencies' => array(
  438. 'cron' => BACKUP_MIGRATE_CRON_ELYSIA,
  439. ),
  440. );
  441. $form['cron_settings']['cron_schedule_settings']['cron_schedule'] = array(
  442. "#type" => "textfield",
  443. "#title" => t('Cron Schedule'),
  444. '#length' => 10,
  445. "#description" => t('Specify the frequency of the schedule using standard cron notation. For more information see the !elysiareadme.', array('!elysiareadme' => l(t('the Elysia Cron README'), 'http://drupalcode.org/project/elysia_cron.git/blob/refs/heads/7.x-1.x:/README.txt'))),
  446. "#default_value" => $this->get('cron_schedule'),
  447. '#parents' => array('cron_schedule'),
  448. );
  449. $form['cron_settings']['cron_none'] = array(
  450. "#type" => "radio",
  451. "#title" => t('Do not run automatically'),
  452. '#return_value' => 'none',
  453. "#description" => t('Do not run this schedule automatically. You can still run it using !drush.', array('!drush' => l(t('Drush'), 'http://drupal.org/project/drush'))),
  454. "#default_value" => $cron ? $cron : BACKUP_MIGRATE_CRON_BUILTIN,
  455. '#parents' => array('cron'),
  456. );
  457. $keep = $this->get('keep');
  458. $form['delete'] = array(
  459. '#type' => 'checkbox',
  460. '#default_value' => $keep != 0,
  461. '#title' => t('Automatically delete old backups'),
  462. );
  463. $form['delete_settings'] = array(
  464. '#type' => 'backup_migrate_dependent',
  465. '#dependencies' => array(
  466. 'delete' => TRUE,
  467. ),
  468. );
  469. $keep_hourly = variable_get('backup_migrate_smart_keep_hourly', BACKUP_MIGRATE_SMART_KEEP_HOURLY);
  470. $keep_daily = variable_get('backup_migrate_smart_keep_daily', BACKUP_MIGRATE_SMART_KEEP_DAILY);
  471. $keep_weekly = variable_get('backup_migrate_smart_keep_weekly', BACKUP_MIGRATE_SMART_KEEP_WEEKLY);
  472. $form['delete_settings']['smartdelete'] = array(
  473. "#type" => "radio",
  474. "#title" => t('Smart Delete'),
  475. '#return_value' => BACKUP_MIGRATE_SMART_DELETE,
  476. "#description" => t('Keep !keep. <strong>Recommended</strong>', array('!keep' => $this->generate_keep_description(BACKUP_MIGRATE_SMART_DELETE, FALSE))),
  477. "#default_value" => $keep ? $keep : BACKUP_MIGRATE_SMART_DELETE,
  478. '#parents' => array('deletetype'),
  479. );
  480. $form['delete_settings']['standarddelete'] = array(
  481. "#type" => "radio",
  482. "#title" => t('Simple Delete'),
  483. '#return_value' => BACKUP_MIGRATE_STANDARD_DELETE,
  484. "#description" => t("Keep a specified number of files deleting the oldest ones first."),
  485. "#default_value" => $keep > 0 ? BACKUP_MIGRATE_STANDARD_DELETE : 0,
  486. '#parents' => array('deletetype'),
  487. );
  488. $form['delete_settings']['keep-settings'] = array(
  489. '#type' => 'backup_migrate_dependent',
  490. '#dependencies' => array(
  491. 'deletetype' => BACKUP_MIGRATE_STANDARD_DELETE,
  492. ),
  493. );
  494. $form['delete_settings']['keep-settings']['keep'] = array(
  495. "#type" => "textfield",
  496. "#size" => 6,
  497. "#title" => t("Number of Backup files to keep"),
  498. "#description" => t("The number of backup files to keep before deleting old ones."),
  499. "#default_value" => $keep > 0 ? $keep : BACKUP_MIGRATE_KEEP_DEFAULT,
  500. );
  501. $form['destination'] = _backup_migrate_get_destination_pulldown('scheduled backup', $this->get('destination_id'), $this->get('copy_destination_id'));
  502. return $form;
  503. }
  504. /**
  505. * Submit the edit form.
  506. */
  507. public function edit_form_validate($form, &$form_state) {
  508. if (!is_numeric($form_state['values']['period']['number']) || $form_state['values']['period']['number'] <= 0) {
  509. form_set_error('period][number', t('Backup period must be a number greater than 0.'));
  510. }
  511. if (!$form_state['values']['delete']) {
  512. $form_state['values']['keep'] = 0;
  513. }
  514. elseif ($form_state['values']['deletetype'] == BACKUP_MIGRATE_SMART_DELETE) {
  515. $form_state['values']['keep'] = BACKUP_MIGRATE_SMART_DELETE;
  516. }
  517. elseif (!is_numeric($form_state['values']['keep']) || $form_state['values']['keep'] <= 0) {
  518. form_set_error('keep', t('Number to keep must be a number greater than 0.'));
  519. }
  520. parent::edit_form_validate($form, $form_state);
  521. }
  522. /**
  523. * Submit the edit form.
  524. */
  525. public function edit_form_submit($form, &$form_state) {
  526. $periods = $this->frequency_periods();
  527. $period = $periods[$form_state['values']['period']['type']];
  528. $form_state['values']['period'] = $form_state['values']['period']['number'] * $period['seconds'];
  529. parent::edit_form_submit($form, $form_state);
  530. }
  531. /**
  532. * Get the period of the frequency (ie: seconds, minutes etc.).
  533. */
  534. public function get_frequency_period() {
  535. foreach (array_reverse($this->frequency_periods()) as $period) {
  536. if ($period['seconds'] && ($this->period % $period['seconds']) === 0) {
  537. return $period;
  538. }
  539. }
  540. }
  541. /**
  542. * Get a list of available backup periods. Only returns time periods which have a
  543. * (reasonably) consistent number of seconds (ie: no months).
  544. */
  545. public function frequency_periods() {
  546. return array(
  547. 'seconds' => array('type' => 'seconds', 'seconds' => 1, 'title' => t('Seconds'), 'singular' => t('Once a second'), 'plural' => t('Every @count seconds')),
  548. 'minutes' => array('type' => 'minutes', 'seconds' => 60, 'title' => t('Minutes'), 'singular' => t('Once a minute'), 'plural' => t('Every @count minutes')),
  549. 'hours' => array('type' => 'hours', 'seconds' => 3600, 'title' => t('Hours'), 'singular' => t('Once an hour'), 'plural' => t('Every @count hours')),
  550. 'days' => array('type' => 'days', 'seconds' => 86400, 'title' => t('Days'), 'singular' => t('Once a day'), 'plural' => t('Every @count days')),
  551. 'weeks' => array('type' => 'weeks', 'seconds' => 604800, 'title' => t('Weeks'), 'singular' => t('Once a week'), 'plural' => t('Every @count weeks')),
  552. );
  553. }
  554. /**
  555. * Get the message to send to the user when confirming the deletion of the item.
  556. */
  557. public function delete_confirm_message() {
  558. return t('Are you sure you want to delete the schedule %name? Backups made with this schedule will not be deleted.', array('%name' => $this->get('name')));
  559. }
  560. /**
  561. * Perform the cron action. Run the backup if enough time has elapsed.
  562. */
  563. public function cron() {
  564. $now = time();
  565. // Add a small negative buffer (1% of the entire period) to the time to account for slight difference in cron run length.
  566. $wait_time = $this->period - ($this->period * variable_get('backup_migrate_schedule_buffer', BACKUP_MIGRATE_SCHEDULE_BUFFER));
  567. $cron = $this->get('cron');
  568. if ($cron == BACKUP_MIGRATE_CRON_BUILTIN && $this->is_enabled() && ($now - $this->get('last_run')) >= $wait_time) {
  569. $this->run();
  570. }
  571. }
  572. /**
  573. * Run the actual schedule.
  574. */
  575. public function run() {
  576. // Clear cached profile data as it could have been altered by a previous
  577. // schedule run.
  578. drupal_static_reset('backup_migrate_get_profiles');
  579. if ($settings = $this->get_profile()) {
  580. $settings->source_id = $this->get('source_id');
  581. $settings->destination_id = $this->get('destination_ids');
  582. $this->update_last_run(time());
  583. backup_migrate_perform_backup($settings);
  584. $this->remove_expired_backups();
  585. }
  586. else {
  587. backup_migrate_backup_fail("Schedule '%schedule' could not be run because requires a profile which is missing.", array('%schedule' => $schedule->get_name()), $settings);
  588. }
  589. }
  590. /**
  591. * Set the last run time of a schedule to the given timestamp, or now if none specified.
  592. */
  593. public function update_last_run($timestamp = NULL) {
  594. if ($timestamp === NULL) {
  595. $timestamp = time();
  596. }
  597. variable_set('backup_migrate_schedule_last_run_' . $this->get('id'), $timestamp);
  598. }
  599. /**
  600. * Set the last run time of a schedule to the given timestamp, or now if none specified.
  601. */
  602. public function get_last_run() {
  603. return variable_get('backup_migrate_schedule_last_run_' . $this->get('id'), 0);
  604. }
  605. /**
  606. * Remove older backups keeping only the number specified by the aministrator.
  607. */
  608. public function remove_expired_backups() {
  609. require_once dirname(__FILE__) . '/destinations.inc';
  610. $num_to_keep = $this->keep;
  611. // If num to keep is not 0 (0 is infinity).
  612. foreach ((array) $this->get_destinations() as $destination) {
  613. if ($destination && $destination->op('delete') && $destination_files = $destination->list_files()) {
  614. if ($num_to_keep == BACKUP_MIGRATE_SMART_DELETE) {
  615. $this->smart_delete_backups(
  616. $destination,
  617. $destination_files,
  618. variable_get('backup_migrate_smart_keep_subhourly', BACKUP_MIGRATE_SMART_KEEP_SUBHOURLY),
  619. variable_get('backup_migrate_smart_keep_hourly', BACKUP_MIGRATE_SMART_KEEP_HOURLY),
  620. variable_get('backup_migrate_smart_keep_daily', BACKUP_MIGRATE_SMART_KEEP_DAILY),
  621. variable_get('backup_migrate_smart_keep_weekly', BACKUP_MIGRATE_SMART_KEEP_WEEKLY)
  622. );
  623. }
  624. elseif ($num_to_keep != BACKUP_MIGRATE_KEEP_ALL) {
  625. $this->delete_backups($destination, $destination_files, $num_to_keep);
  626. }
  627. }
  628. }
  629. }
  630. /**
  631. * Remove older backups keeping only the number specified by the aministrator.
  632. */
  633. public function delete_backups($destination, $files, $num_to_keep) {
  634. require_once dirname(__FILE__) . '/destinations.inc';
  635. $num_to_keep = $this->keep;
  636. // Sort the files by modified time.
  637. $i = 0;
  638. foreach ($files as $id => $file) {
  639. if ($file->is_recognized_type()) {
  640. $time = $file->info('filetime');
  641. $sorted[$id] = $time;
  642. }
  643. }
  644. asort($sorted);
  645. // If we are beyond our limit, remove as many as we need.
  646. $num_files = count($files);
  647. if ($num_files > $num_to_keep) {
  648. $num_to_delete = $num_files - $num_to_keep;
  649. // Delete from the start of the list (earliest).
  650. foreach ($sorted as $id => $time) {
  651. if (!$num_to_delete--) {
  652. break;
  653. }
  654. $destination->delete_file($id);
  655. }
  656. }
  657. }
  658. /**
  659. * Delete files keeping the specified number of hourly, daily, weekly and monthly backups.
  660. */
  661. public function smart_delete_backups($destination, $files, $keep_subhourly = 3600, $keep_hourly = 24, $keep_daily = 14, $keep_weekly = PHP_INT_MAX, $keep_monthly = PHP_INT_MAX) {
  662. // Each period must be an exact multiple of the next smallest period.
  663. $now = time();
  664. $periods = array(
  665. 'subhourly' => array(
  666. 'delta' => 1,
  667. 'keep' => $keep_subhourly,
  668. 'last_time' => 0,
  669. 'files' => array(),
  670. ),
  671. 'hourly' => array(
  672. 'delta' => 60 * 60,
  673. 'keep' => $keep_hourly,
  674. 'last_time' => 0,
  675. 'files' => array(),
  676. ),
  677. 'daily' => array(
  678. 'delta' => 60 * 60 * 24,
  679. 'keep' => $keep_daily,
  680. 'last_time' => 0,
  681. 'files' => array(),
  682. ),
  683. 'weekly' => array(
  684. 'delta' => 60 * 60 * 24 * 7,
  685. 'keep' => $keep_weekly,
  686. 'last_time' => 0,
  687. 'files' => array(),
  688. ),
  689. /*
  690. 'monthly' => array(
  691. 'delta' => 60*60*24*7*4,
  692. 'keep' => $keep_monthly,
  693. 'last_time' => 0,
  694. 'files' => array(),
  695. ),
  696. */
  697. );
  698. $keep_files = $filetimes = $times = $groups = $sorted = $saved = array();
  699. foreach ($files as $id => $file) {
  700. if ($file->is_recognized_type()) {
  701. $time = $file->info('filetime');
  702. $sorted[$id] = $time;
  703. }
  704. }
  705. // Sort files, oldest first.
  706. asort($sorted);
  707. // Reset internal pointer and get the oldest file time.
  708. $oldest_file_time = reset($sorted);
  709. // Save the oldest file.
  710. $keep_files[key($sorted)] = key($sorted);
  711. foreach ($periods as $i => $period) {
  712. $period_keep_files = array();
  713. $time = $oldest_file_time;
  714. // Set time from which we start saving files.
  715. $period_start_time = $now - (($period['keep'] + 1) * $period['delta']);
  716. // Increase time to within one period time span of the period start
  717. // time. This keeps all the different period starts aligned.
  718. if ($time < $period_start_time) {
  719. $time += ((int) ceil(($period_start_time - $time) / $period['delta'])) * $period['delta'];
  720. }
  721. $file_id = $this->find_nearest_file($sorted, $time);
  722. do {
  723. $period_keep_files[$file_id] = $file_id;
  724. $last_file_id = $file_id;
  725. $time += $period['delta'];
  726. $file_id = $this->find_nearest_file($sorted, $time);
  727. } while ($time < $now);
  728. $keep_files = array_merge($keep_files, $period_keep_files);
  729. }
  730. // Do the delete.
  731. foreach ($files as $id => $file) {
  732. if (!isset($keep_files[$id])) {
  733. $destination->delete_file($file->file_id());
  734. }
  735. }
  736. }
  737. /**
  738. *
  739. */
  740. protected function find_nearest_file($files, $time) {
  741. $last_file_id = NULL;
  742. $last_file_time = NULL;
  743. foreach ($files as $id => $file_time) {
  744. if ($file_time >= $time) {
  745. if ($last_file_time == NULL) {
  746. return $id;
  747. }
  748. if ($file_time == $time) {
  749. return $id;
  750. }
  751. $time_to_prev = $time - $last_file_time;
  752. $time_to_next = $file_time - $time;
  753. if ($time_to_prev >= $time_to_next) {
  754. return $id;
  755. }
  756. else {
  757. return $last_file_id;
  758. }
  759. // Shouldn't hit this but you never know.
  760. break;
  761. }
  762. $last_file_id = $id;
  763. $last_file_time = $file_time;
  764. }
  765. }
  766. }