crud.inc 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656
  1. <?php
  2. /**
  3. * @file
  4. * CRUD functions for backup and migrate types (schedules, profiles etc.).
  5. */
  6. define('BACKUP_MIGRATE_STORAGE_NONE', 0);
  7. define('BACKUP_MIGRATE_STORAGE_DB', 1);
  8. define('BACKUP_MIGRATE_STORAGE_OVERRIDEN', 2);
  9. /**
  10. * Return a list of CRUD types in the module.
  11. */
  12. function backup_migrate_crud_types() {
  13. $out = array(
  14. 'destination' => array(
  15. 'class' => 'backup_migrate_destination',
  16. 'include' => 'destinations',
  17. ),
  18. 'profile' => array(
  19. 'class' => 'backup_migrate_profile',
  20. 'include' => 'profiles',
  21. ),
  22. 'schedule' => array(
  23. 'class' => 'backup_migrate_schedule',
  24. 'include' => 'schedules',
  25. ),
  26. );
  27. return $out;
  28. }
  29. /**
  30. * Get a generic object of the given type to be used for static-like functions.
  31. *
  32. * I'm not using actual static method calls since they don't work on variables prior to PHP 5.3.0
  33. */
  34. function backup_migrate_crud_type_load($type) {
  35. $out = NULL;
  36. $types = backup_migrate_crud_types();
  37. if (!empty($types[$type])) {
  38. $info = $types[$type];
  39. if ($info['include']) {
  40. backup_migrate_include($info['include']);
  41. }
  42. $out = new $info['class'];
  43. }
  44. return $out;
  45. }
  46. /**
  47. * Get the menu items handled by the CRUD code.
  48. */
  49. function backup_migrate_crud_menu() {
  50. $items = array();
  51. foreach (backup_migrate_crud_types() as $type => $info) {
  52. $type = backup_migrate_crud_type_load($type);
  53. $items += (array)$type->get_menu_items();
  54. }
  55. return $items;
  56. }
  57. /**
  58. * Page callback to create a new item.
  59. */
  60. function backup_migrate_crud_ui_create() {
  61. if ($type = backup_migrate_crud_type_load(arg(BACKUP_MIGRATE_MENU_DEPTH))) {
  62. $item = $type->create(array());
  63. return drupal_get_form('backup_migrate_crud_edit_form', $item);
  64. }
  65. }
  66. /**
  67. * Page callback to list all items.
  68. */
  69. function backup_migrate_crud_ui_list() {
  70. $out = '';
  71. if ($type = backup_migrate_crud_type_load(arg(BACKUP_MIGRATE_MENU_DEPTH))) {
  72. $out = $type->get_list();
  73. }
  74. return $out;
  75. }
  76. /**
  77. * Page callback to edit an item.
  78. */
  79. function backup_migrate_crud_ui_edit($item_id = NULL) {
  80. if ($type = backup_migrate_crud_type_load(arg(BACKUP_MIGRATE_MENU_DEPTH))) {
  81. if ($item_id && $item = $type->item($item_id)) {
  82. return drupal_get_form('backup_migrate_crud_edit_form', $item);
  83. }
  84. drupal_goto(BACKUP_MIGRATE_MENU_PATH. '/' . arg(BACKUP_MIGRATE_MENU_DEPTH));
  85. }
  86. }
  87. /**
  88. * A form callback to edit an item.
  89. */
  90. function backup_migrate_crud_edit_form($form, $form_state, $item) {
  91. $form = $item->edit_form();
  92. $form['item'] = array(
  93. '#type' => 'value',
  94. '#value' => $item,
  95. );
  96. $form['#validate'][] = 'backup_migrate_crud_edit_form_validate';
  97. $form['#submit'][] = 'backup_migrate_crud_edit_form_submit';
  98. return $form;
  99. }
  100. /**
  101. * Validate the item edit form.
  102. */
  103. function backup_migrate_crud_edit_form_validate($form, &$form_state) {
  104. $item = $form_state['values']['item'];
  105. $item->edit_form_validate($form, $form_state);
  106. }
  107. /**
  108. * Submit the item edit form.
  109. */
  110. function backup_migrate_crud_edit_form_submit($form, &$form_state) {
  111. $item = $form_state['values']['item'];
  112. $item->edit_form_submit($form, $form_state);
  113. if (empty($form_state['redirect'])) {
  114. $form_state['redirect'] = BACKUP_MIGRATE_MENU_PATH . '/'. $item->type_name;
  115. }
  116. }
  117. /**
  118. * Page callback to delete an item.
  119. */
  120. function backup_migrate_crud_ui_delete($item_id = NULL) {
  121. if ($type = backup_migrate_crud_type_load(arg(BACKUP_MIGRATE_MENU_DEPTH))) {
  122. if ($item_id && $item = $type->item($item_id)) {
  123. return drupal_get_form('backup_migrate_crud_delete_confirm_form', $item);
  124. }
  125. drupal_goto('admin/content/backup_migrate/'. arg(BACKUP_MIGRATE_MENU_DEPTH));
  126. }
  127. }
  128. /**
  129. * Ask confirmation for deletion of a item.
  130. */
  131. function backup_migrate_crud_delete_confirm_form($form, &$form_state, $item) {
  132. $form['item'] = array(
  133. '#type' => 'value',
  134. '#value' => $item,
  135. );
  136. $message = $item->delete_confirm_message();
  137. return confirm_form($form, t('Are you sure?'), BACKUP_MIGRATE_MENU_PATH . '/'. $item->type_name, $message, t('Delete'), t('Cancel'));
  138. }
  139. /**
  140. * Delete a item after confirmation.
  141. */
  142. function backup_migrate_crud_delete_confirm_form_submit($form, &$form_state) {
  143. if ($form_state['values']['confirm']) {
  144. $item = $form_state['values']['item'];
  145. $item->delete();
  146. }
  147. $form_state['redirect'] = BACKUP_MIGRATE_MENU_PATH . "/". $item->type_name;
  148. }
  149. /**
  150. * Export an item.
  151. */
  152. function backup_migrate_crud_ui_export($item_id = NULL) {
  153. if ($type = backup_migrate_crud_type_load(arg(BACKUP_MIGRATE_MENU_DEPTH))) {
  154. if ($item_id && $item = $type->item($item_id)) {
  155. return drupal_get_form('backup_migrate_crud_export_form', $item->export());
  156. }
  157. drupal_goto(BACKUP_MIGRATE_MENU_PATH . '/' . arg(BACKUP_MIGRATE_MENU_DEPTH));
  158. }
  159. }
  160. /**
  161. * Ask confirmation for deletion of a destination.
  162. */
  163. function backup_migrate_crud_export_form($form, &$form_state, $export) {
  164. $form['export'] = array(
  165. '#title' => t('Exported content'),
  166. '#type' => 'textarea',
  167. '#rows' => min(30, count(explode("\n", $export))),
  168. '#value' => $export,
  169. );
  170. return $form;
  171. }
  172. /**
  173. * Get all items of the given type.
  174. */
  175. function backup_migrate_crud_get_items($type) {
  176. if ($type = backup_migrate_crud_type_load($type)) {
  177. return $type->all_items();
  178. }
  179. }
  180. /**
  181. * Get an item of the specified type.
  182. */
  183. function backup_migrate_crud_get_item($type, $id) {
  184. if ($type = backup_migrate_crud_type_load($type)) {
  185. return $type->item($id);
  186. }
  187. }
  188. /**
  189. * Create a new item of the given type.
  190. */
  191. function backup_migrate_crud_create_item($type, $params) {
  192. if ($type = backup_migrate_crud_type_load($type)) {
  193. return $type->create($params);
  194. }
  195. }
  196. /**
  197. * A base class for items which can be stored in the database, listed, edited, deleted etc.
  198. */
  199. class backup_migrate_item {
  200. var $db_table = '';
  201. var $type_name = '';
  202. var $storage = FALSE;
  203. var $default_values = array();
  204. var $singular = 'item';
  205. var $plural = 'items';
  206. /**
  207. * Constructor, set the basic info pulled from the db or generated programatically.
  208. */
  209. function __construct($params = array()) {
  210. $this->from_array((array)$params + (array)$this->get_default_values());
  211. }
  212. /**
  213. * Get the default values for standard parameters.
  214. */
  215. function get_default_values() {
  216. return $this->default_values;
  217. }
  218. /**
  219. * Save the item to the database.
  220. */
  221. function save() {
  222. if (!$this->get_id()) {
  223. $this->generate_id();
  224. }
  225. $data = $this->to_array();
  226. drupal_write_record($this->db_table, $data, !empty($this->storage) ? $this->get_primary_key() : array());
  227. }
  228. /**
  229. * Delete the item from the database.
  230. */
  231. function delete() {
  232. $keys = (array)$this->get_primary_key();
  233. db_query('DELETE FROM {' . $this->db_table . '} WHERE ' . $keys[0] . ' = :id', array(':id' => $this->get_id()));
  234. }
  235. /**
  236. * Load an existing item from an array.
  237. */
  238. function from_array($params) {
  239. foreach ($params as $key => $value) {
  240. if (method_exists($this, 'set_'. $key)) {
  241. $this->{'set_'. $key}($value);
  242. }
  243. else {
  244. $this->{$key} = $value;
  245. }
  246. }
  247. }
  248. /**
  249. * Return as an array of values.
  250. */
  251. function to_array() {
  252. $out = array();
  253. // Return fields as specified in the schema.
  254. $schema = $this->get_schema();
  255. if (!empty($schema['fields']) && is_array($schema['fields'])) {
  256. foreach ($schema['fields'] as $field => $info) {
  257. $out[$field] = $this->get($field);
  258. }
  259. }
  260. return $out;
  261. }
  262. /**
  263. * Return as an exported array of values.
  264. */
  265. function export() {
  266. $out = $this->to_array();
  267. ob_start();
  268. var_export($out);
  269. $out = ob_get_contents();
  270. ob_end_clean();
  271. return $out;
  272. }
  273. /**
  274. * Load an existing item from an database (serialized) array.
  275. */
  276. function load_row($data) {
  277. $params = array();
  278. $schema = $this->get_schema();
  279. // Load fields as specified in the schema.
  280. foreach ($schema['fields'] as $field => $info) {
  281. $params[$field] = empty($info['serialize']) ? $data[$field] : unserialize($data[$field]);
  282. }
  283. $this->from_array($params);
  284. }
  285. /**
  286. * Decode a loaded db row (unserialize necessary fields).
  287. */
  288. function decode_db_row($data) {
  289. $params = array();
  290. $schema = $this->get_schema();
  291. // Load fields as specified in the schema.
  292. foreach ($schema['fields'] as $field => $info) {
  293. $params[$field] = empty($info['serialize']) ? $data[$field] : unserialize($data[$field]);
  294. }
  295. return $params;
  296. }
  297. /**
  298. * Return the fields which must be serialized before saving to the db.
  299. */
  300. function get_serialized_fields() {
  301. $out = array();
  302. $schema = $this->get_schema();
  303. foreach ($schema['fields'] as $field => $info) {
  304. if (!empty($info['serialize'])) {
  305. $out[] = $field;
  306. }
  307. }
  308. return $out;
  309. }
  310. /**
  311. * Get the primary key field title from the schema.
  312. */
  313. function get_primary_key() {
  314. $schema = $this->get_schema();
  315. return @$schema['primary key'];
  316. }
  317. /**
  318. * Get the schema for the item type.
  319. */
  320. function get_schema() {
  321. return drupal_get_schema($this->db_table);
  322. }
  323. /**
  324. * Get the primary id for this item (if any is set).
  325. *
  326. * We only handle single field keys since that's all we need.
  327. */
  328. function get_id() {
  329. $keys = (array)$this->get_primary_key();
  330. return !empty($this->{$keys[0]}) ? (string)$this->{$keys[0]} : '';
  331. }
  332. /**
  333. * Set the primary id for this item (if any is set).
  334. */
  335. function set_id($id) {
  336. $keys = (array)$this->get_primary_key();
  337. if (!empty($keys[0])) {
  338. return $this->{$keys[0]} = $id;
  339. }
  340. return NULL;
  341. }
  342. /**
  343. * Return a random (very very likely unique) string id for a new item.
  344. */
  345. function generate_id() {
  346. $this->set_id(md5(uniqid(mt_rand(), true)));
  347. }
  348. /**
  349. * Get the name of the item.
  350. */
  351. function get_name() {
  352. return @$this->name;
  353. }
  354. /**
  355. * Get the member with the given key.
  356. */
  357. function get($key) {
  358. if (method_exists($this, 'get_'. $key)) {
  359. return $this->{'get_'. $key}();
  360. }
  361. return @$this->{$key};
  362. }
  363. /* UI Stuff */
  364. /**
  365. * Get the action links for a destination.
  366. */
  367. function get_action_links() {
  368. $out = array('edit' => '', 'delete' => '');
  369. $item_id = $this->get_id();
  370. if (@$this->storage == BACKUP_MIGRATE_STORAGE_DB || @$this->storage == BACKUP_MIGRATE_STORAGE_OVERRIDEN) {
  371. $out['edit'] = l(t("edit"), BACKUP_MIGRATE_MENU_PATH . "/$this->type_name/list/edit/$item_id");
  372. }
  373. else if (@$this->storage == BACKUP_MIGRATE_STORAGE_NONE) {
  374. $out['edit'] = l(t("override"), BACKUP_MIGRATE_MENU_PATH . "/$this->type_name/list/edit/$item_id");
  375. }
  376. if (@$this->storage == BACKUP_MIGRATE_STORAGE_DB) {
  377. $out['delete'] = l(t("delete"), BACKUP_MIGRATE_MENU_PATH . "/$this->type_name/list/delete/$item_id");
  378. }
  379. else if (@$this->storage == BACKUP_MIGRATE_STORAGE_OVERRIDEN) {
  380. $out['delete'] = l(t("revert"), BACKUP_MIGRATE_MENU_PATH . "/$this->type_name/list/delete/$item_id");
  381. }
  382. // Export link disabled until we have an import function.
  383. //$out['export'] = l(t("export"), BACKUP_MIGRATE_MENU_PATH . "/$this->type_name/list/export/$item_id");
  384. return $out;
  385. }
  386. /**
  387. * Get a table of all items of this type.
  388. */
  389. function get_list() {
  390. $items = $this->all_items();
  391. $rows = array();
  392. foreach ($items as $item) {
  393. if ($row = $item->get_list_row()) {
  394. $rows[] = $row;
  395. }
  396. }
  397. if (count($rows)) {
  398. $out = theme('table', array('header' => $this->get_list_header(), 'rows' => $rows));
  399. }
  400. else {
  401. $out = t('There are no !items to display.', array('!items' => $this->plural));
  402. }
  403. return $out;
  404. }
  405. /**
  406. * Get the columns needed to list the type.
  407. */
  408. function get_list_column_info() {
  409. return array(
  410. 'actions' => array('title' => t('Operations'), 'html' => TRUE),
  411. );
  412. }
  413. /**
  414. * Get header for a lost of this type.
  415. */
  416. function get_list_header() {
  417. $out = array();
  418. foreach ($this->get_list_column_info() as $key => $col) {
  419. $out[] = $col['title'];
  420. }
  421. return $out;
  422. }
  423. /**
  424. * Get a row of data to be used in a list of items of this type.
  425. */
  426. function get_list_row() {
  427. $out = array();
  428. foreach ($this->get_list_column_info() as $key => $col) {
  429. $out[$key] = empty($col['html']) ? check_plain($this->get($key)) : $this->get($key);
  430. }
  431. return $out;
  432. }
  433. /**
  434. * Get the rendered action links for a destination.
  435. */
  436. function get_actions() {
  437. $links = $this->get_action_links();
  438. return implode(" &nbsp; ", $links);
  439. }
  440. /**
  441. * Get the edit form for the item.
  442. */
  443. function edit_form() {
  444. $form = array();
  445. $form['item'] = array(
  446. '#type' => 'value',
  447. '#value' => $this,
  448. );
  449. $form['id'] = array(
  450. '#type' => 'value',
  451. '#value' => $this->get_id(),
  452. );
  453. $form['actions'] = array('#prefix' => '<div class="container-inline">', '#suffix' => '</div>', '#weight' => 99);
  454. $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save !type', array('!type' => t($this->singular))));
  455. $form['actions']['cancel'] = array('#markup' => l(t('Cancel'), BACKUP_MIGRATE_MENU_PATH . '/destination'));
  456. return $form;
  457. }
  458. /**
  459. * Validate the edit form for the item.
  460. */
  461. function edit_form_validate($form, &$form_state) {
  462. }
  463. /**
  464. * Submit the edit form for the item.
  465. */
  466. function edit_form_submit($form, &$form_state) {
  467. $this->from_array($form_state['values']);
  468. $this->save();
  469. _backup_migrate_message('!type saved', array('!type' => t(ucwords($this->singular))));
  470. }
  471. /**
  472. * Get the message to send to the user when confirming the deletion of the item.
  473. */
  474. function delete_confirm_message() {
  475. return t('Are you sure you want to delete this !type?', array('!type' => t($item->singular)));
  476. }
  477. /* Static Functions */
  478. /**
  479. * This function is not supposed to be called. It is just here to help the po extractor out.
  480. */
  481. function strings() {
  482. // Help the pot extractor find these strings.
  483. t('List !type');
  484. t('Create !type');
  485. t('Delete !type');
  486. t('Edit !type');
  487. t('Export !type');
  488. }
  489. /**
  490. * Get the menu items for manipulating this type.
  491. */
  492. function get_menu_items() {
  493. $type = $this->type_name;
  494. $items[BACKUP_MIGRATE_MENU_PATH . '/' . $type] = array(
  495. 'title' => ucwords($this->plural),
  496. 'page callback' => 'backup_migrate_menu_callback',
  497. 'page arguments' => array('crud', 'backup_migrate_crud_ui_list', TRUE),
  498. 'access arguments' => array('administer backup and migrate'),
  499. 'weight' => 2,
  500. 'type' => MENU_LOCAL_TASK,
  501. );
  502. $items[BACKUP_MIGRATE_MENU_PATH . '/' . $type .'/list'] = array(
  503. 'title' => 'List !type',
  504. 'title arguments' => array('!type' => t(ucwords($this->plural))),
  505. 'weight' => 1,
  506. 'type' => MENU_DEFAULT_LOCAL_TASK,
  507. );
  508. $items[BACKUP_MIGRATE_MENU_PATH . '/' . $type .'/list/add'] = array(
  509. 'title' => 'Add !type',
  510. 'title arguments' => array('!type' => t(ucwords($this->singular))),
  511. 'page callback' => 'backup_migrate_menu_callback',
  512. 'page arguments' => array('crud', 'backup_migrate_crud_ui_create', TRUE),
  513. 'access arguments' => array('administer backup and migrate'),
  514. 'weight' => 2,
  515. 'type' => MENU_LOCAL_ACTION,
  516. );
  517. $items[BACKUP_MIGRATE_MENU_PATH . '/' . $type .'/list/delete'] = array(
  518. 'title' => 'Delete !type',
  519. 'title arguments' => array('!type' => t(ucwords($this->singular))),
  520. 'page callback' => 'backup_migrate_menu_callback',
  521. 'page arguments' => array('crud', 'backup_migrate_crud_ui_delete', TRUE),
  522. 'access arguments' => array('administer backup and migrate'),
  523. 'type' => MENU_CALLBACK,
  524. );
  525. $items[BACKUP_MIGRATE_MENU_PATH . '/' . $type .'/list/edit'] = array(
  526. 'title' => 'Edit !type',
  527. 'title arguments' => array('!type' => t(ucwords($this->singular))),
  528. 'page callback' => 'backup_migrate_menu_callback',
  529. 'page arguments' => array('crud', 'backup_migrate_crud_ui_edit', TRUE),
  530. 'access arguments' => array('administer backup and migrate'),
  531. 'type' => MENU_CALLBACK,
  532. );
  533. $items[BACKUP_MIGRATE_MENU_PATH . '/' . $type .'/list/export'] = array(
  534. 'title' => 'Export !type',
  535. 'title arguments' => array('!type' => t(ucwords($this->singular))),
  536. 'page callback' => 'backup_migrate_menu_callback',
  537. 'page arguments' => array('crud', 'backup_migrate_crud_ui_export', TRUE),
  538. 'access arguments' => array('administer backup and migrate'),
  539. 'type' => MENU_CALLBACK,
  540. );
  541. return $items;
  542. }
  543. /**
  544. * Create a new items with the given input. Doesn't load the parameters, but could use them to determine what type to create.
  545. */
  546. function create($params = array()) {
  547. $type = get_class($this);
  548. return new $type($params);
  549. }
  550. /**
  551. * Get all of the given items.
  552. */
  553. function all_items() {
  554. static $cache = array();
  555. // Allow other modules to declare destinations programatically.
  556. $items = module_invoke_all($this->db_table);
  557. // Get any items stored as a variable. This allows destinations to be defined in settings.php
  558. $defaults = (array)variable_get($this->db_table .'_defaults', array());
  559. foreach ($defaults as $info) {
  560. if (is_array($info) && $item = $this->create($info)) {
  561. $items[$item->get_id()] = $item;
  562. }
  563. }
  564. // Get the items from the db.
  565. $result = db_query("SELECT * FROM {{$this->db_table}}", array(), array('fetch' => PDO::FETCH_ASSOC));
  566. foreach ($result as $info) {
  567. $info = $this->decode_db_row($info);
  568. if ($item = $this->create($info)) {
  569. $item->storage = empty($items[$item->get_id()]) ? BACKUP_MIGRATE_STORAGE_DB : BACKUP_MIGRATE_STORAGE_OVERRIDEN;
  570. $items[$item->get_id()] = $item;
  571. }
  572. }
  573. // Allow other modules to alter the items. This should maybe be before the db override code above
  574. // but then the filters are not able to set defaults for missing values. Other modules should just
  575. // be careful not to overwrite the user's UI changes in an unexpected way.
  576. drupal_alter($this->db_table, $items);
  577. return $items;
  578. }
  579. /**
  580. * A particular item.
  581. */
  582. function item($item_id) {
  583. $items = $this->all_items();
  584. return !empty($items[$item_id]) ? $items[$item_id] : NULL;
  585. }
  586. }