tripal_ws.rest_v0.1.inc 36 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064
  1. <?php
  2. /**
  3. *
  4. */
  5. function tripal_ws_services_v0_1($api_url, $ws_path, $params) {
  6. // Set some initial variables.
  7. $response = array();
  8. $version = 'v0.1';
  9. // Set some defaults for the response.
  10. $response['@context'] = array();
  11. // Lump everything ito a try block so that if there is a problem we can
  12. // throw an error and have that returned in the response.
  13. try {
  14. // The services is the first argument
  15. $service = (count($ws_path) > 0) ? $ws_path[0] : '';
  16. switch ($service) {
  17. case 'doc':
  18. tripal_ws_services_v0_1_handle_doc_service($api_url, $response);
  19. break;
  20. case 'content':
  21. tripal_ws_services_v0_1_handle_content_service($api_url, $response, $ws_path, $params);
  22. break;
  23. case 'vocab':
  24. tripal_ws_services_v0_1_handle_vocab_service($api_url, $response, $ws_path);
  25. break;
  26. default:
  27. tripal_ws_services_v0_1_handle_no_service($api_url, $response);
  28. }
  29. }
  30. catch (Exception $e) {
  31. watchdog('tripal_ws', $e->getMessage(), array(), WATCHDOG_ERROR);
  32. $message = $e->getMessage();
  33. drupal_add_http_header('Status', '400 Bad Request');
  34. }
  35. return $response;
  36. }
  37. /**
  38. *
  39. * @param $api_url
  40. * @param $response
  41. * @param $ws_path
  42. */
  43. function tripal_ws_services_v0_1_handle_content_service($api_url, &$response, $ws_path, $params) {
  44. // Get the content type.
  45. $ctype = (count($ws_path) > 1) ? $ws_path[1] : '';
  46. $entity_id = (count($ws_path) > 2) ? $ws_path[2] : '';
  47. // If we have no content type then list all of the available content types.
  48. if (!$ctype) {
  49. tripal_ws_services_v0_1_get_content_types($api_url, $response);
  50. }
  51. // If we don't have an entity ID then show a paged list of entities with
  52. // the given type.
  53. else if ($ctype and !$entity_id) {
  54. tripal_ws_services_v0_1_get_content_type($api_url, $response, $ws_path, $ctype, $params);
  55. }
  56. // If we have a content type and an entity ID then show the entity
  57. else {
  58. tripal_ws_services_v0_1_get_content($api_url, $response, $ws_path, $ctype, $entity_id, $params);
  59. }
  60. }
  61. /**
  62. *
  63. * @param $api_url
  64. * @param $response
  65. * @param $ws_path
  66. */
  67. function tripal_ws_services_v0_1_handle_vocab_service($api_url, &$response, $ws_path) {
  68. // Get the vocab name.
  69. $vocabulary = (count($ws_path) > 1) ? $ws_path[1] : '';
  70. $accession = (count($ws_path) > 2) ? $ws_path[2] : '';
  71. // If we have no $vocabulary type then list all of the available vocabs.
  72. if (!$vocabulary) {
  73. tripal_ws_services_v0_1_get_vocabs($api_url, $response);
  74. }
  75. // If we don't have a $vocabulary then show a paged list of terms.
  76. else if ($vocabulary and !$accession) {
  77. tripal_ws_services_v0_1_get_vocab($api_url, $response, $ws_path, $vocabulary);
  78. }
  79. // If we have a content type and an entity ID then show the entity
  80. else if ($vocabulary and $accession) {
  81. tripal_ws_services_v0_1_get_term($api_url, $response, $ws_path, $vocabulary, $accession);
  82. }
  83. }
  84. /**
  85. *
  86. * @param $api_url
  87. * @param $response
  88. */
  89. function tripal_ws_services_v0_1_get_vocabs($api_url, &$response) {
  90. // First, add the vocabularies used into the @context section.
  91. $response['@context']['rdfs'] = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
  92. $response['@context']['hydra'] = 'http://www.w3.org/ns/hydra/core#';
  93. // Next add in the ID for tihs resource.
  94. $response['@id'] = url($api_url . '/vocab', array('absolute' => TRUE));
  95. // Start the list.
  96. $response['@type'] = 'Collection';
  97. $response['totalItems'] = 0;
  98. $response['label'] = 'Content Types';
  99. $response['member'] = array();
  100. $vocabs = db_select('tripal_vocab', 'tv')
  101. ->fields('tv')
  102. ->execute();
  103. // Iterate through the vocabularies and add an entry in the collection.
  104. $i = 0;
  105. while ($vocab = $vocabs->fetchObject()) {
  106. // Add the bundle as a content type.
  107. $response['member'][] = array(
  108. '@id' => url($api_url . '/vocab/' . urlencode($vocab->vocabulary), array('absolute' => TRUE)),
  109. '@type' => 'vocabulary',
  110. 'vocabulary' => $vocab->vocabulary,
  111. );
  112. $i++;
  113. }
  114. $response['totalItems'] = $i;
  115. // Lastly, add in the terms used into the @context section.
  116. $response['@context']['Collection'] = 'hydra:Collection';
  117. $response['@context']['totalItems'] = 'hydra:totalItems';
  118. $response['@context']['member'] = 'hydra:member';
  119. $response['@context']['label'] = 'rdfs:label';
  120. $response['@context']['description'] = 'hydra:description';
  121. tripal_ws_services_v0_1_write_context($response, $ctype);
  122. }
  123. /**
  124. *
  125. * @param $api_url
  126. * @param $response
  127. * @param $ws_path
  128. */
  129. function tripal_ws_services_v0_1_get_vocab($api_url, &$response, $ws_path, $vocabulary) {
  130. // First, add the vocabularies used into the @context section.
  131. $response['@context']['rdfs'] = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
  132. $response['@context']['hydra'] = 'http://www.w3.org/ns/hydra/core#';
  133. $response['@context']['schema'] = 'https://schema.org/';
  134. // Next add in the ID for tihs resource.
  135. $response['@id'] = url($api_url . '/vocab/' . $vocabulary, array('absolute' => TRUE));
  136. // Get the vocabulary
  137. $vocab = tripal_load_vocab_entity(array('vocabulary' => $vocabulary));
  138. // Start the list.
  139. $response['@type'] = 'Collection';
  140. $response['totalItems'] = 0;
  141. $response['label'] = vocabulary . " vocabulary collection";
  142. $response['comment'] = 'The following list of terms may not be the full ' .
  143. 'list for the vocabulary. The terms listed here are only those ' .
  144. 'that have associated content on this site.';
  145. // Get the list of terms for this vocab.
  146. $query = db_select('tripal_term', 'tt')
  147. ->fields('tt', array('id'))
  148. ->condition('vocab_id', $vocab->id)
  149. ->orderBy('accession', 'DESC');
  150. // Iterate through the entities and add them to the list.
  151. $terms = $query->execute();
  152. $i = 0;
  153. while($term = $terms->fetchObject()) {
  154. $term = tripal_load_term_entity(array('term_id' => $term->id));
  155. $response['member'][] = array(
  156. '@id' => url($api_url . '/vocab/' . urlencode($vocabulary) . '/' . urlencode($term->accession), array('absolute' => TRUE)),
  157. '@type' => 'vocabulary_term',
  158. 'vocabulary' => $vocab->vocabulary,
  159. 'accession' => $term->accession,
  160. 'name' => $term->name,
  161. 'definition' => $term->definition,
  162. );
  163. $i++;
  164. }
  165. $response['totalItems'] = $i;
  166. // Lastly, add in the terms used into the @context section.
  167. $response['@context']['Collection'] = 'hydra:Collection';
  168. $response['@context']['totalItems'] = 'hydra:totalItems';
  169. $response['@context']['member'] = 'hydra:member';
  170. $response['@context']['label'] = 'rdfs:label';
  171. $response['@context']['comment'] = 'rdfs:comment';
  172. $response['@context']['itemPage'] = 'schema:itemPage';
  173. tripal_ws_services_v0_1_write_context($response, $ctype);
  174. }
  175. /**
  176. *
  177. * @param $api_url
  178. * @param $response
  179. * @param $ws_path
  180. */
  181. function tripal_ws_services_v0_1_get_term($api_url, &$response, $ws_path, $vocabulary, $accession) {
  182. // First, add the vocabularies used into the @context section.
  183. $response['@context']['rdfs'] = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
  184. $response['@context']['hydra'] = 'http://www.w3.org/ns/hydra/core#';
  185. $response['@context']['schema'] = 'https://schema.org/';
  186. // Get the term.
  187. $term = tripal_load_term_entity(array('vocabulary' => $vocabulary, 'accession' => $accession));
  188. // Next add in the ID and Type for this resources.
  189. $response['@id'] = url($api_url . '/vocab/' . urlencode($vocabulary) . '/' . urlencode($accession), array('absolute' => TRUE));
  190. $response['@type'] = 'vocabulary_term';
  191. $response['label'] = $term->name;
  192. $response['vocabulary'] = $vocabulary;
  193. $response['accession'] = $accession;
  194. $response['name'] = $term->name;
  195. $response['definition'] = $term->definition;
  196. if ($term->url) {
  197. $response['URL'] = $term->url;
  198. }
  199. // Lastly, add in the terms used into the @context section.
  200. $response['@context']['label'] = 'rdfs:label';
  201. $response['@context']['itemPage'] = 'schema:itemPage';
  202. tripal_ws_services_v0_1_write_context($response, $ctype);
  203. }
  204. /**
  205. * Provides a collection (list) of all of the content types.
  206. *
  207. * @param $api_url
  208. * @param $response
  209. */
  210. function tripal_ws_services_v0_1_get_content_types($api_url, &$response) {
  211. // First, add the vocabularies used into the @context section.
  212. $response['@context']['rdfs'] = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
  213. $response['@context']['hydra'] = 'http://www.w3.org/ns/hydra/core#';
  214. // Next add in the ID for tihs resource.
  215. $response['@id'] = url($api_url . '/content', array('absolute' => TRUE));
  216. // Start the list.
  217. $response['@type'] = 'Collection';
  218. $response['totalItems'] = 0;
  219. $response['label'] = 'Content Types';
  220. $response['member'] = array();
  221. // Get the list of published terms (these are the bundle IDs)
  222. $bundles = db_select('tripal_bundle', 'tb')
  223. ->fields('tb')
  224. ->orderBy('tb.label', 'ASC')
  225. ->execute();
  226. // Iterate through the terms and add an entry in the collection.
  227. $i = 0;
  228. while ($bundle = $bundles->fetchObject()) {
  229. $entity = entity_load('TripalTerm', array('id' => $bundle->term_id));
  230. $term = reset($entity);
  231. $vocab = $term->vocab;
  232. $response['@context'][$term->name] = $term->url;
  233. // Get the bundle description. If no description is provided then
  234. // use the term definition
  235. $description = tripal_get_bundle_variable('description', $bundle->id);
  236. if (!$description) {
  237. $description = $term->definition;
  238. }
  239. // Add the bundle as a content type.
  240. $response['member'][] = array(
  241. '@id' => url($api_url . '/content/' . urlencode($bundle->label), array('absolute' => TRUE)),
  242. '@type' => $term->name,
  243. 'label' => $bundle->label,
  244. 'description' => $description,
  245. );
  246. $i++;
  247. }
  248. $response['totalItems'] = $i;
  249. // Lastly, add in the terms used into the @context section.
  250. $response['@context']['Collection'] = 'hydra:Collection';
  251. $response['@context']['totalItems'] = 'hydra:totalItems';
  252. $response['@context']['member'] = 'hydra:member';
  253. $response['@context']['label'] = 'rdfs:label';
  254. $response['@context']['description'] = 'hydra:description';
  255. tripal_ws_services_v0_1_write_context($response, $ctype);
  256. }
  257. /**
  258. *
  259. * @param $api_url
  260. * @param $response
  261. * @param $ws_path
  262. */
  263. function tripal_ws_services_v0_1_get_content_type($api_url, &$response, $ws_path, $ctype, $params) {
  264. // First, add the vocabularies used into the @context section.
  265. $response['@context']['rdfs'] = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
  266. $response['@context']['hydra'] = 'http://www.w3.org/ns/hydra/core#';
  267. // Next add in the ID for this resource.
  268. $URL = url($api_url . '/content/' . $ctype, array('absolute' => TRUE));
  269. $response['@id'] = $URL;
  270. // Get the TripalBundle, TripalTerm and TripalVocab type for this type.
  271. $bundle = tripal_load_bundle_entity(array('label' => $ctype));
  272. $term = entity_load('TripalTerm', array('id' => $bundle->term_id));
  273. $term = reset($term);
  274. $response['@context'][$term->name] = $term->url;
  275. // Start the list.
  276. $response['@type'] = 'Collection';
  277. $response['totalItems'] = 0;
  278. $response['label'] = $bundle->label . " collection";
  279. // Iterate through the fields and create a $field_mapping array that makes
  280. // it easier to determine which filter criteria belongs to which field. The
  281. // key is the label for the field and the value is the field name. This way
  282. // user's can use the field label or the field name to form a query.
  283. $field_mapping = array();
  284. $fields = field_info_fields();
  285. foreach ($fields as $field) {
  286. if (array_key_exists('TripalEntity', $field['bundles'])) {
  287. foreach ($field['bundles']['TripalEntity'] as $bundle_name) {
  288. if ($bundle_name == $bundle->name) {
  289. $instance = field_info_instance('TripalEntity', $field['field_name'], $bundle_name);
  290. if (array_key_exists('term_accession', $instance['settings'])){
  291. $vocabulary = $instance['settings']['term_vocabulary'];
  292. $accession = $instance['settings']['term_accession'];
  293. $term = tripal_get_term_details($vocabulary, $accession);
  294. $key = $term['name'];
  295. $key = strtolower(preg_replace('/ /', '_', $key));
  296. $field_mapping[$key] = $field['field_name'];
  297. $field_mapping[$field['field_name']] = $field['field_name'];
  298. }
  299. }
  300. }
  301. }
  302. }
  303. // Convert the filters to their field names
  304. $new_params = array();
  305. $order = array();
  306. $order_dir = array();
  307. $URL_add = array();
  308. foreach ($params as $param => $value) {
  309. $URL_add[] = "$param=$value";
  310. // Ignore non filter parameters
  311. if ($param == 'page' or $param == 'limit') {
  312. continue;
  313. }
  314. // Handle order separately
  315. if ($param == 'order') {
  316. $temp = explode(',', $value);
  317. foreach ($temp as $key) {
  318. $matches = array();
  319. $dir = 'ASC';
  320. // The user can provide a direction by separating the field key and the
  321. // direction with a '|' character.
  322. if (preg_match('/^(.*)\|(.*)$/', $key, $matches)) {
  323. $key = $matches[1];
  324. if ($matches[2] == 'ASC' or $matches[2] == 'DESC') {
  325. $dir = $matches[2];
  326. }
  327. else {
  328. // TODO: handle error of providing an incorrect direction.
  329. }
  330. }
  331. if (array_key_exists($key, $field_mapping)) {
  332. $order[$field_mapping[$key]] = $key;
  333. $order_dir[] = $dir;
  334. }
  335. else {
  336. // TODO: handle error of providing a non existing field name.
  337. }
  338. }
  339. continue;
  340. }
  341. // Break apart any operators
  342. $key = $param;
  343. $op = '=';
  344. $matches = array();
  345. if (preg_match('/^(.+);(.+)$/', $key, $matches)) {
  346. $key = $matches[1];
  347. $op = $matches[2];
  348. }
  349. // Break apart any subkeys and pull the first one out for the term name key.
  350. $subkeys = explode(',', $key);
  351. if (count($subkeys) > 0) {
  352. $key = array_shift($subkeys);
  353. }
  354. $column_name = $key;
  355. // Map the values in the filters to their appropriate field names.
  356. if (array_key_exists($key, $field_mapping)) {
  357. $field_name = $field_mapping[$key];
  358. if (count($subkeys) > 0) {
  359. $column_name .= '.' . implode('.', $subkeys);
  360. }
  361. $new_params[$field_name]['value'] = $value;
  362. $new_params[$field_name]['op'] = $op;
  363. $new_params[$field_name]['column'] = $column_name;
  364. }
  365. else {
  366. throw new Exception("The filter term, '$key', is not available for use.");
  367. }
  368. }
  369. // Get the list of entities for this bundle.
  370. $query = new TripalFieldQuery();
  371. $query->entityCondition('entity_type', 'TripalEntity');
  372. $query->entityCondition('bundle', $bundle->name);
  373. foreach($new_params as $field_name => $details) {
  374. $value = $details['value'];
  375. $column_name = $details['column'];
  376. switch ($details['op']) {
  377. case 'eq':
  378. $op = '=';
  379. break;
  380. case 'gt':
  381. $op = '>';
  382. break;
  383. case 'gte':
  384. $op = '>=';
  385. break;
  386. case 'lt':
  387. $op = '<';
  388. break;
  389. case 'lte':
  390. $op = '<=';
  391. break;
  392. case 'ne':
  393. $op = '<>';
  394. break;
  395. case 'contains':
  396. $op = 'CONTAINS';
  397. break;
  398. case 'starts':
  399. $op = 'STARTS WITH';
  400. break;
  401. default:
  402. $op = '=';
  403. }
  404. //print_r(array($field_name, $column_name, $value, $op));
  405. // We pass in the $column_name as an identifier for any sub fields
  406. // that are present for the fields.
  407. $query->fieldCondition($field_name, $column_name, $value, $op);
  408. }
  409. // Perform the query just as a count first to get the number of records.
  410. $cquery = clone $query;
  411. $cquery->count();
  412. $num_records = $cquery->execute();
  413. $num_records = count($num_records['TripalEntity']);
  414. if (!$num_records) {
  415. $num_records = 0;
  416. }
  417. // Add in the pager to the response.
  418. $response['totalItems'] = $num_records;
  419. $limit = array_key_exists('limit', $params) ? $params['limit'] : 25;
  420. $total_pages = ceil($num_records / $limit);
  421. $page = array_key_exists('page', $params) ? $params['page'] : 1;
  422. if ($num_records > 0) {
  423. $response['view'] = array(
  424. '@id' => $URL . '?' . implode('&', array_merge($URL_add, array("page=$page", "limit=$limit"))),
  425. '@type' => 'PartialCollectionView',
  426. 'first' => $URL . '?' . implode('&', array_merge($URL_add, array("page=1", "limit=$limit"))),
  427. 'last' => $URL . '?' . implode('&', array_merge($URL_add, array("page=$total_pages", "limit=$limit"))),
  428. );
  429. $prev = $page - 1;
  430. $next = $page + 1;
  431. if ($prev > 0) {
  432. $response['view']['previous'] = $URL . '?' . implode('&', array_merge($URL_add, array("page=$prev", "limit=$limit")));
  433. }
  434. if ($next < $total_pages) {
  435. $response['view']['next'] = $URL . '?' . implode('&', array_merge($URL_add, array("page=$next", "limit=$limit")));
  436. }
  437. }
  438. // Set the query order
  439. $order_keys = array_keys($order);
  440. for($i = 0; $i < count($order_keys); $i++) {
  441. $query->fieldOrderBy($order_keys[$i], $order[$order_keys[$i]], $order_dir[$i]);
  442. }
  443. // Set the query range
  444. $start = ($page - 1) * $limit;
  445. $query->range($start, $limit);
  446. // Now perform the query.
  447. $results = $query->execute();
  448. // Iterate through the entities and add them to the list.
  449. $i = 0;
  450. foreach ($results['TripalEntity'] as $entity_id => $stub) {
  451. $vocabulary = '';
  452. $term_name = '';
  453. // We don't need all of the attached fields for an entity so, we'll
  454. // not use the entity_load() function. Instead just pull it from the
  455. // database table.
  456. $query = db_select('tripal_entity', 'TE');
  457. $query->join('tripal_term', 'TT', 'TE.term_id = TT.id');
  458. $query->fields('TE');
  459. $query->fields('TT', array('name'));
  460. $query->condition('TE.id', $entity_id);
  461. $entity = $query->execute()->fetchObject();
  462. //$entity = tripal_load_entity('TripalEntity', array($entity->id));
  463. $response['member'][] = array(
  464. '@id' => url($api_url . '/content/' . urlencode($ctype) . '/' . $entity->id, array('absolute' => TRUE)),
  465. '@type' => $entity->name,
  466. 'label' => $entity->title,
  467. 'itemPage' => url('/bio_data/' . $entity->id, array('absolute' => TRUE)),
  468. );
  469. $i++;
  470. }
  471. // Lastly, add in the terms used into the @context section.
  472. $response['@context']['Collection'] = 'hydra:Collection';
  473. $response['@context']['totalItems'] = 'hydra:totalItems';
  474. $response['@context']['member'] = 'hydra:member';
  475. $response['@context']['label'] = 'rdfs:label';
  476. $response['@context']['itemPage'] = 'schema:itemPage';
  477. // $response['operation'][] = array(
  478. // '@type' => 'hydra:CreateResourceOperation',
  479. // 'hydra:method' => 'PUT'
  480. // );
  481. // $response['query'] = array(
  482. // '@id' => $response['@id'],
  483. // '@type' => 'IriTemplate',
  484. // "template" => $response['@id'] . "{?name,}",
  485. // "mapping" => array(
  486. // array(
  487. // "hydra:variable" => 'name',
  488. // "hydra:property" => 'name',
  489. // )
  490. // )
  491. // );
  492. tripal_ws_services_v0_1_write_context($response, $ctype);
  493. }
  494. /**
  495. *
  496. * @param unknown $response
  497. * @param unknown $ws_path
  498. * @param unknown $ctype
  499. * @param unknown $entity_id
  500. * @param unknown $params
  501. */
  502. function tripal_ws_services_v0_1_get_content_add_fields($entity, $bundle, $api_url, &$response, $ws_path, $ctype, $entity_id, $params) {
  503. // Get information about the fields attached to this bundle and sort them
  504. // in the order they were set for the display.
  505. $instances = field_info_instances('TripalEntity', $bundle->name);
  506. uasort($instances, function($a, $b) {
  507. $a_weight = (is_array($a) && isset($a['widget']['weight'])) ? $a['widget']['weight'] : 0;
  508. $b_weight = (is_array($b) && isset($b['widget']['weight'])) ? $b['widget']['weight'] : 0;
  509. if ($a_weight == $b_weight) {
  510. return 0;
  511. }
  512. return ($a_weight < $b_weight) ? -1 : 1;
  513. });
  514. // Iterate through the fields and add each value to the response.
  515. //$response['fields'] = $fields;
  516. foreach ($instances as $field_name => $instance) {
  517. // Ignore the content_type field provided by Tripal.
  518. if ($field_name == 'content_type') {
  519. continue;
  520. }
  521. // Skip hidden fields.
  522. if ($instance['display']['default']['type'] == 'hidden') {
  523. continue;
  524. }
  525. // Get the information about this field. It will have settings different
  526. // from the instance.
  527. $field = field_info_field($field_name);
  528. // By default, the label for the key in the output should be the
  529. // term from the vocabulary that the field is assigned. But in the
  530. // case that the field is not assigned a term, we must use the field name.
  531. $field_name = $instance['field_name'];
  532. $vocabulary = $instance['settings']['term_vocabulary'];
  533. $accession = $instance['settings']['term_accession'];
  534. $term = tripal_get_term_details($vocabulary, $accession);
  535. if ($term) {
  536. $key = $term['name'];
  537. $key_adj = strtolower(preg_replace('/ /', '_', $key));
  538. // The term schema:url also points to a recource so we need
  539. // to make sure we set the type to be '@id'.
  540. if ($vocabulary == 'schema' and $accession == 'url') {
  541. $response['@context'][$key_adj] = array(
  542. '@id' => $term['url'],
  543. '@type' => '@id',
  544. );
  545. }
  546. else {
  547. $response['@context'][$key_adj] = $term['url'];
  548. }
  549. }
  550. else {
  551. continue;
  552. }
  553. // If this field should not be attached by default then just add a link
  554. // so that the caller can get the information separately.
  555. $instance_settings = $instance['settings'];
  556. if (array_key_exists('auto_attach', $instance['settings']) and
  557. $instance_settings['auto_attach'] == FALSE) {
  558. $response['@context'][$key_adj] = array(
  559. '@id' => $response['@context'][$key_adj],
  560. '@type' => '@id'
  561. );
  562. // Add a URL only if there are values. If there are no values then
  563. // don't add a URL which would make the end-user think they can get
  564. // that information.
  565. $items = field_get_items('TripalEntity', $entity, $field_name);
  566. if ($items and count($items) > 0 and $items[0]['value']) {
  567. $response[$key_adj] = url($api_url . '/content/' . $ctype . '/' . $entity->id . '/' . urlencode($key), array('absolute' => TRUE));
  568. }
  569. else {
  570. $response[$key_adj] = NULL;
  571. }
  572. continue;
  573. }
  574. // Get the details for this field for the JSON-LD response.
  575. tripal_ws_services_v0_1_get_content_add_field($key_adj, $entity, $field, $instance, $api_url, $response);
  576. }
  577. // Lastly, add in the terms used into the @context section.
  578. $response['@context']['label'] = 'https://www.w3.org/TR/rdf-schema/#ch_label';
  579. $response['@context']['itemPage'] = 'https://schema.org/ItemPage';
  580. // $response['operation'][] = array(
  581. // '@type' => 'hydra:DeleteResourceOperation',
  582. // 'hydra:method' => 'DELETE'
  583. // );
  584. // $response['operation'][] = array(
  585. // '@type' => 'hydra:ReplaceResourceOperation',
  586. // 'hydra:method' => 'POST'
  587. // );
  588. }
  589. /**
  590. *
  591. * @param unknown $field_arg
  592. * @param unknown $api_url
  593. * @param unknown $response
  594. * @param unknown $ws_path
  595. * @param unknown $ctype
  596. * @param unknown $entity_id
  597. * @param unknown $params
  598. */
  599. function tripal_ws_services_v0_1_get_content_find_field($field_arg, $ctype, $entity_id) {
  600. $bundle = tripal_load_bundle_entity(array('label' => $ctype));
  601. $entity = tripal_load_entity('TripalEntity', array('id' => $entity_id));
  602. $entity = reset($entity);
  603. $term = NULL;
  604. // Find the field whose term matches the one provied.
  605. $value = array();
  606. $instances = field_info_instances('TripalEntity', $bundle->name);
  607. foreach ($instances as $instance) {
  608. $field_name = $instance['field_name'];
  609. $field = field_info_field($field_name);
  610. $vocabulary = $instance['settings']['term_vocabulary'];
  611. $accession = $instance['settings']['term_accession'];
  612. $temp_term = tripal_get_term_details($vocabulary, $accession);
  613. if ($temp_term['name'] == $field_arg) {
  614. return array($entity, $bundle, $field, $instance, $temp_term);
  615. }
  616. }
  617. }
  618. /**
  619. *
  620. * @param unknown $api_url
  621. * @param unknown $response
  622. * @param unknown $ws_path
  623. * @param unknown $ctype
  624. * @param unknown $entity_id
  625. * @param unknown $params
  626. * @return number
  627. */
  628. function tripal_ws_services_v0_1_get_content($api_url, &$response, $ws_path, $ctype, $entity_id, $params) {
  629. // First, add the vocabularies used into the @context section.
  630. $response['@context']['rdfs'] = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
  631. $response['@context']['hydra'] = 'http://www.w3.org/ns/hydra/core#';
  632. // If we have an argument in the 4th element (3rd index) then the user
  633. // is requesting to expand the details of a field that was not
  634. // initially attached to the enity.
  635. $field_arg = '';
  636. if (array_key_exists(3, $ws_path)) {
  637. $field_arg = urldecode($ws_path[3]);
  638. list($entity, $bundle, $field, $instance, $term) = tripal_ws_services_v0_1_get_content_find_field($field_arg, $ctype, $entity_id);
  639. // If we couldn't match this field argument to a field and entity then return
  640. if (!$entity or !$field) {
  641. return;
  642. }
  643. // Next add in the ID and Type for this resources.
  644. $key = $term['name'];
  645. $key_adj = strtolower(preg_replace('/ /', '_', $term['name']));
  646. $response['@context'][$key_adj] = $term['url'];
  647. $response['@id'] = url($api_url . '/content/' . $ctype . '/' . $entity->id . '/' . urlencode($key), array('absolute' => TRUE));
  648. // Attach the field and then add it's values to the response.
  649. field_attach_load($entity->type, array($entity->id => $entity),
  650. FIELD_LOAD_CURRENT, array('field_id' => $field['id']));
  651. tripal_ws_services_v0_1_get_content_add_field($key_adj, $entity, $field, $instance, $api_url, $response, TRUE);
  652. tripal_ws_services_v0_1_write_context($response, $ctype);
  653. return;
  654. }
  655. // If we don't have a 4th argument then we're loading the base record.
  656. // Get the TripalBundle, TripalTerm and TripalVocab type for this type.
  657. $bundle = tripal_load_bundle_entity(array('label' => $ctype));
  658. $term = entity_load('TripalTerm', array('id' => $bundle->term_id));
  659. $term = reset($term);
  660. $vocab = $term->vocab;
  661. // Add the vocabulary for this content type to the @context section.
  662. if (!array_key_exists($vocab->vocabulary, $response['@context'])) {
  663. // If there is no URL prefix then use this API's vocabulary API
  664. if (property_exists($term, 'urlprefix')) {
  665. $response['@context'][$vocab->vocabulary] = $term->urlprefix;
  666. }
  667. else {
  668. $response['@context'][$vocab->vocabulary] = url($api_url . '/vocab/' . $vocab->vocabulary . '/', array('absolute' => TRUE));
  669. }
  670. }
  671. // Get the TripalEntity
  672. $entity = tripal_load_entity('TripalEntity', array('id' => $entity_id));
  673. $entity = reset($entity);
  674. // Next add in the ID and Type for this resources.
  675. $response['@id'] = url($api_url . '/content/' . $ctype . '/' . $entity_id, array('absolute' => TRUE));
  676. $response['@type'] = $term->name;
  677. $response['@context'][$term->name] = $term->url;
  678. $response['label'] = $entity->title;
  679. $response['itemPage'] = url('/bio_data/' . $entity->id, array('absolute' => TRUE));
  680. tripal_ws_services_v0_1_get_content_add_fields($entity, $bundle, $api_url, $response, $ws_path, $ctype, $entity_id, $params);
  681. tripal_ws_services_v0_1_write_context($response, $ctype);
  682. }
  683. /**
  684. *
  685. * @param $response
  686. * @param $ctype
  687. */
  688. function tripal_ws_services_v0_1_write_context(&$response, $ctype) {
  689. // Save the response '@context' into a temporary file
  690. $context = array('@context' => $response['@context']);
  691. $file_name = drupal_tempnam(file_default_scheme() . '://', 'tws_context-') . '.json';
  692. $context_file = file_save_data(json_encode($context), $file_name, FILE_EXISTS_REPLACE );
  693. // Mark the file as temporary by setting it's status
  694. $context_file->status = 0;
  695. file_save($context_file);
  696. // Return the response with the '@context' section replaced with the file URL.
  697. $response['@context'] = file_create_url($context_file->uri);
  698. }
  699. /**
  700. *
  701. */
  702. function tripal_ws_services_v0_1_get_content_add_field($key, $entity, $field, $instance, $api_url, &$response, $is_field_page = NULL) {
  703. // Get the field settings.
  704. $field_name = $field['field_name'];
  705. $field_settings = $field['settings'];
  706. $items = field_get_items('TripalEntity', $entity, $field_name);
  707. if (!$items) {
  708. return;
  709. }
  710. // Give modules the opportunity to edit values for web services. This hook
  711. // really should be used sparingly. Where it helps is with non Tripal fields
  712. // that are added to a TripalEntity content type and it doesn't follow
  713. // the rules (e.g. Image field).
  714. drupal_alter('tripal_ws_value', $items, $field, $instance);
  715. $values = array();
  716. for ($i = 0; $i < count($items); $i++) {
  717. $values[$i] = tripal_ws_services_v0_1_rewrite_field_items_keys($items[$i]['value'], $response, $api_url);
  718. }
  719. // If the field cardinality is 1
  720. if ($field[cardinality] == 1) {
  721. // If the value is an array and this is the field page then all of those
  722. // key/value pairs should be added directly to the response.
  723. if (is_array($values[0])) {
  724. if ($is_field_page) {
  725. foreach ($values[0] as $k => $v) {
  726. $response[$k] = $v;
  727. }
  728. }
  729. else {
  730. $response[$key] = $values[0];
  731. }
  732. }
  733. // If the value is not an array it's a scalar so add it as is to the
  734. // response.
  735. else {
  736. $response[$key] = $values[0];
  737. }
  738. }
  739. // If the field cardinality is > 1
  740. if ($field[cardinality] != 1) {
  741. // If this is the field page then the Collection is added directly to the
  742. // response, otherwise, it's added under the field $key.
  743. if ($is_field_page) {
  744. $response['@type'] = 'Collection';
  745. $response['totalItems'] = count($values);
  746. $response['label'] = $instance['label'];
  747. $response['member'] = $values;
  748. }
  749. else {
  750. $response[$key] = array(
  751. '@type' => 'Collection',
  752. 'totalItems' => count($values),
  753. 'label' => $instance['label'],
  754. 'member' => $values,
  755. );
  756. }
  757. }
  758. }
  759. /**
  760. *
  761. */
  762. function tripal_ws_services_v0_1_rewrite_field_items_keys($value, &$response, $api_url) {
  763. $new_value = '';
  764. // If the value is an array rather than a scalar then map the sub elements
  765. // to controlled vocabulary terms.
  766. if (is_array($value)) {
  767. $temp = array();
  768. foreach ($value as $k => $v) {
  769. $matches = array();
  770. if (preg_match('/^(.+):(.+)$/', $k, $matches)) {
  771. $vocabulary = $matches[1];
  772. $accession = $matches[2];
  773. $term = tripal_get_term_details($vocabulary, $accession);
  774. $key_adj = strtolower(preg_replace('/ /', '_', $term['name']));
  775. if (is_array($v)) {
  776. $temp[$key_adj] = tripal_ws_services_v0_1_rewrite_field_items_keys($v, $response, $api_url);
  777. }
  778. else {
  779. $temp[$key_adj] = $v !== "" ? $v : NULL;
  780. }
  781. // The term schema:url also points to a recource so we need
  782. // to make sure we set the type to be '@id'.
  783. if ($vocabulary == 'schema' and $accession == 'url') {
  784. $response['@context'][$key_adj] = array(
  785. '@id' => $term['url'],
  786. '@type' => '@id',
  787. );
  788. }
  789. else {
  790. $response['@context'][$key_adj] = $term['url'];
  791. }
  792. }
  793. else {
  794. $temp[$k] = $v;
  795. }
  796. }
  797. $new_value = $temp;
  798. // Recurse through the values array and set the entity elements
  799. // and add the fields to the context.
  800. tripal_ws_services_v0_1_rewrite_field_items_entity($new_value, $response, $api_url);
  801. }
  802. else {
  803. $new_value = $value !== "" ? $value : NULL;
  804. }
  805. return $new_value;
  806. }
  807. /**
  808. *
  809. */
  810. function tripal_ws_services_v0_1_rewrite_field_items_entity(&$items, &$response, $api_url) {
  811. if (!$items) {
  812. return;
  813. }
  814. foreach ($items as $key => $value) {
  815. if (is_array($value)) {
  816. tripal_ws_services_v0_1_rewrite_field_items_entity($items[$key], $response, $api_url);
  817. continue;
  818. }
  819. if ($key == 'entity') {
  820. list($item_etype, $item_eid) = explode(':', $items['entity']);
  821. if ($item_eid) {
  822. $item_entity = tripal_load_entity($item_etype, array($item_eid));
  823. $item_entity = reset($item_entity);
  824. $bundle = tripal_load_bundle_entity(array('name' => $item_entity->bundle));
  825. $items['@id'] = url($api_url . '/content/' . $bundle->label . '/' . $item_eid, array('absolute' => TRUE));
  826. }
  827. unset($items['entity']);
  828. }
  829. }
  830. }
  831. /**
  832. * Provides the Hydra compatible apiDocumentation page that describes this API.
  833. *
  834. * @param $api_url
  835. * @param $response
  836. */
  837. function tripal_ws_services_v0_1_handle_doc_service($api_url, &$response) {
  838. // First, add the vocabularies used into the @context section.
  839. $response['@context']['rdfs'] = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
  840. $response['@context']['hydra'] = 'http://www.w3.org/ns/hydra/core#';
  841. // Next add in the ID for tihs resource.
  842. $site_name = variable_get('site_name', '');
  843. $response['@id'] = url($api_url . '/doc/', array('absolute' => TRUE));
  844. $response['title'] = $site_name . ": RESTful Web Services API";
  845. $response['entrypoint'] = url($api_url, array('absolute' => TRUE));
  846. $response['description'] = "A fully queryable REST API using JSON-LD and " .
  847. "discoverable using the WC3 Hydra specification.";
  848. // Lastly, add in the terms used into the @context section.
  849. $response['@context']['title'] = 'hydra:title';
  850. $response['@context']['entrypoint'] = array(
  851. "@id" => "hydra:entrypoint",
  852. "@type" => "@id",
  853. );
  854. $response['@context']['description'] = 'hydra:description';
  855. tripal_ws_services_v0_1_write_context($response, $ctype);
  856. }
  857. /**
  858. * This function specifies the types of resources avaiable via the API.
  859. *
  860. * @param $api_url
  861. * @param $response
  862. * @param $ws_path
  863. */
  864. function tripal_ws_services_v0_1_handle_no_service($api_url, &$response) {
  865. // First, add the vocabularies used into the @context section.
  866. $response['@context']['rdfs'] = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
  867. $response['@context']['hydra'] = 'http://www.w3.org/ns/hydra/core#';
  868. $response['@context']['dc'] = 'http://purl.org/dc/dcmitype/';
  869. $response['@context']['schema'] = 'https://schema.org/';
  870. // Next add in the ID for tihs resource.
  871. $response['@id'] = url($api_url, array('absolute' => TRUE));
  872. // Start the list.
  873. $response['@type'] = 'Collection';
  874. $response['totalItems'] = 0;
  875. $response['label'] = 'Services';
  876. $response['member'] = array();
  877. // Start the list.
  878. $response['member'][] = array(
  879. '@id' => url($api_url . '/content/', array('absolute' => TRUE)),
  880. '@type' => 'Service',
  881. 'label' => 'Content Types',
  882. 'description' => 'Provides acesss to the biological and ' .
  883. 'ancilliary data available on this site. Each content type ' .
  884. 'represents biological data that is defined in a controlled vocabulary '.
  885. '(e.g. Sequence Ontology term: gene (SO:0000704)).',
  886. );
  887. $response['member'][] = array(
  888. '@id' => url($api_url . '/doc/', array('absolute' => TRUE)),
  889. '@type' => 'Service',
  890. 'label' => 'API Documentation',
  891. 'description' => 'The WC3 Hydra compatible documentation for this API.',
  892. );
  893. $response['member'][] = array(
  894. '@id' => url($api_url . '/vocab/', array('absolute' => TRUE)),
  895. '@type' => 'Service',
  896. 'label' => 'Vocabulary',
  897. 'description' => 'Defines in-house locally defined vocabulary terms that ' .
  898. 'have been added specifically for this site. These terms are typically ' .
  899. 'added because no other appropriate term exists in another community-vetted '.
  900. 'controlled vocabulary.',
  901. );
  902. $response['totalItems'] = count($response['member']);
  903. $response['@context']['Collection'] = 'hydra:Collection';
  904. $response['@context']['totalItems'] = 'hydra:totalItems';
  905. $response['@context']['member'] = 'hydra:member';
  906. $response['@context']['Service'] = 'dc:Service';
  907. $response['@context']['label'] = 'rdfs:label';
  908. $response['@context']['description'] = 'hydra:description';
  909. }
  910. /**
  911. * Implements hook_tripal_ws_value_alter().
  912. *
  913. * The hook_tripal_ws_value_alter is a hook created by the Tripal WS module.
  914. * It allows the modules to adjust the values of a field for display in
  915. * web services. This hook should be used sparingly. It is meant primarily
  916. * to adjust 3rd Party (non Tripal) fields so that they work with web
  917. * services.
  918. */
  919. function tripal_ws_tripal_ws_value_alter(&$items, $field, $instance) {
  920. // The image module doesn't properly set the 'value' field, so we'll do it
  921. // here.
  922. if($field['type'] == 'image' and $field['module'] == 'image') {
  923. foreach ($items as $delta => $details) {
  924. if ($items[$delta] and array_key_exists('uri', $items[$delta])) {
  925. $items[$delta]['value']['schema:url'] = file_create_url($items[$delta]['uri']);
  926. }
  927. }
  928. }
  929. }