tripal_core.chado_nodes.api.inc 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901
  1. <?php
  2. /**
  3. * @file
  4. * API to handle much of the common functionality implemented when creating a drupal node type.
  5. */
  6. /**
  7. * @defgroup tripal_chado_node_api Chado Node API
  8. * @ingroup tripal_chado_api
  9. * @{
  10. * Many Tripal modules implement Drupal node types as a means of displaying chado
  11. * records individually through Drupal as a single web page. In order to do this, many of
  12. * the same drupal hooks are implemented and the code between modules is actually quite
  13. * similar. This API aims to abstract much of the common functionality in order to make
  14. * it easier for new Tripal modules to implement drupal node types and to centralize the
  15. * maintenance effort as much as possible.
  16. *
  17. * A generic sync form has been created. See chado_node_sync_form() for
  18. * instructions on how to implement this form in your module.
  19. *
  20. * Many of the base chado tables also have associated prop, _dbxref and _relationship
  21. * tables. Generic mini-forms have been created to help you handle these forms. To
  22. * implement this functionality you call the mini-form from your module node form and
  23. * then call the associated update functions from both your hook_insert and hook_update.
  24. * The functions of interest are as follows:
  25. * - chado_add_node_form_properties() and chado_update_node_form_properties()
  26. * to provide an interface for adding/removing properties
  27. * - chado_add_node_form_dbxrefs() and chado_update_node_form_dbxrefs()
  28. * to provide an interface for adding/removing additional database references
  29. * - chado_add_node_form_relationships() and chado_update_node_form_relationships()
  30. * to provide an interface for adding/removing relationships between chado records
  31. * from your base table
  32. * @}
  33. */
  34. /**
  35. * Get chado id for a node. E.g, if you want to get 'analysis_id' from the
  36. * 'analysis' table for a synced 'chado_analysis' node, (the same for
  37. * organisms and features):
  38. * $analysis_id = chado_get_id_from_nid ('analysis', $node->nid)
  39. * $organism_id = chado_get_id_from_nid ('organism', $node->nid)
  40. * $feature_id = chado_get_id_from_nid ('feature', $node->nid)
  41. *
  42. * @param $table
  43. * The chado table the chado record is from
  44. * @param $nid
  45. * The value of the primary key of node
  46. * @param $linking_table
  47. * The Drupal table linking the chado record to it's node.
  48. * This field is optional and defaults to chado_$table
  49. *
  50. * @return
  51. * The chado id of the associated chado record
  52. *
  53. * @ingroup tripal_chado_node_api
  54. */
  55. function chado_get_id_from_nid($table, $nid, $linking_table = NULL) {
  56. if (empty($linking_table)) {
  57. $linking_table = 'chado_' . $table;
  58. }
  59. $sql = "SELECT " . $table . "_id as id FROM {$linking_table} WHERE nid = :nid";
  60. return db_query($sql, array(':nid' => $nid))->fetchField();
  61. }
  62. /**
  63. * Get node id for a chado feature/organism/analysis. E.g, if you want to
  64. * get the node id for an analysis, use:
  65. * $nid = chado_get_nid_from_id ('analysis', $analysis_id)
  66. * Likewise,
  67. * $nid = chado_get_nid_from_id ('organism', $organism_id)
  68. * $nid = chado_get_nid_from_id ('feature', $feature_id)
  69. *
  70. * @param $table
  71. * The chado table the id is from
  72. * @param $id
  73. * The value of the primary key from the $table chado table (ie: feature_id)
  74. * @param $linking_table
  75. * The Drupal table linking the chado record to it's node.
  76. * This field is optional and defaults to chado_$table
  77. *
  78. * @return
  79. * The nid of the associated node
  80. *
  81. * @ingroup tripal_chado_node_api
  82. */
  83. function chado_get_nid_from_id($table, $id, $linking_table = NULL) {
  84. if (empty($linking_table)) {
  85. $linking_table = 'chado_' . $table;
  86. }
  87. $sql = "SELECT nid FROM {" . $linking_table . "} WHERE " . $table . "_id = :" . $table . "_id";
  88. return db_query($sql, array(":" . $table . "_id" => $id))->fetchField();
  89. }
  90. /**
  91. * Determine the chado base table for a given content type
  92. *
  93. * @param $content_type
  94. * The machine name of the content type (node type) you want to
  95. * determine the base chado table of
  96. * @param $module
  97. * (Optional) The machine-name of the module implementing the
  98. * content type
  99. *
  100. * @return
  101. * The name of the chado base table for the specified content type
  102. *
  103. * @ingroup tripal_chado_node_api
  104. */
  105. function chado_node_get_base_table($content_type, $module = FALSE) {
  106. if ($module) {
  107. $node_info = call_user_func($details['module'] . '_node_info');
  108. }
  109. else {
  110. $node_types = module_invoke_all('node_info');
  111. if (isset($node_types[$content_type])) {
  112. $node_info = $node_types[$content_type];
  113. }
  114. else {
  115. return FALSE;
  116. }
  117. }
  118. if (isset($node_info['chado_node_api']['base_table'])) {
  119. return $node_info['chado_node_api']['base_table'];
  120. }
  121. else {
  122. return FALSE;
  123. }
  124. }
  125. /**
  126. * @section
  127. * Sync Form
  128. */
  129. /**
  130. * Generic Sync Form to aid in sync'ing (create drupal nodes linking to chado content)
  131. * any chado node type.
  132. *
  133. * To use this you need to add a call to it from your hook_menu() and
  134. * add some additional information to your hook_node_info(). The Following code gives an
  135. * example of how this might be done:
  136. * @code
  137. function modulename_menu() {
  138. // the machine name of your module
  139. $module_name = 'tripal_example';
  140. // the base specified in hook_node_info
  141. $linking_table = 'chado_example';
  142. // This menu item will be a tab on the admin/tripal/chado/tripal_example page
  143. // that is not selected by default
  144. $items['admin/tripal/chado/tripal_example/sync'] = array(
  145. 'title' => ' Sync',
  146. 'description' => 'Sync examples from Chado with Drupal',
  147. 'page callback' => 'drupal_get_form',
  148. 'page arguments' => array('chado_node_sync_form', $module_name, $linking_table),
  149. 'access arguments' => array('administer tripal examples'),
  150. 'type' => MENU_LOCAL_TASK,
  151. 'weight' => 0
  152. );
  153. return $items;
  154. }
  155. function modulename_node_info() {
  156. return array(
  157. 'chado_example' => array(
  158. 'name' => t('example'),
  159. 'base' => 'chado_example',
  160. 'description' => t('A Chado example is a collection of material that can be sampled and have experiments performed on it.'),
  161. 'has_title' => TRUE,
  162. 'locked' => TRUE,
  163. // this is what differs from the regular Drupal-documented hook_node_info()
  164. 'chado_node_api' => array(
  165. 'base_table' => 'example', // the name of the chado base table
  166. 'hook_prefix' => 'chado_example', // usually the name of the node type
  167. 'record_type_title' => array(
  168. 'singular' => t('Example'), // Singular human-readable title
  169. 'plural' => t('Examples') // Plural human-readable title
  170. ),
  171. 'sync_filters' => array( // filters for syncing
  172. 'type_id' => TRUE, // TRUE if there is an example.type_id field
  173. 'organism_id' => TRUE, // TRUE if there is an example.organism_id field
  174. 'checkboxes' => array('name') // If the 'checkboxes' key is present then the
  175. // value must be an array of column names in
  176. // base table. The values from these columns will
  177. // be retreived, contentated with a space delimeter
  178. // and provided in a list of checkboxes
  179. // for the user to choose which to sync.
  180. ),
  181. )
  182. ),
  183. );
  184. }
  185. * @endcode
  186. *
  187. * For more information on how you can override some of this behaviour while still
  188. * benifiting from as much of the common architecture as possible see the following
  189. * functions: hook_chado_node_sync_create_new_node(), hook_chado_node_sync_form(),
  190. * hook_chado_node_sync_select_query().
  191. *
  192. * @ingroup tripal_chado_node_api
  193. */
  194. function chado_node_sync_form($form, &$form_state) {
  195. $form = array();
  196. if (isset($form_state['build_info']['args'][0])) {
  197. $module = $form_state['build_info']['args'][0];
  198. $linking_table = $form_state['build_info']['args'][1];
  199. $node_info = call_user_func($module . '_node_info');
  200. $args = $node_info[$linking_table]['chado_node_api'];
  201. $form_state['chado_node_api'] = $args;
  202. }
  203. $form['linking_table'] = array(
  204. '#type' => 'hidden',
  205. '#value' => $linking_table
  206. );
  207. // define the fieldsets
  208. $form['sync'] = array(
  209. '#type' => 'fieldset',
  210. '#title' => 'Sync ' . $args['record_type_title']['plural'],
  211. '#descrpition' => '',
  212. );
  213. $form['sync']['description'] = array(
  214. '#type' => 'item',
  215. '#value' => t("%title_plural of the types listed ".
  216. "below in the %title_singular Types box will be synced (leave blank to sync all types). You may limit the ".
  217. "%title_plural to be synced by a specific organism. Depending on the ".
  218. "number of %title_plural in the chado database this may take a long ".
  219. "time to complete. ",
  220. array(
  221. '%title_singular' => $args['record_type_title']['singular'],
  222. '%title_plural' => $args['record_type_title']['plural']
  223. )),
  224. );
  225. if ($args['sync_filters']['type_id']) {
  226. $form['sync']['type_ids'] = array(
  227. '#title' => t('%title_singular Types',
  228. array(
  229. '%title_singular' => $args['record_type_title']['singular'],
  230. '%title_plural' => $args['record_type_title']['plural']
  231. )),
  232. '#type' => 'textarea',
  233. '#description' => t("Enter the names of the %title_singular types to sync. " .
  234. "Leave blank to sync all %title_plural. Separate each type with a comma ".
  235. "or new line. Pages for these %title_singular ".
  236. "types will be created automatically for %title_plural that exist in the ".
  237. "chado database. The names must match ".
  238. "exactly (spelling and case) with terms in the ontologies",
  239. array(
  240. '%title_singular' => strtolower($args['record_type_title']['singular']),
  241. '%title_plural' => strtolower($args['record_type_title']['plural'])
  242. )),
  243. '#default_value' => (isset($form_state['values']['type_id'])) ? $form_state['values']['type_id'] : '',
  244. );
  245. }
  246. // get the list of organisms
  247. if ($args['sync_filters']['organism_id']) {
  248. $sql = "SELECT * FROM {organism} ORDER BY genus, species";
  249. $results = chado_query($sql);
  250. $organisms[] = '';
  251. foreach ($results as $organism) {
  252. $organisms[$organism->organism_id] = "$organism->genus $organism->species ($organism->common_name)";
  253. }
  254. $form['sync']['organism_id'] = array(
  255. '#title' => t('Organism'),
  256. '#type' => t('select'),
  257. '#description' => t("Choose the organism for which %title_plural types set above will be synced.",
  258. array(
  259. '%title_singular' => $args['record_type_title']['singular'],
  260. '%title_plural' => $args['record_type_title']['plural']
  261. )),
  262. '#options' => $organisms,
  263. '#default_value' => (isset($form_state['values']['organism_id'])) ? $form_state['values']['organism_id'] : 0,
  264. );
  265. }
  266. // get the list of organisms
  267. if (array_key_exists('checkboxes', $args['sync_filters'])) {
  268. // get the base schema
  269. $base_table = $args['base_table'];
  270. $table_info = chado_get_schema($base_table);
  271. // if the base table does not have a primary key or has more than one then
  272. // we can't proceed, otherwise, generate the checkboxes
  273. if (array_key_exists('primary key', $table_info) and count($table_info['primary key']) == 1) {
  274. $pkey = $table_info['primary key'][0];
  275. $columns = $args['sync_filters']['checkboxes'];
  276. $select_cols = implode("|| ' ' ||", $columns);
  277. // get non-synced records
  278. $sql = "
  279. SELECT BT.$pkey as id, $select_cols as value
  280. FROM {" . $base_table . "} BT
  281. LEFT JOIN public.$linking_table LT ON LT.$pkey = BT.$pkey
  282. WHERE LT.$pkey IS NULL
  283. ORDER BY value ASC
  284. ";
  285. $results = chado_query($sql);
  286. $values = array();
  287. foreach ($results as $result) {
  288. $values[$result->id] = $result->value;
  289. }
  290. if (count($values) > 0) {
  291. $form['sync']['ids'] = array(
  292. '#title' => 'Avaliable ' . $args['record_type_title']['plural'],
  293. '#type' => 'checkboxes',
  294. '#options' => $values,
  295. '#default_value' => (isset($form_state['values']['ids'])) ? $form_state['values']['ids'] : array(),
  296. '#suffix' => '</div><br>',
  297. '#prefix' => t("The following %title_plural have not been synced. Check those to be synced or leave all unchecked to sync them all.",
  298. array(
  299. '%title_singular' => strtolower($args['record_type_title']['singular']),
  300. '%title_plural' => strtolower($args['record_type_title']['plural'])
  301. )) . '<div style="height: 200px; overflow: scroll">',
  302. );
  303. }
  304. else {
  305. $form['sync']['no_ids'] = array(
  306. '#markup' => "<p>There are no " . strtolower($args['record_type_title']['plural']) . " to sync.</p>",
  307. );
  308. }
  309. }
  310. }
  311. // if we provide a list of checkboxes we shouldn't need a max_sync
  312. else {
  313. $form['sync']['max_sync'] = array(
  314. '#type' => 'textfield',
  315. '#title' => t('Maximum number of records to Sync'),
  316. '#description' => t('Leave this field empty to sync all records, regardless of number'),
  317. '#default_value' => (isset($form_state['values']['max_sync'])) ? $form_state['values']['max_sync'] : '',
  318. );
  319. }
  320. $form['sync']['button'] = array(
  321. '#type' => 'submit',
  322. '#value' => t('Sync ' . $args['record_type_title']['plural']),
  323. '#weight' => 3,
  324. );
  325. $form['cleanup'] = array(
  326. '#type' => 'fieldset',
  327. '#title' => t('Clean Up')
  328. );
  329. $form['cleanup']['description'] = array(
  330. '#markup' => t("<p>With Drupal and chado residing in different databases " .
  331. "it is possible that nodes in Drupal and " . strtolower($args['record_type_title']['plural']) . " in Chado become " .
  332. "\"orphaned\". This can occur if a node in Drupal is " .
  333. "deleted but the corresponding chado records is not and/or vice " .
  334. "versa. Click the button below to resolve these discrepancies.</p>"),
  335. '#weight' => 1,
  336. );
  337. $form['cleanup']['button'] = array(
  338. '#type' => 'submit',
  339. '#value' => 'Clean up orphaned ' . strtolower($args['record_type_title']['plural']),
  340. '#weight' => 2,
  341. );
  342. // Allow each module to alter this form as needed
  343. $hook_form_alter = $args['hook_prefix'] . '_chado_node_sync_form';
  344. if (function_exists($hook_form_alter)) {
  345. $form = call_user_func($hook_form_alter, $form, $form_state);
  346. }
  347. return $form;
  348. }
  349. /**
  350. * Generic Sync Form Submit
  351. *
  352. * @ingroup tripal_core
  353. */
  354. function chado_node_sync_form_submit($form, $form_state) {
  355. global $user;
  356. if (preg_match('/^Sync/', $form_state['values']['op'])) {
  357. // get arguments
  358. $args = $form_state['chado_node_api'];
  359. $module = $form_state['chado_node_api']['hook_prefix'];
  360. $base_table = $form_state['chado_node_api']['base_table'];
  361. $linking_table = $form_state['values']['linking_table'];
  362. // Allow each module to hijack the submit if needed
  363. $hook_form_hijack_submit = $args['hook_prefix'] . '_chado_node_sync_form_submit';
  364. if (function_exists($hook_form_hijack_submit)) {
  365. return call_user_func($hook_form_hijack_submit, $form, $form_state);
  366. }
  367. // Get the types separated into a consistent string
  368. $types = array();
  369. if (isset($form_state['values']['type_ids'])) {
  370. // seperate by new line or comma.
  371. $temp_types = preg_split("/[,\n\r]+/", $form_state['values']['type_ids']);
  372. // remove any extra spacing around the types
  373. for($i = 0; $i < count($temp_types); $i++) {
  374. // skip empty types
  375. if (trim($temp_types[$i]) == '') {
  376. continue;
  377. }
  378. $types[$i] = trim($temp_types[$i]);
  379. }
  380. }
  381. // Get the ids to be synced
  382. $ids = array();
  383. if (array_key_exists('ids', $form_state['values'])){
  384. foreach ($form_state['values']['ids'] as $id => $selected) {
  385. if ($selected) {
  386. $ids[] = $id;
  387. }
  388. }
  389. }
  390. // get the organism to be synced
  391. $organism_id = FALSE;
  392. if (array_key_exists('organism_id', $form_state['values'])) {
  393. $organism_id = $form_state['values']['organism_id'];
  394. }
  395. // Job Arguments
  396. $job_args = array(
  397. 'base_table' => $base_table,
  398. 'max_sync' => (!empty($form_state['values']['max_sync'])) ? $form_state['values']['max_sync'] : FALSE,
  399. 'organism_id' => $organism_id,
  400. 'types' => $types,
  401. 'ids' => $ids,
  402. 'inking_table' => $linking_table
  403. );
  404. $title = "Sync " . $args['record_type_title']['plural'];
  405. tripal_add_job($title, $module, 'chado_node_sync_records', $job_args, $user->uid);
  406. }
  407. if (preg_match('/^Clean up orphaned/', $form_state['values']['op'])) {
  408. $module = $form_state['chado_node_api']['hook_prefix'];
  409. $base_table = $form_state['chado_node_api']['base_table'];
  410. $job_args = array($base_table);
  411. tripal_add_job($form_state['values']['op'], $module, 'chado_cleanup_orphaned_nodes', $job_args, $user->uid);
  412. }
  413. }
  414. /**
  415. * Actual Sync Function. Works on a group of records
  416. *
  417. * @ingroup tripal_chado_node_api
  418. */
  419. function chado_node_sync_records($base_table, $max_sync = FALSE, $organism_id = FALSE,
  420. $types = array(), $ids = array(), $linking_table = FALSE, $job_id = NULL) {
  421. global $user;
  422. $base_table_id = $base_table . '_id';
  423. if (!$linking_table) {
  424. $linking_table = 'chado_' . $base_table;
  425. }
  426. print "\nSync'ing $base_table records. ";
  427. // START BUILDING QUERY TO GET ALL RECORD FROM BASE TABLE THAT MATCH
  428. $select = array("$base_table.*");
  429. $joins = array();
  430. $where_clauses = array();
  431. $where_args = array();
  432. // If types are supplied then handle them
  433. $restrictions = '';
  434. if (count($types) > 0) {
  435. $restrictions .= " Type(s): " . implode(', ',$types) . "\n";
  436. $select[] = 'cvterm.name as cvtname';
  437. $joins[] = "LEFT JOIN {cvterm} cvterm ON $base_table.type_id = cvterm.cvterm_id";
  438. foreach ($types as $type) {
  439. $sanitized_type = str_replace(' ','_',$type);
  440. $where_clauses['type'][] = "cvterm.name = :type_name_$sanitized_type";
  441. $where_args['type'][":type_name_$sanitized_type"] = $type;
  442. }
  443. }
  444. // if IDs have been supplied
  445. if ($ids) {
  446. $restrictions .= " Specific Records: " . count($ids) . " recored(s) specified.\n";
  447. foreach ($ids as $id) {
  448. $where_clauses['id'][] = "$base_table.$base_table_id = :id_$id";
  449. $where_args['id'][":id_$id"] = $id;
  450. }
  451. }
  452. // If Organism is supplied
  453. if ($organism_id) {
  454. $organism = chado_select_record('organism', array('*'), array('organism_id' => $organism_id));
  455. $restrictions .= " Organism: " . $organism[0]->genus . " " . $organism[0]->species . "\n";
  456. $select[] = 'organism.*';
  457. $joins[] = "LEFT JOIN {organism} organism ON organism.organism_id = $base_table.organism_id";
  458. $where_clauses['organism'][] = 'organism.organism_id = :organism_id';
  459. $where_args['organism'][':organism_id'] = $organism_id;
  460. }
  461. // Allow module to add to query
  462. $hook_query_alter = $linking_table . '_chado_node_sync_select_query';
  463. if (function_exists($hook_query_alter)) {
  464. $update = call_user_func($hook_query_alter, array(
  465. 'select' => $select,
  466. 'joins' => $joins,
  467. 'where_clauses' => $where_clauses,
  468. 'where_args' => $where_args,
  469. ));
  470. // Now add in any new changes
  471. if ($update and is_array($update)) {
  472. $select = $update['select'];
  473. $joins = $update['joins'];
  474. $where_clauses = $update['where_clauses'];
  475. $where_args = $update['where_args'];
  476. }
  477. }
  478. // Build Query, we do a left join on the chado_xxxx table in the Drupal schema
  479. // so that if no criteria are specified we only get those items that have not
  480. // yet been synced.
  481. $query = "
  482. SELECT " . implode(', ', $select) . ' ' .
  483. 'FROM {' . $base_table . '} ' . $base_table . ' ' . implode(' ', $joins) . ' '.
  484. " LEFT JOIN public.$linking_table CT ON CT.$base_table_id = $base_table.$base_table_id " .
  485. "WHERE CT.$base_table_id IS NULL ";
  486. // extend the where clause if needed
  487. $where = '';
  488. $sql_args = array();
  489. foreach ($where_clauses as $category => $items) {
  490. $where .= ' AND (';
  491. foreach ($items as $item) {
  492. $where .= $item . ' OR ';
  493. }
  494. $where = substr($where, 0, -4); // remove the trailing 'OR'
  495. $where .= ') ';
  496. $sql_args = array_merge($sql_args, $where_args[$category]);
  497. }
  498. if ($where) {
  499. $query .= $where;
  500. }
  501. $query .- " ORDER BY " . $base_table_id;
  502. // If Maximum number to Sync is supplied
  503. if ($max_sync) {
  504. $query .= " LIMIT $max_sync";
  505. $restrictions .= " Limited to $max_sync records.\n";
  506. }
  507. if ($restrictions) {
  508. print "Records matching these criteria will be synced: \n$restrictions";
  509. }
  510. else {
  511. print "\n";
  512. }
  513. // execute the query
  514. $results = chado_query($query, $sql_args);
  515. // Iterate through records that need to be synced
  516. $count = $results->rowCount();
  517. $interval = intval($count * 0.01);
  518. if ($interval < 1) {
  519. $interval = 1;
  520. }
  521. print "\n$count $base_table records found.\n";
  522. $i = 0;
  523. $transaction = db_transaction();
  524. print "\nNOTE: Syncing is performed using a database transaction. \n" .
  525. "If the sync fails or is terminated prematurely then the entire set of \n" .
  526. "synced items is rolled back and will not be found in the database\n\n";
  527. try {
  528. foreach ($results as $record) {
  529. //print "\nLoading $base_table " . ($i + 1) . " of $count ($base_table_id=" . $record->{$base_table_id} . ")...";
  530. // update the job status every 1% features
  531. if ($job_id and $i % $interval == 0) {
  532. $percent = sprintf("%.2f", (($i + 1) / $count) * 100);
  533. print "Syncing $base_table " . ($i + 1) . " of $count (" . $percent . "%). Memory: " . number_format(memory_get_usage()) . " bytes.\r";
  534. tripal_set_job_progress($job_id, intval(($i/$count)*100));
  535. }
  536. // Check if it is in the chado linking table (ie: check to see if it is already linked to a node)
  537. $result = db_select($linking_table, 'lnk')
  538. ->fields('lnk',array('nid'))
  539. ->condition($base_table_id, $record->{$base_table_id}, '=')
  540. ->execute()
  541. ->fetchObject();
  542. if (!empty($result)) {
  543. //print " Previously Sync'd";
  544. }
  545. else {
  546. // Create generic new node
  547. $new_node = new stdClass();
  548. $new_node->type = $linking_table;
  549. $new_node->uid = $user->uid;
  550. $new_node->{$base_table_id} = $record->{$base_table_id};
  551. $new_node->$base_table = $record;
  552. $new_node->language = LANGUAGE_NONE;
  553. // TODO: should we get rid of this hook and use hook_node_presave() instead?
  554. // allow base module to set additional fields as needed
  555. $hook_create_new_node = $linking_table . '_chado_node_sync_create_new_node';
  556. if (function_exists($hook_create_new_node)) {
  557. $new_node = call_user_func($hook_create_new_node, $new_node, $record);
  558. }
  559. // Validate and Save New Node
  560. $form = array();
  561. $form_state = array();
  562. node_validate($new_node, $form, $form_state);
  563. if (!form_get_errors()) {
  564. $node = node_submit($new_node);
  565. node_save($node);
  566. //print " Node Created (nid=".$node->nid.")";
  567. }
  568. else {
  569. watchdog('trp-fsync', "Failed to insert $base_table: %title", array('%title' => $new_node->title), WATCHDOG_ERROR);
  570. }
  571. }
  572. $i++;
  573. }
  574. print "\n\nComplete!\n";
  575. }
  576. catch (Exception $e) {
  577. $transaction->rollback();
  578. print "\n"; // make sure we start errors on new line
  579. watchdog_exception('trp-fsync', $e);
  580. print "FAILED: Rolling back database changes...\n";
  581. }
  582. }
  583. /**
  584. * This function will delete Drupal nodes for any sync'ed table (e.g.
  585. * feature, organism, analysis, stock, library) if the chado record has been
  586. * deleted or the entry in the chado_[table] table has been removed.
  587. *
  588. * @param $table
  589. * The name of the table that corresonds to the node type we want to clean up.
  590. * @param $job_id
  591. * This should be the job id from the Tripal jobs system. This function
  592. * will update the job status using the provided job ID.
  593. *
  594. * @ingroup tripal_chado_node_api
  595. */
  596. function chado_cleanup_orphaned_nodes($table, $job_id = NULL) {
  597. $count = 0;
  598. // build the SQL statments needed to check if nodes point to valid analyses
  599. $dsql = "SELECT * FROM {node} WHERE type = 'chado_" . $table . "' order by nid";
  600. $nsql = "SELECT * FROM {node} WHERE nid = :nid";
  601. $csql = "SELECT * FROM {chado_" . $table . "} WHERE nid = :nid ";
  602. $clsql= "SELECT * FROM {chado_" . $table . "}";
  603. $lsql = "SELECT * FROM {" . $table . "} where " . $table . "_id = :" . $table . "_id ";
  604. // load into nodes array
  605. print "Getting nodes\n";
  606. $nodes = array();
  607. $res = db_query($dsql);
  608. foreach ($res as $node) {
  609. $nodes[$count] = $node;
  610. $count++;
  611. }
  612. // load the chado_$table into an array
  613. print "Getting chado_$table\n";
  614. $cnodes = array();
  615. $res = db_query($clsql);
  616. foreach ($res as $node) {
  617. $cnodes[$count] = $node;
  618. $count++;
  619. }
  620. $interval = intval($count * 0.01);
  621. if ($interval < 1) {
  622. $interval = 1;
  623. }
  624. // iterate through all of the chado_$table entries and remove those
  625. // that don't have a node or don't have a $table record in chado.libary
  626. print "Verifying all chado_$table Entries\n";
  627. $deleted = 0;
  628. foreach ($cnodes as $nid) {
  629. // update the job status every 1% analyses
  630. if ($job_id and $i % $interval == 0) {
  631. tripal_set_job_progress($job_id, intval(($i / $count) * 100));
  632. }
  633. // see if the node exits, if not remove the entry from the chado_$table table
  634. $results = db_query($nsql, array(':nid' => $nid->nid));
  635. $node = $results->fetchObject();
  636. if (!$node) {
  637. $deleted++;
  638. db_query("DELETE FROM {chado_" . $table . "} WHERE nid = :nid", array(':nid' => $nid->nid));
  639. $message = "chado_$table missing node.... DELETING: $nid->nid";
  640. watchdog('tripal_core', $message, array(), WATCHDOG_WARNING);
  641. }
  642. // see if the record in chado exist, if not remove the entry from the chado_$table
  643. $table_id = $table . "_id";
  644. $results = chado_query($lsql, array(":" . $table . "_id" => $nid->$table_id));
  645. $record = $results->fetchObject();
  646. if (!$record) {
  647. $deleted++;
  648. $sql = "DELETE FROM {chado_" . $table . "} WHERE " . $table . "_id = :" . $table . "_id";
  649. db_query($sql, array(":" . $table . "_id" => $nid->$table_id));
  650. $message = "chado_$table missing $table.... DELETING entry.";
  651. watchdog('tripal_core', $message, array(), WATCHDOG_NOTICE);
  652. }
  653. $i++;
  654. }
  655. print "\t$deleted chado_$table entries missing either a node or chado entry.\n";
  656. // iterate through all of the nodes and delete those that don't
  657. // have a corresponding entry in chado_$table
  658. $deleted = 0;
  659. foreach ($nodes as $node) {
  660. // update the job status every 1% libraries
  661. if ($job_id and $i % $interval == 0) {
  662. tripal_set_job_progress($job_id, intval(($i / $count) * 100));
  663. }
  664. // check to see if the node has a corresponding entry
  665. // in the chado_$table table. If not then delete the node.
  666. $results = db_query($csql, array(":nid" => $node->nid));
  667. $link = $results->fetchObject();
  668. if (!$link) {
  669. if (node_access('delete', $node)) {
  670. $deleted++;
  671. $message = "Node missing in chado_$table table.... DELETING node $node->nid";
  672. watchdog("tripal_core", $message, array(), WATCHDOG_NOTICE);
  673. node_delete($node->nid);
  674. }
  675. else {
  676. $message = "Node missing in chado_$table table.... but cannot delete due to improper permissions (node $node->nid)";
  677. watchdog("tripal_core", $message, array(), WATCHDOG_WARNING);
  678. }
  679. }
  680. $i++;
  681. }
  682. print "\t$deleted nodes did not have corresponding chado_$table entries.\n";
  683. return '';
  684. }
  685. /**
  686. * Create New Node
  687. *
  688. * Note: For your own module, replace hook in the function name with the machine-name of
  689. * your chado node type (ie: chado_feature).
  690. *
  691. * @param $new_node:
  692. * a basic new node object
  693. * @param $record:
  694. * the record object from chado specifying the biological data for this node
  695. *
  696. * @return
  697. * A node object containing all the fields necessary to create a new node during sync
  698. *
  699. * @ingroup tripal_chado_node_api
  700. */
  701. function hook_chado_node_sync_create_new_node($new_node, $record) {
  702. // Add relevant chado details to the new node object
  703. // This really only needs to be the fields from the node used during node creation
  704. // including values used to generate the title, etc.
  705. // All additional chado data will be added via nodetype_load when the node is later used
  706. $new_node->uniquename = $record->uniquename;
  707. return $new_node;
  708. }
  709. /**
  710. * Alter the sync form (optional)
  711. *
  712. * This might be necessary if you need additional filtering options for choosing which
  713. * chado records to sync or even if you just want to further customize the help text
  714. * provided by the form.
  715. *
  716. * Note: For your own module, replace hook in the function name with the machine-name of
  717. * your chado node type (ie: chado_feature).
  718. *
  719. * @ingroup tripal_chado_node_api
  720. */
  721. function hook_chado_node_sync_form($form, &$form_state) {
  722. // Change or add to the form array as needed
  723. // Any changes should be made in accordance with the Drupal Form API
  724. return $form;
  725. }
  726. /**
  727. * Bypass chado node api sync form submit (optional). Allows you to use this function
  728. * as your own submit.
  729. *
  730. * This might be necessary if you want to add additional arguements to the tripal job or
  731. * to call your own sync'ing function if the generic chado_node_sync_records() is not
  732. * sufficient.
  733. *
  734. * Note: For your own module, replace hook in the function name with the machine-name of
  735. * your chado node type (ie: chado_feature).
  736. *
  737. * @ingroup tripal_chado_node_api
  738. */
  739. function hook_chado_node_sync_form_submit ($form, $form_state) {
  740. global $user;
  741. $job_args = array(
  742. $base_table, // the base chado table (ie: feature)
  743. $max_sync, // the maximum number of records to sync or FALSE for sync all that match
  744. $organism_id, // the organism_id to restrict records to or FALSE if not to restrict by organism_id
  745. $types // A string with the cvterm.name of the types to restrict to separated by |||
  746. );
  747. // You should register a tripal job
  748. tripal_add_job(
  749. $title, // the title of the job -be descriptive
  750. $module, // the name of your module
  751. 'chado_node_sync_records', // the chado node api sync function
  752. $job_args, // an array with the arguments to pass to the above function
  753. $user->uid // the user who submitted the job
  754. );
  755. }
  756. /**
  757. * Alter the query that retrieves records to be sync'd (optional)
  758. *
  759. * This might be necessary if you need fields from other chado tables to create your node
  760. * or if your chado node type only supports a subset of a given table (ie: a germplasm node
  761. * type might only support node creation for cerain types of stock records in which case
  762. * you would need to filter the results to only those types).
  763. *
  764. * Note: For your own module, replace hook in the function name with the machine-name of
  765. * your chado node type (ie: chado_feature).
  766. *
  767. * @param $query
  768. * An array containing the following:
  769. * 'select': An array of select clauses
  770. * 'joins: An array of joins (ie: a single join could be
  771. * 'LEFT JOIN {chadotable} alias ON base.id=alias.id')
  772. * 'where_clauses: An array of where clauses which will all be AND'ed
  773. * together. Use :placeholders for values.
  774. * 'where_args: An associative array of arguments to be subbed in to the
  775. * where clause where the
  776. *
  777. * @ingroup tripal_chado_node_api
  778. */
  779. function hook_chado_node_sync_select_query($query) {
  780. // You can add fields to be selected. Be sure to prefix each field with the
  781. // tale name.
  782. $query['select'][] = 'example.myfavfield';
  783. // Provide any join you may need to the joins array. Be sure to wrap the
  784. // table name in curly brackets.
  785. $query['joins'][] = 'LEFT JOIN {exampleprop} PROP ON PROP.example_id=EXAMPLE.example_id';
  786. // The category should be a unique id for a group of items that will be
  787. // concatenated together via an SQL 'OR'. By default the $where_clases
  788. // variable will come with categories of 'id', 'organism' and 'type'.
  789. // you can add your own unique category or alter the contents of the existing
  790. // categories. Be sure to make sure the category doesn't already exist
  791. // in the $query['where_clauses']
  792. $category = 'my_category';
  793. // Provide any aditionall where clauses and their necessary arguments.
  794. // Be sure to prefix the field with the table name. Be sure that the
  795. // placeholder is unique across all categories (perhaps add a unique
  796. // prefix/suffix).
  797. $query['where_clauses'][$category][] = 'example.myfavfield = :favvalue';
  798. $query['where_args'][$category][':favvalue'] = 'awesome-ness';
  799. // Must return the updated query
  800. return $query;
  801. }