tripal_fields.field_storage.inc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. <?php
  2. /**
  3. * Implements hook_field_storage_info().
  4. */
  5. function tripal_fields_field_storage_info() {
  6. return array(
  7. 'field_chado_storage' => array(
  8. 'label' => t('Chado storage'),
  9. 'description' => t('Stores fields in the local Chado database.'),
  10. 'settings' => array(),
  11. ),
  12. );
  13. }
  14. /**
  15. * Implements hook_field_storage_query().
  16. *
  17. * @param $query
  18. */
  19. function tripal_fields_field_storage_query($query) {
  20. // TODO: figure out what this function does.
  21. }
  22. /**
  23. * Implements hook_field_storage_write().
  24. */
  25. function tripal_fields_field_storage_write($entity_type, $entity, $op, $fields) {
  26. // Convert the fields into a key/value list of fields and their values.
  27. $field_vals = tripal_fields_field_storage_unnest_fields($fields, $entity_type, $entity);
  28. $data_table = NULL;
  29. $type_field = NULL;
  30. $record_id = NULL;
  31. switch ($op) {
  32. case FIELD_STORAGE_INSERT:
  33. // Use the cvterm_id to look up tables where this term is used
  34. $sel_values = array(
  35. 'term_id' => array(
  36. 'cvterm_id' => $entity->cvterm_id,
  37. ),
  38. );
  39. $term_usage = chado_generate_var('tripal_term_usage', $sel_values, array('return_array' => 1));
  40. // For each table that uses this term, insert the field recursively
  41. foreach ($term_usage as $usage) {
  42. $data_table = $usage->data_table;
  43. //$type_table = $usage->type_table;
  44. $type_field = $usage->field;
  45. // Recursively write fields for the base table.
  46. tripal_fields_field_storage_write_recursive($entity_type, $entity,
  47. $op, $field_vals, $data_table, $type_field);
  48. // Now get the new chado_entity that was just added.
  49. $details = db_select('chado_entity', 'ce')
  50. ->fields('ce')
  51. ->condition('entity_id', $entity->id)
  52. ->execute()
  53. ->fetchObject();
  54. $data_table = $details->data_table;
  55. $type_field = $details->field;
  56. $record_id = $details->record_id;
  57. }
  58. break;
  59. case FIELD_STORAGE_UPDATE :
  60. // Get the base table and record id for the fields of this entity.
  61. $details = db_select('chado_entity', 'ce')
  62. ->fields('ce')
  63. ->condition('entity_id', $entity->id)
  64. ->execute()
  65. ->fetchObject();
  66. $data_table = $details->data_table;
  67. $type_field = $details->field;
  68. $record_id = $details->record_id;
  69. tripal_fields_field_storage_write_recursive($entity_type, $entity,
  70. $op, $field_vals, $data_table, $type_field, $record_id);
  71. if (!$details) {
  72. // TODO: what to do if record is missing!
  73. }
  74. break;
  75. }
  76. // Recursively write fields for any referring tables that have
  77. // values in the $field_vals array. There should be a key for each
  78. // FK table in the $field_vals array.
  79. $schema = chado_get_schema($data_table);
  80. $rtables = $schema['referring_tables'];
  81. dpm($schema);
  82. foreach ($rtables as $rtable) {
  83. dpm($rtable);
  84. if (array_key_exists($rtable, $field_vals)) {
  85. tripal_fields_field_storage_write_recursive($entity_type, $entity,
  86. $op, $field_vals, $rtable, $type_field, $record_id, 1);
  87. }
  88. }
  89. }
  90. /**
  91. * Implements hook_field_storage_write_recursive().
  92. */
  93. function tripal_fields_field_storage_write_recursive($entity_type, $entity,
  94. $op, $field_vals, $tablename, $type_field = NULL, $record_id = NULL, $depth = 0) {
  95. // Intialize the values array and $record_id;
  96. $values = array();
  97. // Get the schema for this table so that we can identify the primary key
  98. // and foreign keys.
  99. $schema = chado_get_schema($tablename);
  100. $pkey_field = $schema['primary key'][0];
  101. $fkey_fields = $schema['foreign keys'];
  102. $fkey_fields_list = array();
  103. // STEP 1: Recurse on the FK fields.
  104. // Loop through the foreign keys so that we can recurse on those first.
  105. foreach ($fkey_fields as $fk_table => $details) {
  106. foreach ($details['columns'] as $local_id => $remote_id) {
  107. // If this is the base table then do not recurse on the type_id. The
  108. // type_id is set in the entity. We are at the base table when
  109. // the depth is 0.
  110. if ($depth == 0 && $local_id == $type_field) {
  111. $values[$local_id] = $entity->cvterm_id;
  112. continue;
  113. }
  114. // Keep track of the FK fields so that in STEP 2 we don't have to
  115. // loop through the $fk_fields again.
  116. $fkey_fields_list[] = $local_id;
  117. // Get the value of the FK field as provided by the user.
  118. $fk_val = NULL;
  119. $fk_field_name = $tablename . '__' . $local_id;
  120. // If this is the base table then we'll find any values for the FK
  121. // fields as any other field. IF we are not at the base table,
  122. // then the FK values are in a sub element of the array where the
  123. // key of the field is the table name. Because we are looping
  124. // on FKs that are members of the current table we should always only
  125. // have one value.
  126. if ($depth == 0) {
  127. $fk_val = array_key_exists($fk_field_name, $field_vals) ? $field_vals[$fk_field_name] : NULL;
  128. }
  129. else {
  130. if (array_key_exists($fk_table, $field_vals) and
  131. array_key_exists($fk_field_name, $field_vals[$fk_table])) {
  132. $fk_val = $field_vals[$fk_table][$fk_field_name];
  133. }
  134. }
  135. // Don't recurse if the value of the FK field is set to NULL. The
  136. // Tripal Chado API value for NULL is '__NULL__'.
  137. if ($fk_val == "__NULL__") {
  138. $values[$local_id] = $fk_val;
  139. continue;
  140. }
  141. // Recurse on the FK field. Pass in the ID for the FK field if one
  142. // exists in the $field_vals;
  143. $fk_val = tripal_fields_field_storage_write_recursive($entity_type,
  144. $entity, $op, $field_vals, $fk_table, NULL, $fk_val, $depth + 1);
  145. if (isset($fk_val) and $fk_val != '' and $fk_val != 0) {
  146. $values[$local_id] = $fk_val;
  147. }
  148. }
  149. }
  150. // STEP 2: Loop through the incoming fields.
  151. // Loop through the fields passed to the function and find any that
  152. // are for this table. Then add their values to the $values array.
  153. foreach ($field_vals as $field_name => $field_val) {
  154. // If the field value is empty then continue.
  155. if (!isset($field_val) or $field_val === '' or $field_val === NULL) {
  156. continue;
  157. }
  158. if (preg_match('/^' . $tablename . '__(.*)/', $field_name, $matches)) {
  159. $chado_field = $matches[1];
  160. // Skip the Pkey field. We won't ever insert a primary key and if
  161. // one is provided in the fields then we use it for matching on an
  162. // update. We don't add it to the $values array in either case.
  163. if ($chado_field == $pkey_field) {
  164. continue;
  165. }
  166. // Skip FK fields as those should already have been dealt with the
  167. // recursive code above.
  168. if (in_array($chado_field, $fkey_fields_list)) {
  169. continue;
  170. }
  171. // Add the value of the field to the $values arr for later insert/update.
  172. $values[$chado_field] = $field_vals[$field_name];
  173. }
  174. }
  175. // STEP 3: Insert/Update the record.
  176. // If there are no values then return.
  177. if (count($values) == 0) {
  178. return $record_id;
  179. }
  180. // If we don't have an incoming record ID then this is an insert.
  181. if ($record_id == NULL) {
  182. // STEP 3a: Before inserting, we want to make sure the record does not
  183. // already exist. Using the unique constraint check for a matching record.
  184. $options = array('is_duplicate' => TRUE);
  185. $is_duplicate = chado_select_record($tablename, array('*'), $values, $options);
  186. if($is_duplicate) {
  187. $record = chado_select_record($tablename, array('*'), $values);
  188. return $record[0]->$pkey_field;
  189. }
  190. // STEP 3b: Insert the reocrd
  191. // Insert the values array as a new record in the table.
  192. $record = chado_insert_record($tablename, $values);
  193. if ($record === FALSE) {
  194. throw new Exception('Could not insert Chado record into table: "' . $tablename . '".');
  195. }
  196. $record_id = $record[$pkey_field];
  197. // Add a record to the chado_entity table so that the data for the
  198. // fields can be pulled from Chado when loaded the next time. Only do
  199. // this for the base table record.
  200. if ($depth == 0) {
  201. $record = array(
  202. 'entity_id' => $entity->id,
  203. 'record_id' => $record_id,
  204. 'data_table' => $tablename,
  205. 'type_table' => $tablename, // TODO: this must be fixed.
  206. 'field' => $type_field,
  207. );
  208. $success = drupal_write_record('chado_entity', $record);
  209. if (!$success) {
  210. drupal_set_message('Unable to insert new Chado entity.', 'error');
  211. }
  212. }
  213. }
  214. // We have an incoming record_id so this is an update.
  215. else {
  216. $match[$pkey_field] = $record_id;
  217. if (!chado_update_record($tablename, $match, $values)) {
  218. drupal_set_message("Could not update Chado record in table: $tablename.", 'error');
  219. }
  220. }
  221. return $record_id;
  222. }
  223. /**
  224. * Implements hook_field_storage_load().
  225. *
  226. * Responsible for loading the fields from the Chado database and adding
  227. * their values to the entity.
  228. */
  229. function tripal_fields_field_storage_load($entity_type, $entities, $age, $fields, $options) {
  230. $load_current = $age == FIELD_LOAD_CURRENT;
  231. global $language;
  232. $langcode = $language->language;
  233. foreach ($entities as $id => $entity) {
  234. // Get the base table and record id for the fields of this entity.
  235. $details = db_select('chado_entity', 'ce')
  236. ->fields('ce')
  237. ->condition('entity_id', $entity->id)
  238. ->execute()
  239. ->fetchObject();
  240. if (!$details) {
  241. // TODO: what to do if record is missing!
  242. }
  243. // Find out which table should receive the insert.
  244. $tablename = $details->data_table;
  245. $type_field = $details->field;
  246. $schema = chado_get_schema($tablename);
  247. $pkey_field = $schema['primary key'][0];
  248. $record_id = $details->record_id;
  249. // Iterate through the field names to get the list of tables and fields
  250. // that should be queried.
  251. $columns = array();
  252. foreach ($fields as $field_id => $ids) {
  253. // By the time this hook runs, the relevant field definitions have been
  254. // populated and cached in FieldInfo, so calling field_info_field_by_id()
  255. // on each field individually is more efficient than loading all fields in
  256. // memory upfront with field_info_field_by_ids().
  257. $field = field_info_field_by_id($field_id);
  258. $field_name = $field['field_name'];
  259. $matches = array();
  260. if (preg_match('/^(.*?)__(.*?)$/', $field_name, $matches)) {
  261. $table = $matches[1];
  262. $field = $matches[2];
  263. $columns[$table][] = $field;
  264. }
  265. }
  266. // Get the record
  267. $values = array($pkey_field => $record_id);
  268. $record = chado_select_record($tablename, $columns[$tablename], $values);
  269. // Now set the field values
  270. foreach ($fields as $field_id => $ids) {
  271. $field = field_info_field_by_id($field_id);
  272. $field_name = $field['field_name'];
  273. $matches = array();
  274. if (preg_match('/^(.*?)__(.*?)$/', $field_name, $matches)) {
  275. $table = $matches[1];
  276. $field = $matches[2];
  277. $entity->{$field_name}['und'][] = array('value' => $record[0]->$field);
  278. }
  279. }
  280. }
  281. }
  282. /**
  283. * Iterates through all of the fields reformats to a key/value array.
  284. *
  285. * @param $fields
  286. */
  287. function tripal_fields_field_storage_unnest_fields($fields, $entity_type, $entity) {
  288. $new_fields = array();
  289. foreach ($fields as $field_id => $ids) {
  290. $field = field_info_field_by_id($field_id);
  291. $field_name = $field['field_name'];
  292. // Currently, we only support one language, but for for the sake of
  293. // thoroughness we'll iterate through all possible languages.
  294. $all_languages = field_available_languages($entity_type, $field);
  295. $field_languages = array_intersect($all_languages, array_keys((array) $entity->$field_name));
  296. foreach ($field_languages as $langcode) {
  297. // The number of items is related to the cardinatily of the field.
  298. $items = (array) $entity->{$field_name}[$langcode];
  299. foreach ($items as $delta => $item) {
  300. // There must be a 'value' field.
  301. if (!array_key_exists('value', $item)) {
  302. continue;
  303. }
  304. // Case 1: base table fields.
  305. // If the value is not an array then we can just use the value as is.
  306. // This occurs for fields of the base table.
  307. if (!is_array($item['value'])) {
  308. $new_fields[$field_name] = $item['value'];
  309. continue;
  310. }
  311. // Case 2: foreign key fields.
  312. // If the value is an array then there are sub fields. This occurs
  313. // for fields that represent FKs with the base table.
  314. $i = 0;
  315. foreach ($item['value'] as $children) {
  316. foreach ($children as $child_field_name => $child_value) {
  317. $matches = array();
  318. if (preg_match('/^(.*?)__.*?$/', $child_field_name, $matches)) {
  319. $table_name = $matches[1];
  320. // Case 2a: the FK field is in the basee table.
  321. if ($table_name == $entity->chado_entity->data_table) {
  322. $new_fields[$child_field_name] = $child_value;
  323. }
  324. // Case 2b: the fields of the FK relationship are in the non
  325. // base table. We store these in an array with the key being
  326. // the table name.
  327. else {
  328. $new_fields[$table_name][$i][$child_field_name] = $child_value;
  329. }
  330. }
  331. }
  332. $i++;
  333. }
  334. }
  335. }
  336. }
  337. return $new_fields;
  338. }