schedules.inc 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804
  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. backup_migrate_include('crud');
  18. /**
  19. * Implementation of 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. backup_migrate_include('profiles');
  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. backup_migrate_include('profiles');
  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. backup_migrate_include('profiles');
  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. backup_migrate_include('destinations');
  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. backup_migrate_include('destinations');
  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. backup_migrate_include('destinations');
  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. backup_migrate_include('profiles');
  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. $form = parent::edit_form();
  352. backup_migrate_include('destinations', 'sources', 'profiles');
  353. $form['name'] = array(
  354. "#type" => "textfield",
  355. "#title" => t("Schedule Name"),
  356. "#default_value" => $this->get('name'),
  357. );
  358. $form += _backup_migrate_get_source_form($this->get('source_id'));
  359. $form['profile_id'] = array(
  360. "#type" => "select",
  361. "#title" => t("Settings Profile"),
  362. "#options" => _backup_migrate_get_profile_form_item_options(),
  363. "#default_value" => $this->get('profile_id'),
  364. );
  365. $form['profile_id']['#description'] = ' ' . l(t('Create new profile'), BACKUP_MIGRATE_MENU_PATH . '/settings/profile/add');
  366. if (!$form['profile_id']['#options']) {
  367. $form['profile_id']['#options'] = array('0' => t('-- None Available --'));
  368. }
  369. $period_options = array();
  370. foreach ($this->frequency_periods() as $type => $period) {
  371. $period_options[$type] = $period['title'];
  372. }
  373. $default_period = $this->get_frequency_period();
  374. $default_period_num = $this->get('period') / $default_period['seconds'];
  375. $form['enabled'] = array(
  376. '#type' => "checkbox",
  377. '#title' => t('Enabled'),
  378. '#default_value' => $this->get('enabled'),
  379. );
  380. $form['cron_settings'] = array(
  381. '#type' => 'backup_migrate_dependent',
  382. '#dependencies' => array(
  383. 'enabled' => TRUE,
  384. ),
  385. );
  386. $cron = $this->get('cron');
  387. $form['cron_settings']['cron_builtin'] = array(
  388. "#type" => "radio",
  389. "#title" => t('Run using Drupal\'s cron'),
  390. '#return_value' => BACKUP_MIGRATE_CRON_BUILTIN,
  391. "#description" => t('Run this schedule when !cron runs.', array('!cron' => l(t('your cron task'), 'http://drupal.org/cron'))),
  392. "#default_value" => $cron ? $cron : BACKUP_MIGRATE_CRON_BUILTIN,
  393. '#parents' => array('cron'),
  394. );
  395. $form['cron_settings']['period_settings'] = array(
  396. '#type' => 'backup_migrate_dependent',
  397. '#dependencies' => array(
  398. 'cron' => BACKUP_MIGRATE_CRON_BUILTIN,
  399. ),
  400. );
  401. $form['cron_settings']['period_settings']['period'] = array(
  402. "#type" => "item",
  403. "#title" => t("Backup every"),
  404. "#prefix" => '<div class="container-inline">',
  405. "#suffix" => '</div>',
  406. "#tree" => TRUE,
  407. '#parents' => array('period'),
  408. );
  409. $form['cron_settings']['period_settings']['period']['number'] = array(
  410. "#type" => "textfield",
  411. "#size" => 6,
  412. "#default_value" => $default_period_num,
  413. '#parents' => array('period', 'number'),
  414. );
  415. $form['cron_settings']['period_settings']['period']['type'] = array(
  416. "#type" => "select",
  417. "#options" => $period_options,
  418. "#default_value" => $default_period['type'],
  419. '#parents' => array('period', 'type'),
  420. );
  421. $form['cron_settings']['cron_elysia'] = array(
  422. "#type" => "radio",
  423. "#title" => t('Run using Elysia cron'),
  424. '#return_value' => BACKUP_MIGRATE_CRON_ELYSIA,
  425. "#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'))),
  426. "#default_value" => $cron ? $cron : BACKUP_MIGRATE_CRON_BUILTIN,
  427. '#parents' => array('cron'),
  428. );
  429. if (!module_exists('elysia_cron') && !module_exists('ultimate_cron')) {
  430. $form['cron_settings']['cron_elysia']['#disabled'] = TRUE;
  431. $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')));
  432. }
  433. $form['cron_settings']['cron_schedule_settings'] = array(
  434. '#type' => 'backup_migrate_dependent',
  435. '#dependencies' => array(
  436. 'cron' => BACKUP_MIGRATE_CRON_ELYSIA,
  437. ),
  438. );
  439. $form['cron_settings']['cron_schedule_settings']['cron_schedule'] = array(
  440. "#type" => "textfield",
  441. "#title" => t('Cron Schedule'),
  442. '#length' => 10,
  443. "#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'))),
  444. "#default_value" => $this->get('cron_schedule'),
  445. '#parents' => array('cron_schedule'),
  446. );
  447. $form['cron_settings']['cron_none'] = array(
  448. "#type" => "radio",
  449. "#title" => t('Do not run automatically'),
  450. '#return_value' => 'none',
  451. "#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'))),
  452. "#default_value" => $cron ? $cron : BACKUP_MIGRATE_CRON_BUILTIN,
  453. '#parents' => array('cron'),
  454. );
  455. $keep = $this->get('keep');
  456. $form['delete'] = array(
  457. '#type' => 'checkbox',
  458. '#default_value' => $keep != 0,
  459. '#title' => t('Automatically delete old backups'),
  460. );
  461. $form['delete_settings'] = array(
  462. '#type' => 'backup_migrate_dependent',
  463. '#dependencies' => array(
  464. 'delete' => TRUE,
  465. ),
  466. );
  467. $keep_hourly = variable_get('backup_migrate_smart_keep_hourly', BACKUP_MIGRATE_SMART_KEEP_HOURLY);
  468. $keep_daily = variable_get('backup_migrate_smart_keep_daily', BACKUP_MIGRATE_SMART_KEEP_DAILY);
  469. $keep_weekly = variable_get('backup_migrate_smart_keep_weekly', BACKUP_MIGRATE_SMART_KEEP_WEEKLY);
  470. $form['delete_settings']['smartdelete'] = array(
  471. "#type" => "radio",
  472. "#title" => t('Smart Delete'),
  473. '#return_value' => BACKUP_MIGRATE_SMART_DELETE,
  474. "#description" => t('Keep !keep. <strong>Recommended</strong>', array('!keep' => $this->generate_keep_description(BACKUP_MIGRATE_SMART_DELETE, FALSE))),
  475. "#default_value" => $keep ? $keep : BACKUP_MIGRATE_SMART_DELETE,
  476. '#parents' => array('deletetype'),
  477. );
  478. $form['delete_settings']['standarddelete'] = array(
  479. "#type" => "radio",
  480. "#title" => t('Simple Delete'),
  481. '#return_value' => BACKUP_MIGRATE_STANDARD_DELETE,
  482. "#description" => t("Keep a specified number of files deleting the oldest ones first."),
  483. "#default_value" => $keep > 0 ? BACKUP_MIGRATE_STANDARD_DELETE : 0,
  484. '#parents' => array('deletetype'),
  485. );
  486. $form['delete_settings']['keep-settings'] = array(
  487. '#type' => 'backup_migrate_dependent',
  488. '#dependencies' => array(
  489. 'deletetype' => BACKUP_MIGRATE_STANDARD_DELETE,
  490. ),
  491. );
  492. $form['delete_settings']['keep-settings']['keep'] = array(
  493. "#type" => "textfield",
  494. "#size" => 6,
  495. "#title" => t("Number of Backup files to keep"),
  496. "#description" => t("The number of backup files to keep before deleting old ones."),
  497. "#default_value" => $keep > 0 ? $keep : BACKUP_MIGRATE_KEEP_DEFAULT,
  498. );
  499. $form['destination'] = _backup_migrate_get_destination_pulldown('scheduled backup', $this->get('destination_id'), $this->get('copy_destination_id'));
  500. return $form;
  501. }
  502. /**
  503. * Submit the edit form.
  504. */
  505. public function edit_form_validate($form, &$form_state) {
  506. if (!is_numeric($form_state['values']['period']['number']) || $form_state['values']['period']['number'] <= 0) {
  507. form_set_error('period][number', t('Backup period must be a number greater than 0.'));
  508. }
  509. if (!$form_state['values']['delete']) {
  510. $form_state['values']['keep'] = 0;
  511. }
  512. elseif ($form_state['values']['deletetype'] == BACKUP_MIGRATE_SMART_DELETE) {
  513. $form_state['values']['keep'] = BACKUP_MIGRATE_SMART_DELETE;
  514. }
  515. elseif (!is_numeric($form_state['values']['keep']) || $form_state['values']['keep'] <= 0) {
  516. form_set_error('keep', t('Number to keep must be a number greater than 0.'));
  517. }
  518. parent::edit_form_validate($form, $form_state);
  519. }
  520. /**
  521. * Submit the edit form.
  522. */
  523. public function edit_form_submit($form, &$form_state) {
  524. $periods = $this->frequency_periods();
  525. $period = $periods[$form_state['values']['period']['type']];
  526. $form_state['values']['period'] = $form_state['values']['period']['number'] * $period['seconds'];
  527. parent::edit_form_submit($form, $form_state);
  528. }
  529. /**
  530. * Get the period of the frequency (ie: seconds, minutes etc.).
  531. */
  532. public function get_frequency_period() {
  533. foreach (array_reverse($this->frequency_periods()) as $period) {
  534. if ($period['seconds'] && ($this->period % $period['seconds']) === 0) {
  535. return $period;
  536. }
  537. }
  538. }
  539. /**
  540. * Get a list of available backup periods. Only returns time periods which have a
  541. * (reasonably) consistent number of seconds (ie: no months).
  542. */
  543. public function frequency_periods() {
  544. return array(
  545. 'seconds' => array('type' => 'seconds', 'seconds' => 1, 'title' => t('Seconds'), 'singular' => t('Once a second'), 'plural' => t('Every @count seconds')),
  546. 'minutes' => array('type' => 'minutes', 'seconds' => 60, 'title' => t('Minutes'), 'singular' => t('Once a minute'), 'plural' => t('Every @count minutes')),
  547. 'hours' => array('type' => 'hours', 'seconds' => 3600, 'title' => t('Hours'), 'singular' => t('Once an hour'), 'plural' => t('Every @count hours')),
  548. 'days' => array('type' => 'days', 'seconds' => 86400, 'title' => t('Days'), 'singular' => t('Once a day'), 'plural' => t('Every @count days')),
  549. 'weeks' => array('type' => 'weeks', 'seconds' => 604800, 'title' => t('Weeks'), 'singular' => t('Once a week'), 'plural' => t('Every @count weeks')),
  550. );
  551. }
  552. /**
  553. * Get the message to send to the user when confirming the deletion of the item.
  554. */
  555. public function delete_confirm_message() {
  556. 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')));
  557. }
  558. /**
  559. * Perform the cron action. Run the backup if enough time has elapsed.
  560. */
  561. public function cron() {
  562. $now = time();
  563. // Add a small negative buffer (1% of the entire period) to the time to account for slight difference in cron run length.
  564. $wait_time = $this->period - ($this->period * variable_get('backup_migrate_schedule_buffer', 0.01));
  565. $cron = $this->get('cron');
  566. if ($cron == BACKUP_MIGRATE_CRON_BUILTIN && $this->is_enabled() && ($now - $this->get('last_run')) >= $wait_time) {
  567. $this->run();
  568. }
  569. }
  570. /**
  571. * Run the actual schedule.
  572. */
  573. public function run() {
  574. // Clear cached profile data which could have been altered by previous
  575. // schedule run; see #2672478
  576. drupal_static_reset('backup_migrate_get_profiles');
  577. if ($settings = $this->get_profile()) {
  578. $settings->source_id = $this->get('source_id');
  579. $settings->destination_id = $this->get('destination_ids');
  580. $this->update_last_run(time());
  581. backup_migrate_perform_backup($settings);
  582. $this->remove_expired_backups();
  583. }
  584. else {
  585. backup_migrate_backup_fail("Schedule '%schedule' could not be run because requires a profile which is missing.", array('%schedule' => $schedule->get_name()), $settings);
  586. }
  587. }
  588. /**
  589. * Set the last run time of a schedule to the given timestamp, or now if none specified.
  590. */
  591. public function update_last_run($timestamp = NULL) {
  592. if ($timestamp === NULL) {
  593. $timestamp = time();
  594. }
  595. variable_set('backup_migrate_schedule_last_run_' . $this->get('id'), $timestamp);
  596. }
  597. /**
  598. * Set the last run time of a schedule to the given timestamp, or now if none specified.
  599. */
  600. public function get_last_run() {
  601. return variable_get('backup_migrate_schedule_last_run_' . $this->get('id'), 0);
  602. }
  603. /**
  604. * Remove older backups keeping only the number specified by the aministrator.
  605. */
  606. public function remove_expired_backups() {
  607. backup_migrate_include('destinations');
  608. $num_to_keep = $this->keep;
  609. // If num to keep is not 0 (0 is infinity).
  610. foreach ((array) $this->get_destinations() as $destination) {
  611. if ($destination && $destination->op('delete') && $destination_files = $destination->list_files()) {
  612. if ($num_to_keep == BACKUP_MIGRATE_SMART_DELETE) {
  613. $this->smart_delete_backups(
  614. $destination,
  615. $destination_files,
  616. variable_get('backup_migrate_smart_keep_subhourly', BACKUP_MIGRATE_SMART_KEEP_SUBHOURLY),
  617. variable_get('backup_migrate_smart_keep_hourly', BACKUP_MIGRATE_SMART_KEEP_HOURLY),
  618. variable_get('backup_migrate_smart_keep_daily', BACKUP_MIGRATE_SMART_KEEP_DAILY),
  619. variable_get('backup_migrate_smart_keep_weekly', BACKUP_MIGRATE_SMART_KEEP_WEEKLY)
  620. );
  621. }
  622. elseif ($num_to_keep != BACKUP_MIGRATE_KEEP_ALL) {
  623. $this->delete_backups($destination, $destination_files, $num_to_keep);
  624. }
  625. }
  626. }
  627. }
  628. /**
  629. * Remove older backups keeping only the number specified by the aministrator.
  630. */
  631. public function delete_backups($destination, $files, $num_to_keep) {
  632. backup_migrate_include('destinations');
  633. $num_to_keep = $this->keep;
  634. // Sort the files by modified time.
  635. $i = 0;
  636. foreach ($files as $id => $file) {
  637. if ($file->is_recognized_type()) {
  638. $time = $file->info('filetime');
  639. $sorted[$id] = $time;
  640. }
  641. }
  642. asort($sorted);
  643. // If we are beyond our limit, remove as many as we need.
  644. $num_files = count($files);
  645. if ($num_files > $num_to_keep) {
  646. $num_to_delete = $num_files - $num_to_keep;
  647. // Delete from the start of the list (earliest).
  648. foreach ($sorted as $id => $time) {
  649. if (!$num_to_delete--) {
  650. break;
  651. }
  652. $destination->delete_file($id);
  653. }
  654. }
  655. }
  656. /**
  657. * Delete files keeping the specified number of hourly, daily, weekly and monthly backups.
  658. */
  659. 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) {
  660. $now = time();
  661. $periods = array(
  662. 'subhourly' => array(
  663. 'delta' => 1,
  664. 'keep' => $keep_subhourly,
  665. 'last_time' => 0,
  666. 'files' => array(),
  667. ),
  668. 'hourly' => array(
  669. 'delta' => 60 * 60,
  670. 'keep' => $keep_hourly,
  671. 'last_time' => 0,
  672. 'files' => array(),
  673. ),
  674. 'daily' => array(
  675. 'delta' => 60 * 60 * 24,
  676. 'keep' => $keep_daily,
  677. 'last_time' => 0,
  678. 'files' => array(),
  679. ),
  680. 'weekly' => array(
  681. 'delta' => 60 * 60 * 24 * 7,
  682. 'keep' => $keep_weekly,
  683. 'last_time' => 0,
  684. 'files' => array(),
  685. ),
  686. /*
  687. 'monthly' => array(
  688. 'delta' => 60*60*24*7*4,
  689. 'keep' => $keep_monthly,
  690. 'last_time' => 0,
  691. 'files' => array(),
  692. ),
  693. */
  694. );
  695. $keep_files = $filetimes = $times = $groups = $sorted = $saved = array();
  696. foreach ($files as $id => $file) {
  697. if ($file->is_recognized_type()) {
  698. $time = $file->info('filetime');
  699. $sorted[$id] = $time;
  700. }
  701. }
  702. arsort($sorted);
  703. $now = time();
  704. foreach ($periods as $i => $period) {
  705. foreach ($sorted as $id => $time) {
  706. if ($time < ($now - ($period['delta'] * $period['keep']))) {
  707. break;
  708. }
  709. if ($period['last_time'] == 0 || $time <= ($period['last_time'] - $period['delta'])) {
  710. $period['last_time'] = $time;
  711. $keep_files[$id] = $id;
  712. }
  713. }
  714. // Keep oldest backup or it will get deleted if it doesn't fall on an exact multiple of the period.
  715. if ($id) {
  716. $keep_files[$id] = $id;
  717. }
  718. }
  719. // Do the delete.
  720. foreach ($files as $id => $file) {
  721. if (!isset($keep_files[$id])) {
  722. $destination->delete_file($file->file_id());
  723. }
  724. }
  725. }
  726. }