tripal_core.chado_nodes.api.inc 49 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344
  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. * Common Functionality for Properties, Dbxrefs and relationships chado node API
  128. */
  129. /**
  130. * Validate the Triggering element from a node form.
  131. *
  132. * We are going to inspect the post to determine what PHP knows is the triggering
  133. * element and if it doesn't agree with Drupal then we are actually going to
  134. * change it in Drupal.
  135. *
  136. * This fixes an obscure bug triggered when a property is added and then
  137. * a relationship removed, Drupal thinks the first property remove button was
  138. * clicked and instead removes a property (not a relationship) and renders the new
  139. * property table in the relationship table page space.
  140. *
  141. * NOTE: Many Drupal issues state that this problem is solved if the #name
  142. * of the button is unique (which it is in our case) but we are still experiencing
  143. * incorrectly determined triggering elements so we need to handle it ourselves.
  144. */
  145. function chado_validate_node_form_triggering_element($form, &$form_state) {
  146. // We are going to inspect the post to determine what PHP knows is the triggering
  147. // element and if it doesn't agree with Drupal then we are actually going to
  148. // change it in Drupal.
  149. if ($_POST['_triggering_element_name'] != $form_state['triggering_element']['#name']) {
  150. $form_state['triggering_element']['#name'] = $_POST['_triggering_element_name'];
  151. }
  152. }
  153. /**
  154. * Validate Adding Subtables entries from the node forms.
  155. * Supported subtables: Properties, Relationships, Additional DBxrefs.
  156. *
  157. * @param array $form
  158. * @param array $form_state
  159. */
  160. function chado_add_node_form_subtables_add_button_validate($form, &$form_state) {
  161. // Based on triggering element call the correct validation function
  162. // ASUMPTION #1: each of the buttons must have property, dbxref or relationship
  163. // as the first part of the #name to uniquely identify the subsection.
  164. if (preg_match('/^([a-z]+).*/', $form_state['triggering_element']['#name'], $matches)) {
  165. $subsection = $matches[1];
  166. switch($subsection) {
  167. case 'properties':
  168. chado_add_node_form_properties_add_button_validate($form, $form_state);
  169. break;
  170. case 'dbxrefs':
  171. chado_add_node_form_dbxrefs_add_button_validate($form, $form_state);
  172. break;
  173. case 'relationships':
  174. chado_add_node_form_relationships_add_button_validate($form, $form_state);
  175. break;
  176. }
  177. }
  178. }
  179. /**
  180. * Add subtable entries to the node forms.
  181. * Supported subtables: Properties, Relationships, Additional DBxrefs.
  182. *
  183. * @param array $form
  184. * @param array $form_state
  185. */
  186. function chado_add_node_form_subtables_add_button_submit($form, &$form_state) {
  187. // Based on triggering element call the correct submit function
  188. // ASUMPTION #1: each of the buttons must have properties, dbxrefs or relationships
  189. // as the first part of the #name to uniquely identify the subsection.
  190. if (preg_match('/^([a-z]+).*/', $form_state['triggering_element']['#name'], $matches)) {
  191. $subsection = $matches[1];
  192. switch($subsection) {
  193. case 'properties':
  194. chado_add_node_form_properties_add_button_submit($form, $form_state);
  195. break;
  196. case 'dbxrefs':
  197. chado_add_node_form_dbxrefs_add_button_submit($form, $form_state);
  198. break;
  199. case 'relationships':
  200. chado_add_node_form_relationships_add_button_submit($form, $form_state);
  201. break;
  202. }
  203. }
  204. // This is needed to ensure the form builder function is called for the node
  205. // form in order for any of these changes to be seen.
  206. $form_state['rebuild'] = TRUE;
  207. }
  208. /**
  209. * Validate Removing Subtables entries from the node forms.
  210. * Supported subtables: Properties, Relationships, Additional DBxrefs.
  211. *
  212. * Since Removing isn't associated with any user input the only thing we
  213. * need to validate is that Drupal has determined the triggering element correctly.
  214. * That said, we will call each subtables associated validate function just incase
  215. * there is some case-specific validation we do not know of or have not anticipated.
  216. *
  217. * @param array $form
  218. * @param array $form_state
  219. */
  220. function chado_add_node_form_subtables_remove_button_validate($form, &$form_state) {
  221. // We need to validate the trigerring element since Drupal has known
  222. // issues determining this correctly when there are multiple buttons
  223. // with the same label.
  224. chado_validate_node_form_triggering_element($form, $form_state);
  225. // Based on triggering element call the correct validation function
  226. // ASUMPTION #1: each of the buttons must have property, dbxref or relationship
  227. // as the first part of the #name to uniquely identify the subsection.
  228. if (preg_match('/^([a-z]+).*/', $form_state['triggering_element']['#name'], $matches)) {
  229. $subsection = $matches[1];
  230. switch($subsection) {
  231. case 'properties':
  232. chado_add_node_form_properties_remove_button_validate($form, $form_state);
  233. break;
  234. case 'dbxrefs':
  235. chado_add_node_form_dbxrefs_remove_button_validate($form, $form_state);
  236. break;
  237. case 'relationships':
  238. chado_add_node_form_relationships_remove_button_validate($form, $form_state);
  239. break;
  240. }
  241. }
  242. }
  243. /**
  244. * Remove subtable entries to the node forms.
  245. * Supported subtables: Properties, Relationships, Additional DBxrefs.
  246. *
  247. * @param array $form
  248. * @param array $form_state
  249. */
  250. function chado_add_node_form_subtables_remove_button_submit($form, &$form_state) {
  251. // Based on triggering element call the correct submit function
  252. // ASUMPTION #1: each of the buttons must have properties, dbxrefs or relationships
  253. // as the first part of the #name to uniquely identify the subsection.
  254. if (preg_match('/^([a-z]+).*/', $form_state['triggering_element']['#name'], $matches)) {
  255. $subsection = $matches[1];
  256. switch($subsection) {
  257. case 'properties':
  258. chado_add_node_form_properties_remove_button_submit($form, $form_state);
  259. break;
  260. case 'dbxrefs':
  261. chado_add_node_form_dbxrefs_remove_button_submit($form, $form_state);
  262. break;
  263. case 'relationships':
  264. chado_add_node_form_relationships_remove_button_submit($form, $form_state);
  265. break;
  266. }
  267. }
  268. // This is needed to ensure the form builder function is called for the node
  269. // form in order for any of these changes to be seen.
  270. $form_state['rebuild'] = TRUE;
  271. }
  272. /**
  273. * Ajax function which returns the section of the form to be re-rendered
  274. * for either the properties, dbxref or relationship sub-sections.
  275. *
  276. * @ingroup tripal_core
  277. */
  278. function chado_add_node_form_subtable_ajax_update($form, &$form_state) {
  279. // We need to validate the trigerring element since Drupal has known
  280. // issues determining this correctly when there are multiple buttons
  281. // with the same label.
  282. chado_validate_node_form_triggering_element($form, $form_state);
  283. // Based on triggering element render the correct part of the form.
  284. // ASUMPTION: each of the buttons must have property, dbxref or relationship
  285. // as the first part of the #name to uniquely identify the subsection.
  286. if (preg_match('/^([a-z]+).*/', $form_state['triggering_element']['#name'], $matches)) {
  287. $subsection = $matches[1];
  288. switch($subsection) {
  289. case 'properties':
  290. return $form['properties']['property_table'];
  291. break;
  292. case 'dbxrefs':
  293. return $form['addtl_dbxrefs']['dbxref_table'];
  294. break;
  295. case 'relationships':
  296. return $form['relationships']['relationship_table'];
  297. break;
  298. }
  299. }
  300. }
  301. /**
  302. * @section
  303. * Sync Form
  304. */
  305. /**
  306. * Generic Sync Form to aid in sync'ing (create drupal nodes linking to chado content)
  307. * any chado node type.
  308. *
  309. * To use this you need to add a call to it from your hook_menu() and
  310. * add some additional information to your hook_node_info(). The Following code gives an
  311. * example of how this might be done:
  312. * @code
  313. function modulename_menu() {
  314. // the machine name of your module
  315. $module_name = 'tripal_example';
  316. // the base specified in hook_node_info
  317. $node_type = 'chado_example';
  318. // This menu item will be a tab on the admin/tripal/chado/tripal_example page
  319. // that is not selected by default
  320. $items['admin/tripal/chado/tripal_example/sync'] = array(
  321. 'title' => ' Sync',
  322. 'description' => 'Sync examples from Chado with Drupal',
  323. 'page callback' => 'drupal_get_form',
  324. 'page arguments' => array('chado_node_sync_form', $module_name, $node_type),
  325. 'access arguments' => array('administer tripal examples'),
  326. 'type' => MENU_LOCAL_TASK,
  327. 'weight' => 0
  328. );
  329. return $items;
  330. }
  331. function modulename_node_info() {
  332. return array(
  333. 'chado_example' => array(
  334. 'name' => t('example'),
  335. 'base' => 'chado_example',
  336. 'description' => t('A Chado example is a collection of material that can be sampled and have experiments performed on it.'),
  337. 'has_title' => TRUE,
  338. 'locked' => TRUE,
  339. // this is what differs from the regular Drupal-documented hook_node_info()
  340. 'chado_node_api' => array(
  341. 'base_table' => 'example', // The name of the chado base table
  342. 'hook_prefix' => 'chado_example', // Usually the name of the node type
  343. 'linking_table' => 'chado_example', // Specifies the linking table used
  344. // to map records to Drupal nodes.
  345. // if 'linking_table' is not specified
  346. // it defaults to the node_type name.
  347. 'record_type_title' => array(
  348. 'singular' => t('Example'), // Singular human-readable title
  349. 'plural' => t('Examples') // Plural human-readable title
  350. ),
  351. 'sync_filters' => array( // filters for syncing
  352. 'type_id' => TRUE, // TRUE if there is an example.type_id field
  353. 'organism_id' => TRUE, // TRUE if there is an example.organism_id field
  354. 'checkboxes' => array('name') // If the 'checkboxes' key is present then the
  355. // value must be an array of column names in
  356. // base table. The values from these columns will
  357. // be retreived, contentated with a space delimeter
  358. // and provided in a list of checkboxes
  359. // for the user to choose which to sync.
  360. ),
  361. )
  362. ),
  363. );
  364. }
  365. * @endcode
  366. *
  367. * For more information on how you can override some of this behaviour while still
  368. * benifiting from as much of the common architecture as possible see the following
  369. * functions: hook_chado_node_sync_create_new_node(), hook_chado_node_sync_form(),
  370. * hook_chado_node_sync_select_query().
  371. *
  372. * @ingroup tripal_chado_node_api
  373. */
  374. function chado_node_sync_form($form, &$form_state) {
  375. $form = array();
  376. if (isset($form_state['build_info']['args'][0])) {
  377. $module = $form_state['build_info']['args'][0];
  378. $node_type = $form_state['build_info']['args'][1];
  379. $node_info = call_user_func($module . '_node_info');
  380. // If a linking table is set in the node_info array then use that,
  381. // otherwise ues the node_type as the linking table.
  382. if (array_key_exists('linking_table', $node_info[$node_type]['chado_node_api'])) {
  383. $linking_table = $node_info[$node_type]['chado_node_api']['linking_table'];
  384. }
  385. else {
  386. $linking_table = 'chado_' . $node_info[$node_type]['chado_node_api']['base_table'];
  387. }
  388. $args = $node_info[$node_type]['chado_node_api'];
  389. $form_state['chado_node_api'] = $args;
  390. }
  391. $form['linking_table'] = array(
  392. '#type' => 'hidden',
  393. '#value' => $linking_table
  394. );
  395. $form['node_type'] = array(
  396. '#type' => 'hidden',
  397. '#value' => $node_type
  398. );
  399. // define the fieldsets
  400. $form['sync'] = array(
  401. '#type' => 'fieldset',
  402. '#title' => 'Sync ' . $args['record_type_title']['plural'],
  403. '#descrpition' => '',
  404. );
  405. $form['sync']['description'] = array(
  406. '#type' => 'item',
  407. '#value' => t("%title_plural of the types listed ".
  408. "below in the %title_singular Types box will be synced (leave blank to sync all types). You may limit the ".
  409. "%title_plural to be synced by a specific organism. Depending on the ".
  410. "number of %title_plural in the chado database this may take a long ".
  411. "time to complete. ",
  412. array(
  413. '%title_singular' => $args['record_type_title']['singular'],
  414. '%title_plural' => $args['record_type_title']['plural']
  415. )),
  416. );
  417. if ($args['sync_filters']['type_id']) {
  418. $form['sync']['type_ids'] = array(
  419. '#title' => t('%title_singular Types',
  420. array(
  421. '%title_singular' => $args['record_type_title']['singular'],
  422. '%title_plural' => $args['record_type_title']['plural']
  423. )),
  424. '#type' => 'textarea',
  425. '#description' => t("Enter the names of the %title_singular types to sync. " .
  426. "Leave blank to sync all %title_plural. Separate each type with a comma ".
  427. "or new line. Pages for these %title_singular ".
  428. "types will be created automatically for %title_plural that exist in the ".
  429. "chado database. The names must match ".
  430. "exactly (spelling and case) with terms in the ontologies",
  431. array(
  432. '%title_singular' => strtolower($args['record_type_title']['singular']),
  433. '%title_plural' => strtolower($args['record_type_title']['plural'])
  434. )),
  435. '#default_value' => (isset($form_state['values']['type_id'])) ? $form_state['values']['type_id'] : '',
  436. );
  437. }
  438. // get the list of organisms
  439. if ($args['sync_filters']['organism_id']) {
  440. $sql = "SELECT * FROM {organism} ORDER BY genus, species";
  441. $results = chado_query($sql);
  442. $organisms[] = '';
  443. foreach ($results as $organism) {
  444. $organisms[$organism->organism_id] = "$organism->genus $organism->species ($organism->common_name)";
  445. }
  446. $form['sync']['organism_id'] = array(
  447. '#title' => t('Organism'),
  448. '#type' => t('select'),
  449. '#description' => t("Choose the organism for which %title_plural types set above will be synced.",
  450. array(
  451. '%title_singular' => $args['record_type_title']['singular'],
  452. '%title_plural' => $args['record_type_title']['plural']
  453. )),
  454. '#options' => $organisms,
  455. '#default_value' => (isset($form_state['values']['organism_id'])) ? $form_state['values']['organism_id'] : 0,
  456. );
  457. }
  458. // get the list of organisms
  459. if (array_key_exists('checkboxes', $args['sync_filters'])) {
  460. // get the base schema
  461. $base_table = $args['base_table'];
  462. $table_info = chado_get_schema($base_table);
  463. // if the base table does not have a primary key or has more than one then
  464. // we can't proceed, otherwise, generate the checkboxes
  465. if (array_key_exists('primary key', $table_info) and count($table_info['primary key']) == 1) {
  466. $pkey = $table_info['primary key'][0];
  467. $columns = $args['sync_filters']['checkboxes'];
  468. $select_cols = '';
  469. foreach ($columns as $column) {
  470. $select_cols .= $base_table . '.' . $column . "|| ' ' ||";
  471. }
  472. // Remove trailing || ' ' ||
  473. $select_cols = substr($select_cols, 0, -9);
  474. $base_table_id = $base_table . '_id';
  475. $select = array($base_table . '.' . $pkey, $select_cols . ' as value');
  476. $joins = array();
  477. $where_clauses = array();
  478. $where_args = array();
  479. // Allow module to update the query.
  480. $hook_query_alter = $node_type . '_chado_node_sync_select_query';
  481. if (function_exists($hook_query_alter)) {
  482. $update = call_user_func($hook_query_alter, array(
  483. 'select' => $select,
  484. 'joins' => $joins,
  485. 'where_clauses' => $where_clauses,
  486. 'where_args' => $where_args,
  487. ));
  488. // Now add in any new changes
  489. if ($update and is_array($update)) {
  490. $select = $update['select'];
  491. $joins = $update['joins'];
  492. $where_clauses = $update['where_clauses'];
  493. $where_args = $update['where_args'];
  494. }
  495. }
  496. // Build Query, we do a left join on the chado_xxxx table in the Drupal schema
  497. // so that if no criteria are specified we only get those items that have not
  498. // yet been synced.
  499. // @todo: re-write the query to support external chado databases.
  500. $query = "SELECT " . implode(', ', $select) . ' ' .
  501. 'FROM {' . $base_table . '} ' . $base_table . ' ' . implode(' ', $joins) . ' '.
  502. " LEFT JOIN [$linking_table] CT ON CT.$base_table_id = $base_table.$base_table_id " .
  503. "WHERE CT.$base_table_id IS NULL";
  504. // extend the where clause if needed
  505. $where = '';
  506. $sql_args = array();
  507. foreach ($where_clauses as $category => $items) {
  508. $where .= ' AND (';
  509. foreach ($items as $item) {
  510. $where .= $item . ' OR ';
  511. }
  512. $where = substr($where, 0, -4); // remove the trailing 'OR'
  513. $where .= ') ';
  514. $sql_args = array_merge($sql_args, $where_args[$category]);
  515. }
  516. if ($where) {
  517. $query .= $where;
  518. }
  519. $query .= " ORDER BY $base_table." . implode(", $base_table.", $columns);
  520. $results = chado_query($query, $sql_args);
  521. $values = array();
  522. foreach ($results as $result) {
  523. $values[$result->$pkey] = $result->value;
  524. }
  525. if (count($values) > 0) {
  526. $form['sync']['ids'] = array(
  527. '#title' => 'Avaliable ' . $args['record_type_title']['plural'],
  528. '#type' => 'checkboxes',
  529. '#options' => $values,
  530. '#default_value' => (isset($form_state['values']['ids'])) ? $form_state['values']['ids'] : array(),
  531. '#suffix' => '</div><br>',
  532. '#prefix' => t("The following %title_plural have not been synced. Check those to be synced or leave all unchecked to sync them all.",
  533. array(
  534. '%title_singular' => strtolower($args['record_type_title']['singular']),
  535. '%title_plural' => strtolower($args['record_type_title']['plural'])
  536. )) . '<div style="height: 200px; overflow: scroll">',
  537. );
  538. }
  539. else {
  540. $form['sync']['no_ids'] = array(
  541. '#markup' => "<p>There are no " . strtolower($args['record_type_title']['plural']) . " to sync.</p>",
  542. );
  543. }
  544. }
  545. }
  546. // if we provide a list of checkboxes we shouldn't need a max_sync
  547. else {
  548. $form['sync']['max_sync'] = array(
  549. '#type' => 'textfield',
  550. '#title' => t('Maximum number of records to Sync'),
  551. '#description' => t('Leave this field empty to sync all records, regardless of number'),
  552. '#default_value' => (isset($form_state['values']['max_sync'])) ? $form_state['values']['max_sync'] : '',
  553. );
  554. }
  555. $form['sync']['button'] = array(
  556. '#type' => 'submit',
  557. '#value' => t('Sync ' . $args['record_type_title']['plural']),
  558. '#weight' => 3,
  559. );
  560. $form['cleanup'] = array(
  561. '#type' => 'fieldset',
  562. '#title' => t('Clean Up')
  563. );
  564. $form['cleanup']['description'] = array(
  565. '#markup' => t("<p>With Drupal and chado residing in different databases " .
  566. "it is possible that nodes in Drupal and " . strtolower($args['record_type_title']['plural']) . " in Chado become " .
  567. "\"orphaned\". This can occur if a node in Drupal is " .
  568. "deleted but the corresponding chado records is not and/or vice " .
  569. "versa. Click the button below to resolve these discrepancies.</p>"),
  570. '#weight' => -10,
  571. );
  572. $form['cleanup']['cleanup_batch_size'] = array(
  573. '#type' => 'textfield',
  574. '#title' => t('Batch Size'),
  575. '#description' => t('The number of records to analyze together in a batch. If you are having memory issues you might want to decrease this number.'),
  576. '#default_value' => variable_get('chado_node_api_cleanup_batch_size', 25000),
  577. );
  578. $form['cleanup']['button'] = array(
  579. '#type' => 'submit',
  580. '#value' => 'Clean up orphaned ' . strtolower($args['record_type_title']['plural']),
  581. '#weight' => 2,
  582. );
  583. // Allow each module to alter this form as needed
  584. $hook_form_alter = $args['hook_prefix'] . '_chado_node_sync_form';
  585. if (function_exists($hook_form_alter)) {
  586. $form = call_user_func($hook_form_alter, $form, $form_state);
  587. }
  588. return $form;
  589. }
  590. /**
  591. * Generic Sync Form Validate
  592. *
  593. * @ingroup tripal_core
  594. */
  595. function chado_node_sync_form_validate($form, &$form_state) {
  596. if (empty($form_state['values']['cleanup_batch_size'])) {
  597. $form_state['values']['cleanup_batch_size'] = 25000;
  598. drupal_set_message('You entered a Batch Size of 0 for Cleaning-up orphaned nodes. Since this is not valid, we reset it to the default of 25,000.', 'warning');
  599. }
  600. elseif (!is_numeric($form_state['values']['cleanup_batch_size'])) {
  601. form_set_error('cleanup_batch_size', 'The batch size must be a postitive whole number.');
  602. }
  603. else {
  604. // Round the value just to make sure.
  605. $form_state['values']['cleanup_batch_size'] = abs(round($form_state['values']['cleanup_batch_size']));
  606. }
  607. }
  608. /**
  609. * Generic Sync Form Submit
  610. *
  611. * @ingroup tripal_core
  612. */
  613. function chado_node_sync_form_submit($form, $form_state) {
  614. global $user;
  615. if (preg_match('/^Sync/', $form_state['values']['op'])) {
  616. // get arguments
  617. $args = $form_state['chado_node_api'];
  618. $module = $form_state['chado_node_api']['hook_prefix'];
  619. $base_table = $form_state['chado_node_api']['base_table'];
  620. $linking_table = $form_state['values']['linking_table'];
  621. $node_type = $form_state['values']['node_type'];
  622. // Allow each module to hijack the submit if needed
  623. $hook_form_hijack_submit = $args['hook_prefix'] . '_chado_node_sync_form_submit';
  624. if (function_exists($hook_form_hijack_submit)) {
  625. return call_user_func($hook_form_hijack_submit, $form, $form_state);
  626. }
  627. // Get the types separated into a consistent string
  628. $types = array();
  629. if (isset($form_state['values']['type_ids'])) {
  630. // seperate by new line or comma.
  631. $temp_types = preg_split("/[,\n\r]+/", $form_state['values']['type_ids']);
  632. // remove any extra spacing around the types
  633. for($i = 0; $i < count($temp_types); $i++) {
  634. // skip empty types
  635. if (trim($temp_types[$i]) == '') {
  636. continue;
  637. }
  638. $types[$i] = trim($temp_types[$i]);
  639. }
  640. }
  641. // Get the ids to be synced
  642. $ids = array();
  643. if (array_key_exists('ids', $form_state['values'])){
  644. foreach ($form_state['values']['ids'] as $id => $selected) {
  645. if ($selected) {
  646. $ids[] = $id;
  647. }
  648. }
  649. }
  650. // get the organism to be synced
  651. $organism_id = FALSE;
  652. if (array_key_exists('organism_id', $form_state['values'])) {
  653. $organism_id = $form_state['values']['organism_id'];
  654. }
  655. // Job Arguments
  656. $job_args = array(
  657. 'base_table' => $base_table,
  658. 'max_sync' => (!empty($form_state['values']['max_sync'])) ? $form_state['values']['max_sync'] : FALSE,
  659. 'organism_id' => $organism_id,
  660. 'types' => $types,
  661. 'ids' => $ids,
  662. 'linking_table' => $linking_table,
  663. 'node_type' => $node_type
  664. );
  665. $title = "Sync " . $args['record_type_title']['plural'];
  666. tripal_add_job($title, $module, 'chado_node_sync_records', $job_args, $user->uid);
  667. }
  668. if (preg_match('/^Clean up orphaned/', $form_state['values']['op'])) {
  669. $module = $form_state['chado_node_api']['hook_prefix'];
  670. $base_table = $form_state['chado_node_api']['base_table'];
  671. $linking_table = $form_state['values']['linking_table'];
  672. $node_type = $form_state['values']['node_type'];
  673. $job_args = array($base_table, $form_state['values']['cleanup_batch_size'], $linking_table, $node_type);
  674. variable_set('chado_node_api_cleanup_batch_size', $form_state['values']['cleanup_batch_size']);
  675. tripal_add_job($form_state['values']['op'], $module, 'chado_cleanup_orphaned_nodes', $job_args, $user->uid);
  676. }
  677. }
  678. /**
  679. * Generic function for syncing records in Chado with Drupal nodes.
  680. *
  681. * @param $base_table
  682. * The name of the Chado table containing the record that should be synced
  683. * @param $max_sync
  684. * Optional: A numeric value to indicate the maximum number of records to sync.
  685. * @param $organism_id
  686. * Optional: Limit the list of records to be synced to only those that
  687. * are associated with this organism_id. If the record is not assocaited
  688. * with an organism then this field is not needed.
  689. * @param $types
  690. * Optional: Limit the list of records to be synced to only those that
  691. * match the types listed in this array.
  692. * @param $ids
  693. * Optional: Limit the list of records to bye synced to only those whose
  694. * primary key value matches the ID provided in this array.
  695. * @param $linking_table
  696. * Optional: Tripal maintains "linking" tables in the Drupal schema
  697. * to link Drupal nodes with Chado records. By default these tables
  698. * are named as 'chado_' . $base_table. But if for some reason the
  699. * linking table is not named in this way then it can be provided by this
  700. * argument.
  701. * @param $node_type
  702. * Optional: Tripal maintains "linking" tables in the Drupal schema
  703. * to link Drupal nodes with Chado records. By default, Tripal expects that
  704. * the node_type and linking table are named the same. However, if this
  705. * is not the case, you can provide the node type name here.
  706. * @param $job_id
  707. * Optional. Used by the Trpial Jobs system when running this function
  708. * as a job. It is not needed othewise.
  709. *
  710. * @ingroup tripal_chado_node_api
  711. */
  712. function chado_node_sync_records($base_table, $max_sync = FALSE,
  713. $organism_id = FALSE, $types = array(), $ids = array(),
  714. $linking_table = FALSE, $node_type = FALSE, $job_id = NULL) {
  715. global $user;
  716. $base_table_id = $base_table . '_id';
  717. if (!$linking_table) {
  718. $linking_table = 'chado_' . $base_table;
  719. }
  720. if (!$node_type) {
  721. $node_type = 'chado_' . $base_table;
  722. }
  723. print "\nSync'ing $base_table records. ";
  724. // START BUILDING QUERY TO GET ALL RECORD FROM BASE TABLE THAT MATCH
  725. $select = array("$base_table.*");
  726. $joins = array();
  727. $where_clauses = array();
  728. $where_args = array();
  729. // If types are supplied then handle them
  730. $restrictions = '';
  731. if (count($types) > 0) {
  732. $restrictions .= " Type(s): " . implode(', ',$types) . "\n";
  733. $select[] = 'cvterm.name as cvtname';
  734. $joins[] = "LEFT JOIN {cvterm} cvterm ON $base_table.type_id = cvterm.cvterm_id";
  735. foreach ($types as $type) {
  736. $sanitized_type = str_replace(' ','_',$type);
  737. $where_clauses['type'][] = "cvterm.name = :type_name_$sanitized_type";
  738. $where_args['type'][":type_name_$sanitized_type"] = $type;
  739. }
  740. }
  741. // if IDs have been supplied
  742. if ($ids) {
  743. $restrictions .= " Specific Records: " . count($ids) . " recored(s) specified.\n";
  744. foreach ($ids as $id) {
  745. $where_clauses['id'][] = "$base_table.$base_table_id = :id_$id";
  746. $where_args['id'][":id_$id"] = $id;
  747. }
  748. }
  749. // If Organism is supplied
  750. if ($organism_id) {
  751. $organism = chado_select_record('organism', array('*'), array('organism_id' => $organism_id));
  752. $restrictions .= " Organism: " . $organism[0]->genus . " " . $organism[0]->species . "\n";
  753. $select[] = 'organism.*';
  754. $joins[] = "LEFT JOIN {organism} organism ON organism.organism_id = $base_table.organism_id";
  755. $where_clauses['organism'][] = 'organism.organism_id = :organism_id';
  756. $where_args['organism'][':organism_id'] = $organism_id;
  757. }
  758. // Allow module to add to query
  759. $hook_query_alter = $node_type . '_chado_node_sync_select_query';
  760. if (function_exists($hook_query_alter)) {
  761. $update = call_user_func($hook_query_alter, array(
  762. 'select' => $select,
  763. 'joins' => $joins,
  764. 'where_clauses' => $where_clauses,
  765. 'where_args' => $where_args,
  766. ));
  767. // Now add in any new changes
  768. if ($update and is_array($update)) {
  769. $select = $update['select'];
  770. $joins = $update['joins'];
  771. $where_clauses = $update['where_clauses'];
  772. $where_args = $update['where_args'];
  773. }
  774. }
  775. // Build Query, we do a left join on the chado_xxxx table in the Drupal schema
  776. // so that if no criteria are specified we only get those items that have not
  777. // yet been synced.
  778. // @todo: re-write to support external chado databases.
  779. $query = "
  780. SELECT " . implode(', ', $select) . ' ' .
  781. 'FROM {' . $base_table . '} ' . $base_table . ' ' . implode(' ', $joins) . ' '.
  782. " LEFT JOIN [$linking_table] CT ON CT.$base_table_id = $base_table.$base_table_id " .
  783. "WHERE CT.$base_table_id IS NULL ";
  784. // extend the where clause if needed
  785. $where = '';
  786. $sql_args = array();
  787. foreach ($where_clauses as $category => $items) {
  788. $where .= ' AND (';
  789. foreach ($items as $item) {
  790. $where .= $item . ' OR ';
  791. }
  792. $where = substr($where, 0, -4); // remove the trailing 'OR'
  793. $where .= ') ';
  794. $sql_args = array_merge($sql_args, $where_args[$category]);
  795. }
  796. if ($where) {
  797. $query .= $where;
  798. }
  799. $query .- " ORDER BY " . $base_table_id;
  800. // If Maximum number to Sync is supplied
  801. if ($max_sync) {
  802. $query .= " LIMIT $max_sync";
  803. $restrictions .= " Limited to $max_sync records.\n";
  804. }
  805. if ($restrictions) {
  806. print "Records matching these criteria will be synced: \n$restrictions";
  807. }
  808. else {
  809. print "\n";
  810. }
  811. // execute the query
  812. $results = chado_query($query, $sql_args);
  813. // Iterate through records that need to be synced
  814. $count = $results->rowCount();
  815. $interval = intval($count * 0.01);
  816. if ($interval < 1) {
  817. $interval = 1;
  818. }
  819. print "\n$count $base_table records found.\n";
  820. $i = 0;
  821. $transaction = db_transaction();
  822. print "\nNOTE: Syncing is performed using a database transaction. \n" .
  823. "If the sync fails or is terminated prematurely then the entire set of \n" .
  824. "synced items is rolled back and will not be found in the database\n\n";
  825. try {
  826. $percent = 0;
  827. foreach ($results as $record) {
  828. // Update the job status every 1% features.
  829. if ($job_id and $i % $interval == 0) {
  830. $percent = sprintf("%.2f", (($i + 1) / $count) * 100);
  831. print "Syncing $base_table " . ($i + 1) . " of $count (" . $percent . "%). Memory: " . number_format(memory_get_usage()) . " bytes.\r";
  832. tripal_set_job_progress($job_id, intval(($i/$count)*100));
  833. }
  834. // Check if the record is already in the chado linking table
  835. // (ie: check to see if it is already linked to a node).
  836. $result = db_select($linking_table, 'lnk')
  837. ->fields('lnk',array('nid'))
  838. ->condition($base_table_id, $record->{$base_table_id}, '=')
  839. ->execute()
  840. ->fetchObject();
  841. if (empty($result)) {
  842. // Create generic new node.
  843. $new_node = new stdClass();
  844. $new_node->type = $node_type;
  845. $new_node->uid = $user->uid;
  846. $new_node->{$base_table_id} = $record->{$base_table_id};
  847. $new_node->$base_table = $record;
  848. $new_node->language = LANGUAGE_NONE;
  849. // TODO: should we get rid of this hook and use hook_node_presave() instead?
  850. // allow base module to set additional fields as needed
  851. $hook_create_new_node = $node_type . '_chado_node_sync_create_new_node';
  852. if (function_exists($hook_create_new_node)) {
  853. $new_node = call_user_func($hook_create_new_node, $new_node, $record);
  854. }
  855. // Validate and Save New Node
  856. $form = array();
  857. $form_state = array();
  858. node_validate($new_node, $form, $form_state);
  859. if (!form_get_errors()) {
  860. $node = node_submit($new_node);
  861. // If there are memory leaks on the node_save it is probably
  862. // caused by the hook_node_insert() function.
  863. node_save($node);
  864. }
  865. else {
  866. throw new Exception(t("Failed to insert $base_table: %title", array('%title' => $new_node->title)));
  867. }
  868. }
  869. $i++;
  870. }
  871. print "\n\nComplete!\n";
  872. }
  873. catch (Exception $e) {
  874. $transaction->rollback();
  875. print "\n"; // make sure we start errors on new line
  876. watchdog_exception('trp-fsync', $e);
  877. print "FAILED: Rolling back database changes...\n";
  878. }
  879. }
  880. /**
  881. * This function is a wrapper for the chado_cleanup_orphaned_nodes function.
  882. * It breaks up the work of chado_cleanup_orphaned_nodes into smaller pieces
  883. * that are more managable for servers that may have low php memory settings.
  884. *
  885. * @param $table
  886. * The name of the table that corresonds to the node type we want to clean up.
  887. * @param $nentries
  888. * Optional. The number of entries to parse at one time (ie: the batch size).
  889. * Set to zero if no limit is needed.
  890. * @param $linking_table
  891. * Optional. The name of the linking table that maps Drupal nodes to Chado
  892. * records. This is only required if the linking table name is not of the
  893. * form: chado_[table] where [table] is the value provided to the $table
  894. * argument.
  895. * @param $node_type
  896. * Optional. The name of the node type for the records. This is only
  897. * required if the node type is not of the form: chado_[table] where
  898. * [table] is the value provided to the $table.
  899. * @param $job_id
  900. * Optional. This should be the job id from the Tripal jobs system. Typically,
  901. * only the Tripal jobs system will use the argument.
  902. *
  903. * @ingroup tripal_chado_node_api
  904. */
  905. function chado_cleanup_orphaned_nodes($table, $nentries = 25000, $linking_table = NULL, $node_type = NULL, $job_id = NULL) {
  906. // The max number of records either as nodes or linked records.
  907. $count = 0;
  908. // Will hold the number of nodes of this type.
  909. $ncount = 0;
  910. // Will hold the number of linked records.
  911. $clcount = 0;
  912. if (!$node_type) {
  913. $node_type = 'chado_' . $table;
  914. }
  915. if (!$linking_table) {
  916. $linking_table = 'chado_' . $table;
  917. }
  918. // Find the number nodes of type chado_$table and find the number of entries
  919. // in chado_$table; keep the larger of the two numbers.
  920. $dsql = "SELECT COUNT(*) FROM {node} WHERE type = :node_type";
  921. $ndat = db_query($dsql, array(':node_type' => $node_type));
  922. $temp = $ndat->fetchObject();
  923. $ncount = $temp->count;
  924. $clsql= "SELECT COUNT(*) FROM {" . $linking_table . "}";
  925. $cdat = db_query($clsql);
  926. $clcount = $cdat->fetchObject();
  927. if ($ncount < $clcount) {
  928. $count = $clcount;
  929. }
  930. else {
  931. $count = $ncount;
  932. }
  933. $transaction = db_transaction();
  934. print "\nNOTE: This operation is performed using a database transaction. \n" .
  935. "If it fails or is terminated prematurely then the entire set of \n" .
  936. "changes is rolled back and will not be found in the database\n\n";
  937. try {
  938. $m = ceil($count / $nentries);
  939. for ($i = 0; $i < $m; $i++) {
  940. $offset = ($nentries * $i);
  941. chado_cleanup_orphaned_nodes_part($table, $job_id, $nentries, $offset, $linking_table, $node_type);
  942. }
  943. }
  944. catch (Exception $e) {
  945. $transaction->rollback();
  946. print "\n"; // make sure we start errors on new line
  947. watchdog_exception('trp-fsync', $e);
  948. print "FAILED: Rolling back database changes...\n";
  949. }
  950. return '';
  951. }
  952. /**
  953. * This function will delete Drupal nodes for any sync'ed table (e.g.
  954. * feature, organism, analysis, stock, library) if the chado record has been
  955. * deleted or the entry in the chado_[table] table has been removed.
  956. *
  957. * @param $table
  958. * The name of the table that corresonds to the node type we want to clean up.
  959. * @param $job_id
  960. * This should be the job id from the Tripal jobs system. This function
  961. * will update the job status using the provided job ID.
  962. *
  963. * @ingroup tripal_chado_node_api
  964. */
  965. function chado_cleanup_orphaned_nodes_part($table, $job_id = NULL, $nentries,
  966. $offset, $linking_table, $node_type) {
  967. $count = 0;
  968. // Retrieve all of the entries in the linker table for a given node type
  969. // and place into an array.
  970. print "Verifying $linking_table records...\n";
  971. $cnodes = array();
  972. $clsql= "
  973. SELECT *
  974. FROM {" . $linking_table . "} LT
  975. INNER JOIN {node} N ON N.nid = LT.nid
  976. WHERE N.type = :node_type
  977. ORDER BY LT.nid LIMIT $nentries OFFSET $offset";
  978. $res = db_query($clsql, array(':node_type' => $node_type));
  979. foreach ($res as $node) {
  980. $cnodes[$count] = $node;
  981. $count++;
  982. }
  983. // Iterate through all of the $linking_table entries and remove those
  984. // that don't have a node or don't have a $table record.
  985. $deleted = 0;
  986. if ($count > 0) {
  987. $i = 0;
  988. $interval = intval($count * 0.01);
  989. if ($interval < 1) {
  990. $interval = 1;
  991. }
  992. foreach ($cnodes as $nid) {
  993. // Update the job status every 1% analyses
  994. if ($job_id and $i % $interval == 0) {
  995. $percent = sprintf("%.2f", ($i / $count) * 100);
  996. tripal_set_job_progress($job_id, intval($percent));
  997. print "Percent complete: $percent%. Memory: " . number_format(memory_get_usage()) . " bytes.\r";
  998. }
  999. // See if the node exits, if not remove the entry from linking table table.
  1000. $nsql = "SELECT * FROM {node} WHERE nid = :nid";
  1001. $results = db_query($nsql, array(':nid' => $nid->nid));
  1002. $node = $results->fetchObject();
  1003. if (!$node) {
  1004. $deleted++;
  1005. db_query("DELETE FROM {" . $linking_table . "} WHERE nid = :nid", array(':nid' => $nid->nid));
  1006. //print "$linking_table missing node.... DELETING: $nid->nid\n";
  1007. }
  1008. // Does record in chado exists, if not remove entry from $linking_table.
  1009. $table_id = $table . "_id";
  1010. $lsql = "SELECT * FROM {" . $table . "} where " . $table . "_id = :" . $table . "_id";
  1011. $results = chado_query($lsql, array(":" . $table . "_id" => $nid->$table_id));
  1012. $record = $results->fetchObject();
  1013. if (!$record) {
  1014. $deleted++;
  1015. $sql = "DELETE FROM {" . $linking_table . "} WHERE " . $table . "_id = :" . $table . "_id";
  1016. db_query($sql, array(":" . $table . "_id" => $nid->$table_id));
  1017. //print "$linking_table missing $table.... DELETING entry.\n";
  1018. }
  1019. $i++;
  1020. }
  1021. $percent = sprintf("%.2f", ($i / $count) * 100);
  1022. tripal_set_job_progress($job_id, intval($percent));
  1023. print "Percent complete: $percent%. Memory: " . number_format(memory_get_usage()) . " bytes.\r";
  1024. }
  1025. print "\nDeleted $deleted record(s) from $linking_table missing either a node or chado entry.\n";
  1026. // Build the SQL statements needed to check if nodes point to valid record.
  1027. print "Verifying nodes...\n";
  1028. $dsql = "
  1029. SELECT *
  1030. FROM {node}
  1031. WHERE type = :node_type
  1032. ORDER BY nid
  1033. LIMIT $nentries OFFSET $offset
  1034. ";
  1035. $dsql_args = array(':node_type' => $node_type);
  1036. $nodes = array();
  1037. $res = db_query($dsql, $dsql_args);
  1038. $count = 0;
  1039. foreach ($res as $node) {
  1040. $nodes[$count] = $node;
  1041. $count++;
  1042. }
  1043. // Iterate through all of the nodes and delete those that don't
  1044. // have a corresponding entry in the linking table.
  1045. $deleted = 0;
  1046. if ($count > 0) {
  1047. $i = 0;
  1048. $interval = intval($count * 0.01);
  1049. if ($interval < 1) {
  1050. $interval = 1;
  1051. }
  1052. foreach ($nodes as $node) {
  1053. // update the job status every 1%
  1054. if ($job_id and $i % $interval == 0) {
  1055. $percent = sprintf("%.2f", ($i / $count) * 100);
  1056. tripal_set_job_progress($job_id, intval($percent));
  1057. print "Percent complete: $percent%. Memory: " . number_format(memory_get_usage()) . " bytes.\r";
  1058. }
  1059. // check to see if the node has a corresponding entry
  1060. // in the $linking_table table. If not then delete the node.
  1061. $csql = "SELECT * FROM {" . $linking_table . "} WHERE nid = :nid ";
  1062. $results = db_query($csql, array(':nid' => $node->nid));
  1063. $link = $results->fetchObject();
  1064. if (!$link) {
  1065. // Checking node_access creates a memory leak. Commenting out for now
  1066. // assuming that this code can only be run by a site administrator
  1067. // anyway.
  1068. // if (node_access('delete', $node)) {
  1069. $deleted++;
  1070. node_delete($node->nid);
  1071. // }
  1072. // else {
  1073. // print "\nNode missing in $linking_table table.... but cannot delete due to improper permissions (node $node->nid)\n";
  1074. // }
  1075. }
  1076. $i++;
  1077. }
  1078. $percent = sprintf("%.2f", ($i / $count) * 100);
  1079. tripal_set_job_progress($job_id, intval($percent));
  1080. print "Percent complete: $percent%. Memory: " . number_format(memory_get_usage()) . " bytes.\r";
  1081. print "\nDeleted $deleted node(s) that did not have corresponding $linking_table entries.\n";
  1082. }
  1083. return '';
  1084. }
  1085. /**
  1086. * Create New Node
  1087. *
  1088. * Note: For your own module, replace hook in the function name with the
  1089. * machine-name of your chado node type (ie: chado_feature).
  1090. *
  1091. * @param $new_node:
  1092. * a basic new node object
  1093. * @param $record:
  1094. * the record object from chado specifying the biological data for this node
  1095. *
  1096. * @return
  1097. * A node object containing all the fields necessary to create a new node
  1098. * during sync
  1099. *
  1100. * @ingroup tripal_chado_node_api
  1101. */
  1102. function hook_chado_node_sync_create_new_node($new_node, $record) {
  1103. // Add relevant chado details to the new node object. This really only
  1104. // needs to be the fields from the node used during node creation
  1105. // including values used to generate the title, etc. All additional chado
  1106. // data will be added via nodetype_load when the node is later used
  1107. $new_node->uniquename = $record->uniquename;
  1108. return $new_node;
  1109. }
  1110. /**
  1111. * Alter the Chado node sync form.
  1112. *
  1113. * This might be necessary if you need additional filtering options for
  1114. * choosing which chado records to sync or even if you just want to further
  1115. * customize the help text provided by the form.
  1116. *
  1117. * Note: For your own module, replace hook in the function name with the
  1118. * machine-name of your chado node type (ie: chado_feature).
  1119. *
  1120. * @ingroup tripal_chado_node_api
  1121. */
  1122. function hook_chado_node_sync_form($form, &$form_state) {
  1123. // Change or add to the form array as needed.
  1124. // Any changes should be made in accordance with the Drupal Form API.
  1125. return $form;
  1126. }
  1127. /**
  1128. * Bypass chado node api sync form submit.
  1129. *
  1130. * Allows you to use this function as your own submit.
  1131. *
  1132. * This might be necessary if you want to add additional arguements to the
  1133. * tripal job or to call your own sync'ing function if the generic
  1134. * chado_node_sync_records() is not sufficient.
  1135. *
  1136. * Note: For your own module, replace hook in the function name with the
  1137. * machine-name of your chado node type (ie: chado_feature).
  1138. *
  1139. * @ingroup tripal_chado_node_api
  1140. */
  1141. function hook_chado_node_sync_form_submit ($form, $form_state) {
  1142. global $user;
  1143. $job_args = array(
  1144. // The base chado table (ie: feature).
  1145. $base_table,
  1146. // The maximum number of records to sync or FALSE for sync all that match.
  1147. $max_sync,
  1148. // The organism_id to restrict records to or FALSE if not to restrict by organism_id.
  1149. $organism_id,
  1150. // A string with the cvterm.name of the types to restrict to separated by |||
  1151. $types
  1152. );
  1153. // You should register a tripal job
  1154. tripal_add_job(
  1155. // The title of the job -be descriptive.
  1156. $title,
  1157. // The name of your module.
  1158. $module,
  1159. // The chado node api sync function.
  1160. 'chado_node_sync_records',
  1161. // An array with the arguments to pass to the above function.
  1162. $job_args,
  1163. // The user who submitted the job.
  1164. $user->uid
  1165. );
  1166. }
  1167. /**
  1168. * Alter the query that retrieves records to be sync'd (optional)
  1169. *
  1170. * This might be necessary if you need fields from other chado tables to
  1171. * create your node or if your chado node type only supports a subset of a
  1172. * given table (ie: a germplasm node type might only support node creation for
  1173. * cerain types of stock records in which case you would need to filter the
  1174. * results to only those types).
  1175. *
  1176. * Note: For your own module, replace hook in the function name with the
  1177. * machine-name of your chado node type (ie: chado_feature).
  1178. *
  1179. * @param $query
  1180. * An array containing the following:
  1181. * 'select': An array of select clauses
  1182. * 'joins: An array of joins (ie: a single join could be
  1183. * 'LEFT JOIN {chadotable} alias ON base.id=alias.id')
  1184. * 'where_clauses: An array of where clauses which will all be AND'ed
  1185. * together. Use :placeholders for values.
  1186. * 'where_args: An associative array of arguments to be subbed in to the
  1187. * where clause where the
  1188. *
  1189. * @ingroup tripal_chado_node_api
  1190. */
  1191. function hook_chado_node_sync_select_query($query) {
  1192. // You can add fields to be selected. Be sure to prefix each field with the
  1193. // tale name.
  1194. $query['select'][] = 'example.myfavfield';
  1195. // Provide any join you may need to the joins array. Be sure to wrap the
  1196. // table name in curly brackets.
  1197. $query['joins'][] = 'LEFT JOIN {exampleprop} PROP ON PROP.example_id=EXAMPLE.example_id';
  1198. // The category should be a unique id for a group of items that will be
  1199. // concatenated together via an SQL 'OR'. By default the $where_clases
  1200. // variable will come with categories of 'id', 'organism' and 'type'.
  1201. // you can add your own unique category or alter the contents of the existing
  1202. // categories. Be sure to make sure the category doesn't already exist
  1203. // in the $query['where_clauses']
  1204. $category = 'my_category';
  1205. // Provide any aditionall where clauses and their necessary arguments.
  1206. // Be sure to prefix the field with the table name. Be sure that the
  1207. // placeholder is unique across all categories (perhaps add a unique
  1208. // prefix/suffix).
  1209. $query['where_clauses'][$category][] = 'example.myfavfield = :favvalue';
  1210. $query['where_args'][$category][':favvalue'] = 'awesome-ness';
  1211. // Must return the updated query
  1212. return $query;
  1213. }