tripal_chado.field_storage.inc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. <?php
  2. /**
  3. * Implements hook_field_storage_info().
  4. */
  5. function tripal_chado_field_storage_info() {
  6. return array(
  7. 'field_chado_storage' => array(
  8. 'label' => t('Chado'),
  9. 'description' => t('Stores fields in the local Chado database.'),
  10. 'settings' => array(),
  11. // The logo_url key is supported by Tripal. It's not a Drupal key. It's
  12. // used for adding a logo or picture for the data store to help make it
  13. // more easily recognized on the field_ui_field_overview_form. Ideally
  14. // the URL should point to a relative path on the local Drupal site.
  15. 'logo_url' => url(drupal_get_path('module', 'tripal') . '/theme/images/250px-ChadoLogo.png'),
  16. ),
  17. );
  18. }
  19. /**
  20. * Implements hook_field_storage_write().
  21. */
  22. function tripal_chado_field_storage_write($entity_type, $entity, $op, $fields) {
  23. // Get the bundle and the term for this entity.
  24. $bundle = tripal_load_bundle_entity(array('name' => $entity->bundle));
  25. $term = entity_load('TripalTerm', array('id' => $entity->term_id));
  26. $term = reset($term);
  27. // Get the base table, type field and record_id from the entity.
  28. $base_table = $entity->chado_table;
  29. $type_field = $entity->chado_column;
  30. $record = $entity->chado_record;
  31. $record_id = $entity->chado_record_id;
  32. // Convert the fields into a key/value list of fields and their values.
  33. $field_vals = tripal_chado_field_storage_unnest_fields($fields, $entity_type, $entity);
  34. dpm($field_vals);
  35. // Recursively write fields for the base table.
  36. $record_id = tripal_chado_field_storage_write_recursive($entity_type, $entity, $term,
  37. $op, $field_vals, $base_table, $base_table, $type_field, $record_id);
  38. // If this is an insert then add the chado_entity record.
  39. if ($op == FIELD_STORAGE_INSERT) {
  40. // Add a record to the chado_entity table so that the data for the
  41. // fields can be pulled from Chado when loaded the next time.
  42. $record = array(
  43. 'entity_id' => $entity->id,
  44. 'record_id' => $record_id,
  45. 'data_table' => $base_table,
  46. 'type_table' => $base_table,
  47. 'field' => $type_field,
  48. );
  49. $success = drupal_write_record('chado_entity', $record);
  50. if (!$success) {
  51. drupal_set_message('Unable to insert new Chado entity.', 'error');
  52. }
  53. }
  54. // Now that we have handled the base table, we need to handle fields that
  55. // are not part of the base table.
  56. foreach ($fields as $field_id) {
  57. // Get the field using the id.
  58. $field = field_info_field_by_id($field_id);
  59. $field_name = $field['field_name'];
  60. // If the field has a chado_table setting then we can try to write.
  61. if (array_key_exists('settings', $field) and array_key_exists('chado_table', $field['settings'])) {
  62. // Skip fields that use the base table, as we've already handled those.
  63. if ($field['settings']['chado_table'] != $base_table){
  64. $field_table = $field['settings']['chado_table'];
  65. // Iterate through each record.
  66. if (array_key_exists($field_name, $field_vals)) {
  67. foreach ($field_vals[$field_name] as $delta => $fvals) {
  68. tripal_chado_field_storage_write_recursive($entity_type, $entity, $term,
  69. $op, $fvals, $base_table, $field_table);
  70. }
  71. }
  72. }
  73. }
  74. }
  75. }
  76. /**
  77. *
  78. * @param $entity_type
  79. * @param $entity
  80. * @param $op
  81. * @param $field_vals
  82. * @param $tablename
  83. * @param $type_field
  84. * @param $record_id
  85. * @param $depth
  86. * @throws Exception
  87. * @return
  88. * The record_id of the table if a matching record exists.
  89. */
  90. function tripal_chado_field_storage_write_recursive($entity_type, $entity, $term,
  91. $op, $field_vals, $base_table, $tablename, $type_field = NULL,
  92. $record_id = NULL, $depth = 0) {
  93. // Intialize the values array.
  94. $values = array();
  95. // Get the schema for this table so that we can identify the primary key
  96. // and foreign keys.
  97. $schema = chado_get_schema($tablename);
  98. $pkey_field = $schema['primary key'][0];
  99. $fkey_fields = $schema['foreign keys'];
  100. $fkey_fields_list = array();
  101. $fkey_base_linker = NULL;
  102. // STEP 1: Recurse on the FK fields.
  103. // Loop through the foreign keys so that we can recurse on those first.
  104. foreach ($fkey_fields as $fk_table => $details) {
  105. foreach ($details['columns'] as $local_id => $remote_id) {
  106. // If this is the base table then do not recurse on the type_id.
  107. if ($tablename == $base_table && $local_id == $type_field) {
  108. $values[$local_id] = $term->details['cvterm']->cvterm_id;
  109. continue;
  110. }
  111. // If this is not the base table then do not recurse on the FK field
  112. // that links this table to the base table.
  113. if ($tablename != $base_table && $details['table'] == $base_table) {
  114. $fkey_base_linker = $local_id;
  115. continue;
  116. }
  117. // Get the value of the FK field as provided by the user.
  118. $fk_val = NULL;
  119. $fk_vals = array();
  120. $fk_field_name = $tablename . '__' . $local_id;
  121. if (array_key_exists($fk_field_name, $field_vals)) {
  122. $fk_val = $field_vals[$fk_field_name][0][$base_table . '__' . $local_id];
  123. $fk_vals = $field_vals[$fk_field_name][0];
  124. }
  125. // Don't recurse if the value of the FK field is set to NULL. The
  126. // Tripal Chado API value for NULL is '__NULL__'.
  127. if ($fk_val == "__NULL__") {
  128. $values[$local_id] = $fk_val;
  129. continue;
  130. }
  131. // Don't recuse if there are no fkvals.
  132. if (count(array_keys($fk_vals)) == 0) {
  133. continue;
  134. }
  135. // Keep track of the FK fields so that in STEP 2 we don't have to
  136. // loop through the $fk_fields again.
  137. $fkey_fields_list[] = $local_id;
  138. // Recurse on the FK field. Pass in the ID for the FK field if one
  139. // exists in the $field_vals;
  140. $fk_val = tripal_chado_field_storage_write_recursive($entity_type,
  141. $entity, $term, $op, $fk_vals, $base_table, $fk_table, NULL, $fk_val, $depth + 1);
  142. if (isset($fk_val) and $fk_val != '' and $fk_val != 0) {
  143. $values[$local_id] = $fk_val;
  144. }
  145. }
  146. }
  147. // STEP 2: Loop through the non FK fields.
  148. // Loop through the fields passed to the function and find any that
  149. // are for this table. Then add their values to the $values array.
  150. foreach ($field_vals as $field_name => $items) {
  151. if (preg_match('/^' . $tablename . '__(.*)/', $field_name, $matches)) {
  152. $chado_field = $matches[1];
  153. // Skip the PKey field. We won't ever insert a primary key and if
  154. // one is provided in the fields then we use it for matching on an
  155. // update. We don't add it to the $values array in either case.
  156. if ($chado_field == $pkey_field) {
  157. continue;
  158. }
  159. // Skip FK fields as those should already have been dealt with the
  160. // recursive code above.
  161. if (in_array($chado_field, $fkey_fields_list)) {
  162. continue;
  163. }
  164. // If the value is empty then exclude this field
  165. if (!$items[0][$tablename . '__' . $chado_field]) {
  166. continue;
  167. }
  168. // Add the value of the field to the $values arr for later insert/update.
  169. $values[$chado_field] = $items[0][$tablename . '__' . $chado_field];
  170. }
  171. }
  172. // STEP 3: Insert/Update the record.
  173. // If there are no values then return.
  174. if (count($values) == 0) {
  175. return $record_id;
  176. }
  177. // If we don't have an incoming record ID then this is an insert.
  178. if ($record_id == NULL) {
  179. // STEP 3a: Before inserting, we want to make sure the record does not
  180. // already exist. Using the unique constraint check for a matching record.
  181. $options = array('is_duplicate' => TRUE);
  182. $is_duplicate = chado_select_record($tablename, array('*'), $values, $options);
  183. if($is_duplicate) {
  184. $record = chado_select_record($tablename, array('*'), $values);
  185. return $record[0]->$pkey_field;
  186. }
  187. // STEP 3b: Insert the reocrd
  188. // Insert the values array as a new record in the table.
  189. $record = chado_insert_record($tablename, $values);
  190. if ($record === FALSE) {
  191. throw new Exception('Could not insert Chado record into table: "' . $tablename . '".');
  192. }
  193. $record_id = $record[$pkey_field];
  194. }
  195. // We have an incoming record_id so this is an update.
  196. else {
  197. // TODO: what if the unique constraint matches another record? That is
  198. // not being tested for here.
  199. $match[$pkey_field] = $record_id;
  200. if (!chado_update_record($tablename, $match, $values)) {
  201. drupal_set_message("Could not update Chado record in table: $tablename.", 'error');
  202. }
  203. }
  204. return $record_id;
  205. }
  206. /**
  207. * Implements hook_field_storage_load().
  208. *
  209. * Responsible for loading the fields from the Chado database and adding
  210. * their values to the entity.
  211. */
  212. function tripal_chado_field_storage_load($entity_type, $entities, $age,
  213. $fields, $options) {
  214. $load_current = $age == FIELD_LOAD_CURRENT;
  215. global $language;
  216. $langcode = $language->language;
  217. foreach ($entities as $id => $entity) {
  218. // Get the base table and record id for the fields of this entity.
  219. $details = db_select('chado_entity', 'ce')
  220. ->fields('ce')
  221. ->condition('entity_id', $entity->id)
  222. ->execute()
  223. ->fetchObject();
  224. if (!$details) {
  225. // TODO: what to do if record is missing!
  226. }
  227. // Get some values needed for loading the values from Chado.
  228. $base_table = $details->data_table;
  229. $type_field = $details->field;
  230. $record_id = $details->record_id;
  231. // Get this table's schema.
  232. $schema = chado_get_schema($base_table);
  233. $pkey_field = $schema['primary key'][0];
  234. // Get the base record if one exists
  235. $columns = array('*');
  236. $match = array($pkey_field => $record_id);
  237. $record = chado_generate_var($base_table, $match);
  238. $entity->chado_record = $record;
  239. // For now, expand all 'text' fields.
  240. // TODO: we want to be a bit smarter and allow the user to configure this
  241. // for now we'll expand.
  242. foreach ($schema['fields'] as $field_name => $details) {
  243. if ($schema['fields'][$field_name]['type'] == 'text') {
  244. $record = chado_expand_var($record, 'field', $base_table . '.' . $field_name);
  245. }
  246. }
  247. // Iterate through the entity's fields so we can get the column names
  248. // that need to be selected from each of the tables represented.
  249. $tables = array();
  250. foreach ($fields as $field_id => $ids) {
  251. // By the time this hook runs, the relevant field definitions have been
  252. // populated and cached in FieldInfo, so calling field_info_field_by_id()
  253. // on each field individually is more efficient than loading all fields in
  254. // memory upfront with field_info_field_by_ids().
  255. $field = field_info_field_by_id($field_id);
  256. $field_name = $field['field_name'];
  257. $field_type = $field['type'];
  258. $field_module = $field['module'];
  259. // Skip fields that don't map to a Chado table (e.g. kvproperty_adder).
  260. if (!array_key_exists('settings', $field) or !array_key_exists('chado_table', $field['settings'])) {
  261. continue;
  262. }
  263. // Get the Chado table and column for this field.
  264. $field_table = $field['settings']['chado_table'];
  265. $field_column = $field['settings']['chado_column'];
  266. // There are only two types of fields: 1) fields that represent a single
  267. // column of the base table, or 2) fields that represent a linked record
  268. // in a many-to-one relationship with the base table.
  269. // Type 1: fields from base tables.
  270. if ($field_table == $base_table) {
  271. // Set an empty value by default, and if there is a record, then update.
  272. $entity->{$field_name}['und'][0]['value'] = '';
  273. if ($record and property_exists($record, $field_column)) {
  274. // If the field column is an object then it's a FK to another table.
  275. // and because $record object is created by the chado_generate_var()
  276. // function we must go one more level deeper to get the value
  277. if (is_object($record->$field_column)) {
  278. $entity->{$field_name}['und'][0][$field_table . '__' . $field_column] = $record->$field_column->$field_column;
  279. }
  280. else {
  281. // For non FK fields we'll make the field value be the same
  282. // as the column value.
  283. $entity->{$field_name}['und'][0]['value'] = $record->$field_column;
  284. $entity->{$field_name}['und'][0][$field_table . '__' . $field_column] = $record->$field_column;
  285. }
  286. }
  287. // Allow the creating module to alter the value if desired. The
  288. // module should do this if the field has any other form elements
  289. // that need populationg besides the value which was set above.
  290. module_load_include('inc', $field_module, 'includes/fields/' . $field_type);
  291. if (preg_match('/^chado/', $field_type) and class_exists($field_type)) {
  292. $field_obj = new $field_type();
  293. $field_obj->load($field, $entity, array('record' => $record));
  294. }
  295. $load_function = $field_type . '_load';
  296. if (function_exists($load_function)) {
  297. $load_function($field, $entity, $base_table, $record);
  298. }
  299. }
  300. // Type 2: fields for linked records. These fields will have any number
  301. // of form elements that might need populating so we'll offload the
  302. // loading of these fields to the field itself.
  303. if ($field_table != $base_table) {
  304. // Set an empty value by default, and let the hook function update it.
  305. $entity->{$field_name}['und'][0]['value'] = '';
  306. $load_function = $field_type . '_load';
  307. module_load_include('inc', $field_module, 'includes/fields/' . $field_type);
  308. if (preg_match('/^chado/', $field_type) and class_exists($field_type)) {
  309. $field_obj = new $field_type();
  310. $field_obj->load($field, $entity, array('record' => $record));
  311. }
  312. if (function_exists($load_function)) {
  313. $load_function($field, $entity, $base_table, $record);
  314. }
  315. }
  316. } // end: foreach ($fields as $field_id => $ids) {
  317. } // end: foreach ($entities as $id => $entity) {
  318. }
  319. /**
  320. * Iterates through all of the fields reformats to a key/value array.
  321. *
  322. * @param $fields
  323. */
  324. function tripal_chado_field_storage_unnest_fields($fields, $entity_type, $entity) {
  325. $new_fields = array();
  326. // Iterate through all of the fields.
  327. foreach ($fields as $field_id => $ids) {
  328. // Get the field name and information about it.
  329. $field = field_info_field_by_id($field_id);
  330. $field_name = $field['field_name'];
  331. // Some fields (e.g. chado_linker_cvterm_adder) don't add data to
  332. // Chado so they don't have a table, but they are still attached to the
  333. // entity. Just skip these.
  334. if (!array_key_exists('chado_table', $field['settings'])) {
  335. continue;
  336. }
  337. $chado_table = $field['settings']['chado_table'];
  338. $chado_column = $field['settings']['chado_column'];
  339. // Iterate through the field's items. Fields with cardinality ($delta) > 1
  340. // are multi-valued.
  341. $items = field_get_items($entity_type, $entity, $field_name);
  342. // if ($field_name == 'feature__organism_id') {
  343. // dpm($items);
  344. // }
  345. foreach ($items as $delta => $item) {
  346. foreach ($item as $item_name => $value) {
  347. $matches = array();
  348. if (preg_match('/^(.*?)__.*?$/', $item_name, $matches)) {
  349. $table_name = $matches[1];
  350. $new_fields[$field_name][$delta][$item_name] = $value;
  351. }
  352. }
  353. // If there is no value set for the field using the [table_name]__[field name]
  354. // naming schema then check if a 'value' item is present and if so use that.
  355. $item_name = $chado_table . '__' . $chado_column;
  356. if ((!array_key_exists($field_name, $new_fields) or
  357. !array_key_exists($item_name, $new_fields[$field_name][$delta])) and
  358. array_key_exists('value', $items[$delta])) {
  359. $new_fields[$field_name][$delta][$item_name] = $items[$delta]['value'];
  360. }
  361. }
  362. }
  363. return $new_fields;
  364. }
  365. /**
  366. * Implements hook_field_storage_query().
  367. *
  368. * Used by EntityFieldQuery to find the entities having certain entity
  369. * and field conditions and sort them in the given field order.
  370. *
  371. * NOTE: This function needs to exist or errors are triggered but so far it doesn't
  372. * appear to actually need to do anything...
  373. */
  374. function tripal_chado_field_storage_query($query) {
  375. $fieldConditions = $query->fieldConditions;
  376. foreach ($fieldConditions as $condition) {
  377. $field = $condition['field'];
  378. $field_name = $field['field_name'];
  379. $column = $condition['column'];
  380. }
  381. }