OBOImporter.inc 61 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729
  1. <?php
  2. class OBOImporter extends TripalImporter {
  3. // --------------------------------------------------------------------------
  4. // EDITABLE STATIC CONSTANTS
  5. //
  6. // The following constants SHOULD be set for each descendent class. They are
  7. // used by the static functions to provide information to Drupal about
  8. // the field and it's default widget and formatter.
  9. // --------------------------------------------------------------------------
  10. /**
  11. * The name of this loader. This name will be presented to the site
  12. * user.
  13. */
  14. public static $name = 'OBO Vocabulary Loader';
  15. /**
  16. * The machine name for this loader. This name will be used to construct
  17. * the URL for the loader.
  18. */
  19. public static $machine_name = 'chado_obo_loader';
  20. /**
  21. * A brief description for this loader. This description will be
  22. * presented to the site user.
  23. */
  24. public static $description = 'Import vocabularies and terms in OBO format.';
  25. /**
  26. * An array containing the extensions of allowed file types.
  27. */
  28. public static $file_types = array('obo');
  29. /**
  30. * Provides information to the user about the file upload. Typically this
  31. * may include a description of the file types allowed.
  32. */
  33. public static $upload_description = 'Please provide the details for importing a new OBO file. The file must have a .obo extension.';
  34. /**
  35. * The title that should appear above the upload button.
  36. */
  37. public static $upload_title = 'New OBO File';
  38. /**
  39. * If the loader should require an analysis record. To maintain provenance
  40. * we should always indiate where the data we are uploading comes from.
  41. * The method that Tripal attempts to use for this by associating upload files
  42. * with an analysis record. The analysis record provides the details for
  43. * how the file was created or obtained. Set this to FALSE if the loader
  44. * should not require an analysis when loading. if $use_analysis is set to
  45. * true then the form values will have an 'analysis_id' key in the $form_state
  46. * array on submitted forms.
  47. */
  48. public static $use_analysis = FALSE;
  49. /**
  50. * If the $use_analysis value is set above then this value indicates if the
  51. * analysis should be required.
  52. */
  53. public static $require_analysis = TRUE;
  54. /**
  55. * Text that should appear on the button at the bottom of the importer
  56. * form.
  57. */
  58. public static $button_text = 'Import OBO File';
  59. /**
  60. * Indicates the methods that the file uploader will support.
  61. */
  62. public static $methods = array(
  63. // Allow the user to upload a file to the server.
  64. 'file_upload' => FALSE,
  65. // Allow the user to provide the path on the Tripal server for the file.
  66. 'file_local' => FALSE,
  67. // Allow the user to provide a remote URL for the file.
  68. 'file_remote' => FALSE,
  69. );
  70. /**
  71. * Be default, all loaders are automaticlly added to the Admin >
  72. * Tripal > Data Laders menu. However, if this loader should be
  73. * made available via a different menu path, then set it here. If the
  74. * value is empty then the path will be the default.
  75. */
  76. public static $menu_path = 'admin/tripal/loaders/chado_vocabs/obo_loader';
  77. public static $file_required = FALSE;
  78. /**
  79. * Keep track of vocabularies that have been added.
  80. *
  81. * @var array
  82. */
  83. private $newcvs = array();
  84. /**
  85. * @see TripalImporter::form()
  86. */
  87. public function form($form, &$form_state) {
  88. // get a list of db from chado for user to choose
  89. $sql = "SELECT * FROM {tripal_cv_obo} ORDER BY name";
  90. $results = db_query($sql);
  91. $obos = array();
  92. $obos[] = 'Select a Vocabulary';
  93. foreach ($results as $obo) {
  94. $obos[$obo->obo_id] = $obo->name;
  95. }
  96. $obo_id = '';
  97. if (array_key_exists('values', $form_state)) {
  98. $obo_id = array_key_exists('obo_id', $form_state['values']) ? $form_state['values']['obo_id'] : '';
  99. }
  100. $form['instructions']['info'] = array(
  101. '#type' => 'item',
  102. '#markup' => t('This page allows you to load vocabularies and ontologies
  103. that are in OBO format. Once loaded, the terms from these
  104. vocabularies can be used to create content.
  105. You may use the form below to either reload a vocabulary that is already
  106. loaded (as when new updates to that vocabulary are available) or load a new
  107. vocabulary.'),
  108. );
  109. $form['obo_existing'] = array(
  110. '#type' => 'fieldset',
  111. '#title' => t('Use a Saved Ontology OBO Reference'),
  112. '#prefix' => '<span id="obo-existing-fieldset">',
  113. '#suffix' => '</span>'
  114. );
  115. $form['obo_existing']['existing_instructions']= array(
  116. '#type' => 'item',
  117. '#markup' => t('The vocabularies listed in the select box below have bene pre-populated
  118. upon installation of Tripal or have been previously loaded. Select one to edit
  119. its settings or submit for loading. You may reload any vocabulary that has
  120. already been loaded to retrieve any new updates.'),
  121. );
  122. $form['obo_existing']['obo_id'] = array(
  123. '#title' => t('Ontology OBO File Reference'),
  124. '#type' => 'select',
  125. '#options' => $obos,
  126. '#ajax' => array(
  127. 'callback' => 'tripal_cv_obo_form_ajax_callback',
  128. 'wrapper' => 'obo-existing-fieldset',
  129. ),
  130. '#description' => t('Select a vocabulary to import.')
  131. );
  132. // If the user has selected an OBO ID then get the form elements for
  133. // updating.
  134. if ($obo_id) {
  135. $uobo_name = '';
  136. $uobo_url = '';
  137. $uobo_file = '';
  138. $vocab = db_select('tripal_cv_obo', 't')
  139. ->fields('t', array('name', 'path'))
  140. ->condition('obo_id', $obo_id)
  141. ->execute()
  142. ->fetchObject();
  143. $uobo_name = $vocab->name;
  144. if (preg_match('/^http/', $vocab->path)) {
  145. $uobo_url = $vocab->path;
  146. }
  147. else {
  148. $uobo_file = trim($vocab->path);
  149. $matches = array();
  150. if (preg_match('/\{(.*?)\}/', $uobo_file, $matches)) {
  151. $modpath = drupal_get_path('module', $matches[1]);
  152. $uobo_file = preg_replace('/\{.*?\}/', $modpath, $uobo_file);
  153. }
  154. }
  155. // We don't want the previous value to remain. We want the new default to
  156. // show up, so remove the input values
  157. unset($form_state['input']['uobo_name']);
  158. unset($form_state['input']['uobo_url']);
  159. unset($form_state['input']['uobo_file']);
  160. $form['obo_existing']['uobo_name']= array(
  161. '#type' => 'textfield',
  162. '#title' => t('Vocabulary Name'),
  163. '#description' => t('Please provide a name for this vocabulary. After upload, this name will appear in the drop down
  164. list above for use again later.'),
  165. '#default_value' => $uobo_name,
  166. );
  167. $form['obo_existing']['uobo_url']= array(
  168. '#type' => 'textfield',
  169. '#title' => t('Remote URL'),
  170. '#description' => t('Please enter a URL for the online OBO file. The file will be downloaded and parsed.
  171. (e.g. http://www.obofoundry.org/ro/ro.obo)'),
  172. '#default_value' => $uobo_url,
  173. );
  174. $form['obo_existing']['uobo_file']= array(
  175. '#type' => 'textfield',
  176. '#title' => t('Local File'),
  177. '#description' => t('Please enter the file system path for an OBO
  178. definition file. If entering a path relative to
  179. the Drupal installation you may use a relative path that excludes the
  180. Drupal installation directory (e.g. sites/default/files/xyz.obo). Note
  181. that Drupal relative paths have no preceeding slash.
  182. Otherwise, please provide the full path on the filesystem. The path
  183. must be accessible to the web server on which this Drupal instance is running.'),
  184. '#default_value' => $uobo_file,
  185. );
  186. $form['obo_existing']['update_obo_details'] = array(
  187. '#type' => 'submit',
  188. '#value' => 'Update Ontology Details',
  189. '#name' => 'update_obo_details'
  190. );
  191. }
  192. $form['obo_new'] = array(
  193. '#type' => 'fieldset',
  194. '#title' => t('Add a New Ontology OBO Reference'),
  195. '#collapsible' => TRUE,
  196. '#collapsed' => TRUE,
  197. );
  198. $form['obo_new']['path_instructions']= array(
  199. '#value' => t('Provide the name and path for the OBO file. If the vocabulary OBO file
  200. is stored local to the server provide a file name. If the vocabulry is stored remotely,
  201. provide a URL. Only provide a URL or a local file, not both.'),
  202. );
  203. $form['obo_new']['obo_name']= array(
  204. '#type' => 'textfield',
  205. '#title' => t('New Vocabulary Name'),
  206. '#description' => t('Please provide a name for this vocabulary. After upload, this name will appear in the drop down
  207. list above for use again later. Additionally, if a default namespace is not provided in the OBO
  208. header this name will be used as the default_namespace.'),
  209. );
  210. $form['obo_new']['obo_url']= array(
  211. '#type' => 'textfield',
  212. '#title' => t('Remote URL'),
  213. '#description' => t('Please enter a URL for the online OBO file. The file will be downloaded and parsed.
  214. (e.g. http://www.obofoundry.org/ro/ro.obo)'),
  215. );
  216. $form['obo_new']['obo_file']= array(
  217. '#type' => 'textfield',
  218. '#title' => t('Local File'),
  219. '#description' => t('Please enter the file system path for an OBO
  220. definition file. If entering a path relative to
  221. the Drupal installation you may use a relative path that excludes the
  222. Drupal installation directory (e.g. sites/default/files/xyz.obo). Note
  223. that Drupal relative paths have no preceeding slash.
  224. Otherwise, please provide the full path on the filesystem. The path
  225. must be accessible to the web server on which this Drupal instance is running.'),
  226. );
  227. return $form;
  228. }
  229. /**
  230. * @see TripalImporter::formSubmit()
  231. */
  232. public function formSubmit($form, &$form_state) {
  233. $obo_id = $form_state['values']['obo_id'];
  234. $obo_name = trim($form_state['values']['obo_name']);
  235. $obo_url = trim($form_state['values']['obo_url']);
  236. $obo_file = trim($form_state['values']['obo_file']);
  237. $uobo_name = array_key_exists('uobo_name', $form_state['values']) ? trim($form_state['values']['uobo_name']) : '';
  238. $uobo_url = array_key_exists('uobo_url', $form_state['values']) ? trim($form_state['values']['uobo_url']) : '';
  239. $uobo_file = array_key_exists('uobo_file', $form_state['values']) ? trim($form_state['values']['uobo_file']) : '';
  240. // If the user requested to alter the details then do that.
  241. if ($form_state['clicked_button']['#name'] == 'update_obo_details') {
  242. $form_state['rebuild'] = TRUE;
  243. $success = db_update('tripal_cv_obo')
  244. ->fields(array(
  245. 'name' => $uobo_name,
  246. 'path' => $uobo_url ? $uobo_url : $uobo_file,
  247. ))
  248. ->condition('obo_id', $obo_id)
  249. ->execute();
  250. if ($success) {
  251. drupal_set_message(t("The vocabulary !vocab has been updated.", array('!vocab' => $uobo_name)));
  252. }
  253. else {
  254. drupal_set_message(t("The vocabulary !vocab could not be updated.", array('!vocab' => $uobo_name)), 'error');
  255. }
  256. }
  257. elseif (!empty($obo_name)) {
  258. $obo_id = db_insert('tripal_cv_obo')
  259. ->fields(array(
  260. 'name' => $obo_name,
  261. 'path' => $obo_url ? $obo_url : $obo_file,
  262. ))
  263. ->execute();
  264. // Add the obo_id to the form_state vaules.
  265. $form_state['values']['obo_id'] = $obo_id;
  266. if ($obo_id) {
  267. drupal_set_message(t("The vocabulary !vocab has been added.", array('!vocab' => $obo_name)));
  268. }
  269. else {
  270. $form_state['rebuild'] = TRUE;
  271. drupal_set_message(t("The vocabulary !vocab could not be added.", array('!vocab' => $obo_name)), 'error');
  272. }
  273. }
  274. }
  275. /**
  276. * @see TripalImporter::formValidate()
  277. */
  278. public function formValidate($form, &$form_state) {
  279. $obo_id = $form_state['values']['obo_id'];
  280. $obo_name = trim($form_state['values']['obo_name']);
  281. $obo_url = trim($form_state['values']['obo_url']);
  282. $obo_file = trim($form_state['values']['obo_file']);
  283. $uobo_name = array_key_exists('uobo_name', $form_state['values']) ? trim($form_state['values']['uobo_name']) : '';
  284. $uobo_url = array_key_exists('uobo_url', $form_state['values']) ? trim($form_state['values']['uobo_url']) : '';
  285. $uobo_file = array_key_exists('uobo_file', $form_state['values']) ? trim($form_state['values']['uobo_file']) : '';
  286. // Make sure if the name is changed it doesn't conflict with another OBO.
  287. if ($form_state['clicked_button']['#name'] == 'update_obo_details' or
  288. $form_state['clicked_button']['#name'] == 'update_load_obo') {
  289. // Get the current record
  290. $vocab = db_select('tripal_cv_obo', 't')
  291. ->fields('t', array('obo_id', 'name', 'path'))
  292. ->condition('name', $uobo_name)
  293. ->execute()
  294. ->fetchObject();
  295. if ($vocab and $vocab->obo_id != $obo_id) {
  296. form_set_error('uobo_name', 'The vocabulary name must be different from existing vocabularies');
  297. }
  298. // Make sure the file exists. First check if it is a relative path
  299. $dfile = $_SERVER['DOCUMENT_ROOT'] . base_path() . $uobo_file;
  300. if (!file_exists($dfile)) {
  301. if (!file_exists($uobo_file)) {
  302. form_set_error('uobo_file', 'The specified path does not exist or cannot be read.');
  303. }
  304. }
  305. if (!$uobo_url and !$uobo_file) {
  306. form_set_error('uobo_url', 'Please provide a URL or a path for the vocabulary.');
  307. }
  308. if ($uobo_url and $uobo_file) {
  309. form_set_error('uobo_url', 'Please provide only a URL or a path for the vocabulary, but not both.');
  310. }
  311. }
  312. if ($form_state['clicked_button']['#name'] == 'add_new_obo') {
  313. // Get the current record
  314. $vocab = db_select('tripal_cv_obo', 't')
  315. ->fields('t', array('obo_id', 'name', 'path'))
  316. ->condition('name', $obo_name)
  317. ->execute()
  318. ->fetchObject();
  319. if ($vocab) {
  320. form_set_error('obo_name', 'The vocabulary name must be different from existing vocabularies');
  321. }
  322. // Make sure the file exists. First check if it is a relative path
  323. $dfile = $_SERVER['DOCUMENT_ROOT'] . base_path() . $obo_file;
  324. if (!file_exists($dfile)) {
  325. if (!file_exists($obo_file)) {
  326. form_set_error('obo_file', 'The specified path does not exist or cannot be read.');
  327. }
  328. }
  329. if (!$obo_url and !$obo_file) {
  330. form_set_error('obo_url', 'Please provide a URL or a path for the vocabulary.');
  331. }
  332. if ($obo_url and $obo_file) {
  333. form_set_error('obo_url', 'Please provide only a URL or a path for the vocabulary, but not both.');
  334. }
  335. }
  336. }
  337. /**
  338. * @see TripalImporter::run()
  339. *
  340. * @param $details
  341. * The following arguments are supported:
  342. * - obo_id: (required) The ID of the ontology to be imported.
  343. */
  344. public function run() {
  345. $arguments = $this->arguments['run_args'];
  346. $obo_id = $arguments['obo_id'];
  347. // Make sure the $obo_id is valid
  348. $obo = db_select('tripal_cv_obo', 'tco')
  349. ->fields('tco')
  350. ->condition('obo_id', $obo_id)
  351. ->execute()
  352. ->fetchObject();
  353. if (!$obo) {
  354. throw new Exception("Invalid OBO ID provided: '$obo_id'.");
  355. }
  356. $this->loadOBO_v1_2_id($obo);
  357. }
  358. /**
  359. * @see TripalImporter::postRun()
  360. *
  361. */
  362. public function postRun() {
  363. // Update the cv_root_mview materiailzed view.
  364. $this->logMessage("Updating the cv_root_mview materialized view...");
  365. $mview_id = tripal_get_mview_id('cv_root_mview');
  366. tripal_populate_mview($mview_id);
  367. $this->logMessage("Updating the db2cv_mview materialized view...");
  368. $mview_id = tripal_get_mview_id('db2cv_mview');
  369. tripal_populate_mview($mview_id);
  370. // Upate the cvtermpath table for each newly added CV.
  371. $this->logMessage("Updating cvtermpath table. This may take a while...");
  372. foreach ($this->newcvs as $namespace => $cvid) {
  373. $this->logMessage("- Loading paths for @vocab", array('@vocab' => $namespace));
  374. tripal_update_cvtermpath($cvid);
  375. }
  376. }
  377. /**
  378. * A wrapper function for importing the user specified OBO file into Chado by
  379. * specifying the obo_id of the OBO. It requires that the file be in OBO v1.2
  380. * compatible format. This function is typically executed via the Tripal jobs
  381. * management after a user submits a job via the Load Onotloies form.
  382. *
  383. * @param $obo_id
  384. * An obo_id from the tripal_cv_obo file that specifies which OBO file to import
  385. * @ingroup tripal_obo_loader
  386. */
  387. private function loadOBO_v1_2_id($obo) {
  388. // Convert the module name to the real path if present
  389. if (preg_match("/\{(.*?)\}/", $obo->path, $matches)) {
  390. $module = $matches[1];
  391. $path = drupal_realpath(drupal_get_path('module', $module));
  392. $obo->path = preg_replace("/\{.*?\}/", $path, $obo->path);
  393. }
  394. // if the reference is for a remote URL then run the URL processing function
  395. if (preg_match("/^https:\/\//", $obo->path) or
  396. preg_match("/^http:\/\//", $obo->path) or
  397. preg_match("/^ftp:\/\//", $obo->path)) {
  398. $this->loadOBO_v1_2_url($obo->name, $obo->path, 0);
  399. }
  400. // if the reference is for a local file then run the file processing function
  401. else {
  402. // check to see if the file is located local to Drupal
  403. $dfile = $_SERVER['DOCUMENT_ROOT'] . base_path() . $obo->path;
  404. if (file_exists($dfile)) {
  405. $this->loadOBO_v1_2_file($obo->name, $dfile, 0);
  406. }
  407. // if not local to Drupal, the file must be someplace else, just use
  408. // the full path provided
  409. else {
  410. if (file_exists($obo->path)) {
  411. $this->loadOBO_v1_2_file($obo->name, $obo->path, 0);
  412. }
  413. else {
  414. print "ERROR: could not find OBO file: '$obo->path'\n";
  415. }
  416. }
  417. }
  418. }
  419. /**
  420. * A wrapper function for importing the user specified OBO file into Chado by
  421. * specifying the filename and path of the OBO. It requires that the file be in OBO v1.2
  422. * compatible format. This function is typically executed via the Tripal jobs
  423. * management after a user submits a job via the Load Onotloies form.
  424. *
  425. * @param $obo_name
  426. * The name of the OBO (typially the ontology or controlled vocabulary name)
  427. * @param $file
  428. * The path on the file system where the ontology can be found
  429. * @param $is_new
  430. * Set to TRUE if this is a new ontology that does not yet exist in the
  431. * tripal_cv_obo table. If TRUE the OBO will be added to the table.
  432. *
  433. * @ingroup tripal_obo_loader
  434. */
  435. private function loadOBO_v1_2_file($obo_name, $file, $is_new = TRUE) {
  436. if ($is_new) {
  437. tripal_insert_obo($obo_name, $file);
  438. }
  439. $success = $this->loadOBO_v1_2($file, $obo_name);
  440. }
  441. /**
  442. * A wrapper function for importing the user specified OBO file into Chado by
  443. * specifying the remote URL of the OBO. It requires that the file be in OBO v1.2
  444. * compatible format. This function is typically executed via the Tripal jobs
  445. * management after a user submits a job via the Load Onotloies form.
  446. *
  447. * @param $obo_name
  448. * The name of the OBO (typially the ontology or controlled vocabulary name)
  449. * @param $url
  450. * The remote URL of the OBO file.
  451. * @param $is_new
  452. * Set to TRUE if this is a new ontology that does not yet exist in the
  453. * tripal_cv_obo table. If TRUE the OBO will be added to the table.
  454. *
  455. * @ingroup tripal_obo_loader
  456. */
  457. private function loadOBO_v1_2_url($obo_name, $url, $is_new = TRUE) {
  458. // first download the OBO
  459. $temp = tempnam(sys_get_temp_dir(), 'obo_');
  460. print "Downloading URL $url, saving to $temp\n";
  461. $url_fh = fopen($url, "r");
  462. $obo_fh = fopen($temp, "w");
  463. if (!$url_fh) {
  464. throw new Exception("Unable to download the remote OBO file at $url. Could a firewall be blocking outgoing connections? " .
  465. " if you are unable to download the file you may manually downlod the OBO file and use the web interface to " .
  466. " specify the location of the file on your server.");
  467. }
  468. while (!feof($url_fh)) {
  469. fwrite($obo_fh, fread($url_fh, 255), 255);
  470. }
  471. fclose($url_fh);
  472. fclose($obo_fh);
  473. if ($is_new) {
  474. tripal_insert_obo($obo_name, $url);
  475. }
  476. // second, parse the OBO
  477. $this->loadOBO_v1_2($temp, $obo_name);
  478. // now remove the temp file
  479. unlink($temp);
  480. }
  481. /**
  482. * Imports a given OBO file into Chado. This function is usually called by
  483. * one of three wrapper functions: loadOBO_v1_2_id,
  484. * loadOBO_v1_2_file or tirpal_cv_load_obo_v1_2_url. But, it can
  485. * be called directly if the full path to an OBO file is available on the
  486. * file system.
  487. *
  488. * @param $flie
  489. * The full path to the OBO file on the file system
  490. *
  491. * @ingroup tripal_obo_loader
  492. */
  493. private function loadOBO_v1_2($file, $obo_name) {
  494. $header = array();
  495. $ret = array();
  496. // Empty the temp table.
  497. $sql = "DELETE FROM {tripal_obo_temp}";
  498. chado_query($sql);
  499. $this->logMessage("Step 1: Preloading File $file...");
  500. // parse the obo file
  501. $default_db = $this->parse($file, $header);
  502. // Add the CV for this ontology to the database. The v1.2 definition
  503. // specifies a 'default-namespace' to be used if a 'namespace' is not
  504. // present for each stanza. Some ontologies have adopted the v1.4 method
  505. // in their v1.2 files and not including it.
  506. if (array_key_exists('default-namespace', $header)) {
  507. $defaultcv = tripal_insert_cv($header['default-namespace'][0], '');
  508. if (!$defaultcv) {
  509. throw new Exception('Cannot add namespace ' . $header['default-namespace'][0]);
  510. }
  511. $this->newcvs[$header['default-namespace'][0]] = $defaultcv->cv_id;
  512. }
  513. // If the 'default-namespace' is missing.
  514. else {
  515. // Grab the first term accession from the file and get the short name for the cv
  516. $fh = fopen($file, 'r');
  517. while ($line = fgets($fh)) {
  518. // Grab the first item's id info to break apart.
  519. if (preg_match('/^\s*\[/', $line)) {
  520. $stanza = TRUE;
  521. continue;
  522. }
  523. if ($stanza === TRUE && (substr($line, 0, 3) === "id:")) {
  524. $parts = explode(':', $line, 2);
  525. $short_name = strtolower($parts[1]);
  526. $short_name = preg_replace('/\s+/', '', $short_name);
  527. break;
  528. }
  529. }
  530. fclose($fh);
  531. // Check if the EBI ontology search has this ontology:
  532. try {
  533. $results = $this->oboEbiLookup($short_name, 'ontology');
  534. if (array_key_exists('default-namespace', $results['config']['annotations'])) {
  535. $results = $results['config']['annotations']['default-namespace'];
  536. if (is_array($results)) {
  537. $results = $results[0];
  538. }
  539. }
  540. elseif (array_key_exists('namespace', $results['config'])) {
  541. $results = $results['config']['namespace'];
  542. }
  543. else {
  544. $results = $short_name;
  545. }
  546. print_r('$results');
  547. print_r($results);
  548. $defaultcv = tripal_insert_cv(strtoupper($results), '');
  549. $this->newcvs[$defaultcv->name] = $defaultcv->cv_id;
  550. }
  551. catch (Exception $e) {
  552. watchdog_exception('OBOImporter no such accession found in EBI Ontology Search', $e);
  553. }
  554. if (empty($defaultcv)) {
  555. throw new Exception("Could not find a namespace for this OBO file: $file");
  556. }
  557. $this->logMessage("This OBO is missing the 'default-namespace' header. It " .
  558. "is not possible to determine which vocabulary terms without a 'namespace' key " .
  559. "should go. Instead, those terms will be placed in the '!vocab' vocabulary.",
  560. array('!vocab' => $defaultcv->name), TRIPAL_WARNING);
  561. }
  562. // Add any typedefs to the vocabulary first.
  563. $this->logMessage("Step 2: Loading type defs...");
  564. $this->loadTypeDefs($defaultcv, $default_db);
  565. // Next add terms to the vocabulary.
  566. $this->logMessage("Step 3: Loading terms...");
  567. if (!$this->processTerms($defaultcv, $default_db)) {
  568. throw new Exception('Cannot add terms from this ontology');
  569. }
  570. // Empty the temp table.
  571. $sql = "DELETE FROM {tripal_obo_temp}";
  572. chado_query($sql);
  573. }
  574. /**
  575. * OBO files are divided into a typedefs terms section and vocabulary terms section.
  576. * This function loads the typedef terms from the OBO.
  577. *
  578. * @param $defaultcv
  579. * A database object containing a record from the cv table for the
  580. * default controlled vocabulary
  581. * @param $default_db
  582. * The name of the default database.
  583. *
  584. * @ingroup tripal_obo_loader
  585. */
  586. private function loadTypeDefs($defaultcv, $default_db) {
  587. $sql = "SELECT * FROM {tripal_obo_temp} WHERE type = 'Typedef' ";
  588. $typedefs = chado_query($sql);
  589. $sql = "
  590. SELECT count(*) as num_terms
  591. FROM {tripal_obo_temp}
  592. WHERE type = 'Typedef'
  593. ";
  594. $result = chado_query($sql)->fetchObject();
  595. $count = $result->num_terms;
  596. $this->setTotalItems($count);
  597. $this->setItemsHandled(0);
  598. $i = 0;
  599. foreach ($typedefs as $typedef) {
  600. $this->setItemsHandled($i);
  601. $term = unserialize(base64_decode($typedef->stanza));
  602. $this->processTerm($term, $defaultcv->name, 1, $default_db);
  603. $i++;
  604. }
  605. $this->setItemsHandled($i);
  606. return 1;
  607. }
  608. /**
  609. * OBO files are divided into a typedefs section and a terms section.
  610. *
  611. * This function loads the typedef terms from the OBO.
  612. *
  613. * @param $defaultcv
  614. * A database object containing a record from the cv table for the
  615. * default controlled vocabulary
  616. * @param $default_db
  617. * The name of the default database.
  618. */
  619. private function processTerms($defaultcv, $default_db) {
  620. $i = 0;
  621. $external = FALSE;
  622. // Iterate through each term from the OBO file and add it.
  623. $sql = "
  624. SELECT * FROM {tripal_obo_temp}
  625. WHERE type = 'Term'
  626. ORDER BY id
  627. ";
  628. $terms = chado_query($sql);
  629. $sql = "
  630. SELECT count(*) as num_terms
  631. FROM {tripal_obo_temp}
  632. WHERE type = 'Term'
  633. ";
  634. $result = chado_query($sql)->fetchObject();
  635. $count = $result->num_terms;
  636. $this->setTotalItems($count);
  637. $this->setItemsHandled(0);
  638. // Iterate through the terms.
  639. foreach ($terms as $t) {
  640. $term = unserialize(base64_decode($t->stanza));
  641. $this->setItemsHandled($i);
  642. // Add/update this term.
  643. $status = $this->processTerm($term, $defaultcv->name, 0, $default_db);
  644. if (!$status) {
  645. throw new Exception("Failed to process terms from the ontology");
  646. }
  647. else if ($status === 2 && $external == FALSE) {
  648. $this->logMessage(
  649. "A term that belongs to another ontology is used within this " .
  650. "vocabulary. Therefore a lookup was performed with the EBI Ontology " .
  651. "Lookup Service to retrieve the information for this term. " .
  652. "Please note, that vocabularies with many non-local terms " .
  653. "require remote lookups and these lookups can dramatically " .
  654. "decrease loading time. " ,
  655. array('!vocab' => $defaultcv->name),
  656. TRIPAL_WARNING
  657. );
  658. $external = TRUE;
  659. }
  660. $i++;
  661. }
  662. $this->setItemsHandled($i);
  663. return 1;
  664. }
  665. /**
  666. * Uses the provided term array to add/update information to Chado about the
  667. * term including the term, dbxref, synonyms, properties, and relationships.
  668. *
  669. * @param $term
  670. * An array representing the cvterm.
  671. * @param $defaultcv
  672. * The name of the default controlled vocabulary
  673. * @is_relationship
  674. * Set to 1 if this term is a relationship term
  675. * @default_db
  676. * The name of the default database.
  677. *
  678. * @ingroup tripal_obo_loader
  679. */
  680. private function processTerm($term, $defaultcv, $is_relationship = 0, $default_db) {
  681. // make sure we have a namespace for this term
  682. if (!array_key_exists('namespace', $term) and !($defaultcv or $defaultcv == '')) {
  683. throw new Exception("Cannot add the term: no namespace defined. " . $term['id'][0]);
  684. }
  685. // construct the term array for sending to the tripal_chado_add_cvterm function
  686. // for adding a new cvterm
  687. $t = array();
  688. $t['id'] = $term['id'][0];
  689. $t['name'] = $term['name'][0];
  690. if (array_key_exists('def', $term)) {
  691. $t['definition'] = $term['def'][0];
  692. }
  693. if (array_key_exists('subset', $term)) {
  694. $t['subset'] = $term['subset'][0];
  695. }
  696. if (array_key_exists('namespace', $term)) {
  697. $t['namespace'] = $term['namespace'][0];
  698. }
  699. if (array_key_exists('is_obsolete', $term)) {
  700. $t['is_obsolete'] = $term['is_obsolete'][0];
  701. }
  702. // Check the id isn't a reference to another term.
  703. //TODO: Check chado for the accession, so we can avoid lookups where possible.
  704. if (strpos($t['id'], ':')) {
  705. $pair = explode(":", $t['id'], 2);
  706. $ontology_id = $pair[0];
  707. $accession_num = $pair[1];
  708. if (is_numeric($accession_num) && ($ontology_id != $default_db)) {
  709. // Check that the term isn't already in Chado.
  710. $results = $this->oboEbiLookup($t['id'], 'term');
  711. if (isset($results['label'])) {
  712. $t['name'] = $results['label'];
  713. $defaultcv = $results['ontology_name'];
  714. $default_db = $results['ontology_prefix'];
  715. $external = TRUE;
  716. }
  717. if (!isset($results['label'])) {
  718. $results = $this->oboEbiLookup($t['id'], 'query');
  719. if (array_key_exists('docs', $results)) {
  720. if (!empty($results['response']['docs'])) {
  721. if (count($results['response']['docs']) > 1) {
  722. foreach ($results['response']['docs'] as $doc) {
  723. if ($doc['obo_id'] == $t['id']) {
  724. $t['name'] = $doc['label'];
  725. $defaultcv = $doc['ontology_name'];
  726. $default_db = $doc['ontology_prefix'];
  727. $external = true;
  728. }
  729. }
  730. } else {
  731. $t['name'] = $results['response']['docs'][0]['label'];
  732. $defaultcv = $results['response']['docs'][0]['ontology_name'];
  733. $default_db = $results['response']['docs'][0]['ontology_prefix'];
  734. $external = true;
  735. }
  736. }
  737. }
  738. }
  739. if ($results['response']['numFound'] == 0 && !isset($results['label'])) {
  740. // The first search doesn't work, so let's try a broader one.
  741. $results = $this->oboEbiLookup($t['id'], 'query-non-local');
  742. if (!empty($results)) {
  743. if (array_key_exists('docs', $results)) {
  744. if (!empty($results['docs'])) {
  745. $accession = $t['id'];
  746. $accession_underscore = str_replace(":", "_", $accession);
  747. foreach ($results['response']['docs'] as $item) {
  748. if ($item['label'] != $accession && $item['label'] != $accession_underscore) {
  749. //Found the first place a label is other than the accession is used, so take
  750. // that info and then end the loop.
  751. $t['name'] = $item['label'];
  752. $defaultcv = $item['ontology_name'];
  753. $default_db = $item['ontology_prefix'];
  754. $external = true;
  755. break;
  756. }
  757. }
  758. }
  759. }
  760. }
  761. }
  762. }
  763. }
  764. // Check that the default_cv is in the cv table.
  765. $sql = "
  766. SELECT CV.name
  767. FROM {cv} CV
  768. WHERE CV.name = '$defaultcv'
  769. ";
  770. $results = chado_query($sql)->fetchObject();
  771. if (!$results){
  772. //The controlled vocabulary is not in the cv term table and needs to be added.
  773. $ontology_info = $this->oboEbiLookup($defaultcv, 'ontology');
  774. if (!empty($ontology_info['config'])){
  775. // CV Name.
  776. if (array_key_exists('namespace', $ontology_info['config'])) {
  777. $cv_info = $ontology_info['config']['namespace'];
  778. }
  779. elseif (array_key_exists('default-namespace', $ontology_info['config']['annotations'])) {
  780. $cv_info = $ontology_info['config']['annotations']['default-namespace'];
  781. }
  782. //CV Description.
  783. if (array_key_exists('description', $ontology_info['config'])) {
  784. $description = $ontology_info['config']['description'];
  785. }
  786. else {
  787. $description = '';
  788. }
  789. $cv_returned = chado_insert_cv($cv_info, $description);
  790. if($cv_returned) {
  791. $defaultcv = $cv_returned->name;
  792. // Now add the db entry.
  793. $values = array(
  794. 'name' => $ontology_info['config']['preferredPrefix'],
  795. 'description' => $ontology_info['config']['description'],
  796. 'url' => $ontology_info['config']['versionIri'],
  797. );
  798. $db_returned = chado_insert_db($values);
  799. if ($db_returned) {
  800. $default_db = $db_returned->name;
  801. }
  802. }
  803. }
  804. }
  805. $t['cv_name'] = $defaultcv;
  806. $t['is_relationship'] = $is_relationship;
  807. $t['db_name'] = $default_db;
  808. // The name being empty regularly causes problems, so let's check it's there.
  809. if (empty($t['name'])){
  810. $results = $this->oboEbiLookup($t['id'], 'term');
  811. if (isset($results['label'])) {
  812. $t['name'] = $results['label'];
  813. }
  814. }
  815. $cvterm = tripal_insert_cvterm($t, array('update_existing' => TRUE));
  816. if (!$cvterm) {
  817. throw new Exception("Cannot add the term " . $term['id'][0]);
  818. }
  819. // Remove any relationships that this term already has (in case it was
  820. // updated) and we'll re-add them.
  821. $sql = "
  822. DELETE FROM {cvterm_relationship} CVTR
  823. WHERE CVTR.subject_id = :cvterm_id
  824. ";
  825. chado_query($sql, array(':cvterm_id' => $cvterm->cvterm_id));
  826. if (array_key_exists('namespace', $term)) {
  827. $this->newcvs[$term['namespace'][0]] = $cvterm->cv_id;
  828. }
  829. // now handle other properites
  830. if (array_key_exists('is_anonymous', $term)) {
  831. //print "WARNING: unhandled tag: is_anonymous\n";
  832. }
  833. if (array_key_exists('alt_id', $term)) {
  834. foreach ($term['alt_id'] as $alt_id) {
  835. if (!$this->addCvtermDbxref($cvterm, $alt_id)) {
  836. throw new Exception("Cannot add alternate id $alt_id");
  837. }
  838. }
  839. }
  840. if (array_key_exists('subset', $term)) {
  841. //print "WARNING: unhandled tag: subset\n";
  842. }
  843. // add synonyms for this cvterm
  844. if (array_key_exists('synonym', $term)) {
  845. if (!$this->addSynonym($term, $cvterm)) {
  846. throw new Exception("Cannot add synonyms");
  847. }
  848. }
  849. // reformat the deprecated 'exact_synonym, narrow_synonym, and broad_synonym'
  850. // types to be of the v1.2 standard
  851. if (array_key_exists('exact_synonym', $term) or array_key_exists('narrow_synonym', $term) or array_key_exists('broad_synonym', $term)) {
  852. if (array_key_exists('exact_synonym', $term)) {
  853. foreach ($term['exact_synonym'] as $synonym) {
  854. $new = preg_replace('/^\s*(\".+?\")(.*?)$/', '$1 EXACT $2', $synonym);
  855. $term['synonym'][] = $new;
  856. }
  857. }
  858. if (array_key_exists('narrow_synonym', $term)) {
  859. foreach ($term['narrow_synonym'] as $synonym) {
  860. $new = preg_replace('/^\s*(\".+?\")(.*?)$/', '$1 NARROW $2', $synonym);
  861. $term['synonym'][] = $new;
  862. }
  863. }
  864. if (array_key_exists('broad_synonym', $term)) {
  865. foreach ($term['broad_synonym'] as $synonym) {
  866. $new = preg_replace('/^\s*(\".+?\")(.*?)$/', '$1 BROAD $2', $synonym);
  867. $term['synonym'][] = $new;
  868. }
  869. }
  870. if (!$this->addSynonym($term, $cvterm)) {
  871. throw new Exception("Cannot add/update synonyms");
  872. }
  873. }
  874. // add the comment to the cvtermprop table
  875. if (array_key_exists('comment', $term)) {
  876. $comments = $term['comment'];
  877. $j = 0;
  878. foreach ($comments as $comment) {
  879. if (!$this->addCvtermProp($cvterm, 'comment', $comment, $j)) {
  880. throw new Exception("Cannot add/update cvterm property");
  881. }
  882. $j++;
  883. }
  884. }
  885. // add any other external dbxrefs
  886. if (array_key_exists('xref', $term)) {
  887. foreach ($term['xref'] as $xref) {
  888. if (!$this->addCvtermDbxref($cvterm, $xref)) {
  889. throw new Exception("Cannot add/update cvterm database reference (dbxref).");
  890. }
  891. }
  892. }
  893. if (array_key_exists('xref_analog', $term)) {
  894. foreach ($term['xref_analog'] as $xref) {
  895. if (!$this->addCvtermDbxref($cvterm, $xref)) {
  896. throw new Exception("Cannot add/update cvterm database reference (dbxref).");
  897. }
  898. }
  899. }
  900. if (array_key_exists('xref_unk', $term)) {
  901. foreach ($term['xref_unk'] as $xref) {
  902. if (!$this->addCvtermDbxref($cvterm, $xref)) {
  903. throw new Exception("Cannot add/update cvterm database reference (dbxref).");
  904. }
  905. }
  906. }
  907. // add is_a relationships for this cvterm
  908. if (array_key_exists('is_a', $term)) {
  909. foreach ($term['is_a'] as $is_a) {
  910. if (!$this->addRelationship($cvterm, $defaultcv, 'is_a', $is_a, $is_relationship, $default_db)) {
  911. throw new Exception("Cannot add relationship is_a: $is_a");
  912. }
  913. }
  914. }
  915. if (array_key_exists('intersection_of', $term)) {
  916. //print "WARNING: unhandled tag: intersection_of\n";
  917. }
  918. if (array_key_exists('union_of', $term)) {
  919. //print "WARNING: unhandled tag: union_on\n";
  920. }
  921. if (array_key_exists('disjoint_from', $term)) {
  922. //print "WARNING: unhandled tag: disjoint_from\n";
  923. }
  924. if (array_key_exists('relationship', $term)) {
  925. foreach ($term['relationship'] as $value) {
  926. $rel = preg_replace('/^(.+?)\s.+?$/', '\1', $value);
  927. $object = preg_replace('/^.+?\s(.+?)$/', '\1', $value);
  928. if (!$this->addRelationship($cvterm, $defaultcv, $rel, $object, $is_relationship, $default_db)) {
  929. throw new Exception("Cannot add relationship $rel: $object");
  930. }
  931. }
  932. }
  933. if (array_key_exists('replaced_by', $term)) {
  934. //print "WARNING: unhandled tag: replaced_by\n";
  935. }
  936. if (array_key_exists('consider', $term)) {
  937. //print "WARNING: unhandled tag: consider\n";
  938. }
  939. if (array_key_exists('use_term', $term)) {
  940. //print "WARNING: unhandled tag: user_term\n";
  941. }
  942. if (array_key_exists('builtin', $term)) {
  943. //print "WARNING: unhandled tag: builtin\n";
  944. }
  945. if ($external ) {
  946. return 2;
  947. }
  948. return 1;
  949. }
  950. /**
  951. * Adds a cvterm relationship
  952. *
  953. * @param $cvterm
  954. * A database object for the cvterm
  955. * @param $rel
  956. * The relationship name
  957. * @param $objname
  958. * The relationship term name
  959. * @param $defaultcv
  960. * A database object containing a record from the cv table for the
  961. * default controlled vocabulary
  962. * @object_is_relationship
  963. * Set to 1 if this term is a relationship term
  964. * @default_db
  965. * The name of the default database.
  966. *
  967. * @ingroup tripal_obo_loader
  968. */
  969. private function addRelationship($cvterm, $defaultcv, $rel,
  970. $objname, $object_is_relationship = 0, $default_db = 'OBO_REL') {
  971. $reference_term = FALSE;
  972. $in_obo = $this->getTerm($objname);
  973. // If an accession was passed we need to see if we can find the actual label.
  974. if (strpos($rel, ':') || strpos($objname, ':') && empty($in_obo['name'])) {
  975. if (strpos($rel, ':')) {
  976. $term_id = $rel;
  977. }
  978. elseif (strpos($objname, ':')) {
  979. $term_id = $objname;
  980. }
  981. $reference_term = TRUE;
  982. $pair = explode(":", $term_id, 2);
  983. $ontology_id = $pair[0];
  984. $accession_num = $pair[1];
  985. $rel_name = '';
  986. if (is_numeric($accession_num)) {
  987. // Before running the oboEbiLookup check for it in the local chado and
  988. // the tripal_obo_temp table.
  989. // $term_with_quotes = '"' . $term_id . '"';
  990. $sql = "
  991. SELECT *
  992. FROM {tripal_obo_temp} tot
  993. WHERE tot.id = :term_id
  994. ";
  995. $result = chado_query($sql, array(':term_id' => $term_id . '_lookup'))->fetchObject();
  996. $oterm = unserialize(base64_decode($result->stanza));
  997. if (empty($oterm['label'])){
  998. // Is this ontology is in the local chado?
  999. $sql = "SELECT * FROM {db} db WHERE db.name = :ontology_id ";
  1000. $db = chado_query($sql, array(':ontology_id' => $ontology_id, ))->fetchObject();
  1001. if (!empty($db)){
  1002. // Find the accession.
  1003. $sql = "
  1004. SELECT *
  1005. FROM {dbxref} dbx
  1006. WHERE dbx.db_id = :db_id
  1007. AND accession = :accession_num ";
  1008. $v = chado_query($sql, array(':db_id' => $db->db_id, ':accession_num' => $accession_num))->fetchObject();
  1009. if (!empty($dbxref)) {
  1010. $sql = "SELECT * FROM {cvterm} WHERE dbxref_id = $dbxref->dbxref_id ";
  1011. $oterm = chado_query($sql)->fetchObject();
  1012. $rel_name = $oterm->name;
  1013. }
  1014. }
  1015. if (empty($dbxref)){
  1016. $results = $this->oboEbiLookup($term_id, 'term');
  1017. if (isset($results['label'])) {
  1018. $rel_name = $results['label'];
  1019. $oterm = $results;
  1020. }
  1021. if (empty($rel_name)) {
  1022. $results = $this->oboEbiLookup($term_id, 'query');
  1023. if (array_key_exists('docs', $results['response'])) {
  1024. if (!empty($results['response']['docs'])) {
  1025. if (count($results['response']['docs']) > 1) {
  1026. foreach ($results['response']['docs'] as $doc) {
  1027. if ($doc['obo_id'] == $term_id) {
  1028. $rel_name = $doc['label'];
  1029. $oterm = $doc;
  1030. }
  1031. }
  1032. } else {
  1033. $rel_name = $results['response']['docs'][0]['label'];
  1034. $oterm = $results['response']['docs'][0];
  1035. }
  1036. }
  1037. }
  1038. }
  1039. if (empty($rel_name)) {
  1040. // The first search doesn't work, so let's try a broader one.
  1041. $results = $this->oboEbiLookup($term_id, 'query-non-local');
  1042. if (!empty($results)) {
  1043. if (array_key_exists('docs', $results['response'])) {
  1044. if (!empty($results['response']['docs'])) {
  1045. foreach ($results['response']['docs'] as $item) {
  1046. if ($item['obo_id'] == $term_id) {
  1047. //Found the first place a label is other than the accession is used, so take
  1048. // that info and then end the loop.
  1049. $rel_name = $item['label'];
  1050. $oterm = $item;
  1051. break;
  1052. }
  1053. }
  1054. }
  1055. }
  1056. }
  1057. }
  1058. }
  1059. // Write the term to the tripal_obo_temp table for future reference
  1060. $values = array(
  1061. 'id' => $term_id . '_lookup',
  1062. 'stanza' => base64_encode(serialize($oterm)),
  1063. 'type' => 'lookup',
  1064. );
  1065. chado_insert_record('tripal_obo_temp', $values);
  1066. }
  1067. }
  1068. }
  1069. // Make sure the relationship cvterm exists.
  1070. $term = array(
  1071. 'name' => $rel,
  1072. 'id' => "$default_db:$rel",
  1073. 'definition' => '',
  1074. 'is_obsolete' => 0,
  1075. 'cv_name' => $defaultcv,
  1076. 'is_relationship' => TRUE,
  1077. 'db_name' => $default_db
  1078. );
  1079. $relcvterm = tripal_insert_cvterm($term, array('update_existing' => FALSE));
  1080. if (!$relcvterm) {
  1081. // If the relationship term couldn't be found in the default_db provided
  1082. // then do on more check to find it in the relationship ontology
  1083. $term = array(
  1084. 'name' => $rel,
  1085. 'id' => "OBO_REL:$rel",
  1086. 'definition' => '',
  1087. 'is_obsolete' => 0,
  1088. 'cv_name' => $defaultcv,
  1089. 'is_relationship' => TRUE,
  1090. 'db_name' => 'OBO_REL'
  1091. );
  1092. $relcvterm = tripal_insert_cvterm($term, array('update_existing' => FALSE));
  1093. if (!$relcvterm) {
  1094. throw new Exception("Cannot find the relationship term in the current ontology or in the relationship ontology: $rel\n");
  1095. }
  1096. }
  1097. // Get the object term.
  1098. if ($reference_term === TRUE && !empty($oterm)) {
  1099. $objterm = array();
  1100. $objterm['id'] = $oterm['obo_id'];
  1101. $objterm['name'] = $oterm['label'];
  1102. if (array_key_exists('def', $oterm)) {
  1103. $objterm['definition'] = $oterm['def'];
  1104. }
  1105. if (array_key_exists('subset', $oterm)) {
  1106. $objterm['subset'] = $oterm['subset'];
  1107. }
  1108. if (array_key_exists('namespace', $oterm)) {
  1109. $objterm['namespace'] = $oterm['ontology_name'];
  1110. }
  1111. if (array_key_exists('is_obsolete', $oterm)) {
  1112. $objterm['is_obsolete'] = $oterm['is_obsolete'];
  1113. }
  1114. }
  1115. else {
  1116. $oterm = $this->getTerm($objname);
  1117. if (!$oterm) {
  1118. throw new Exception("Could not find object term $objname\n");
  1119. }
  1120. $objterm = array();
  1121. $objterm['id'] = $oterm['id'][0];
  1122. $objterm['name'] = $oterm['name'][0];
  1123. if (array_key_exists('def', $oterm)) {
  1124. $objterm['definition'] = $oterm['def'][0];
  1125. }
  1126. if (array_key_exists('subset', $oterm)) {
  1127. $objterm['subset'] = $oterm['subset'][0];
  1128. }
  1129. if (array_key_exists('namespace', $oterm)) {
  1130. $objterm['namespace'] = $oterm['namespace'][0];
  1131. }
  1132. if (array_key_exists('is_obsolete', $oterm)) {
  1133. $objterm['is_obsolete'] = $oterm['is_obsolete'][0];
  1134. }
  1135. }
  1136. $objterm['cv_name' ] = $defaultcv;
  1137. $objterm['is_relationship'] = $object_is_relationship;
  1138. $objterm['db_name'] = $default_db;
  1139. print_r('$objterm');
  1140. print_r($objterm);
  1141. $objcvterm = tripal_insert_cvterm($objterm, array('update_existing' => TRUE));
  1142. if (!$objcvterm) {
  1143. throw new Exception("Cannot add cvterm " . $objterm['name']);
  1144. }
  1145. // check to see if the cvterm_relationship already exists, if not add it
  1146. $values = array(
  1147. 'type_id' => $relcvterm->cvterm_id,
  1148. 'subject_id' => $cvterm->cvterm_id,
  1149. 'object_id' => $objcvterm->cvterm_id
  1150. );
  1151. $result = chado_select_record('cvterm_relationship', array('*'), $values);
  1152. if (count($result) == 0) {
  1153. $options = array('return_record' => FALSE);
  1154. $success = chado_insert_record('cvterm_relationship', $values, $options);
  1155. if (!$success) {
  1156. throw new Exception("Cannot add term relationship: '$cvterm->name' $rel '$objcvterm->name'");
  1157. }
  1158. }
  1159. return TRUE;
  1160. }
  1161. /**
  1162. * Retrieves the term array from the temp loading table for a given term id.
  1163. *
  1164. * @param id
  1165. * The id of the term to retrieve
  1166. *
  1167. * @ingroup tripal_obo_loader
  1168. */
  1169. private function getTerm($id) {
  1170. $values = array('id' => $id);
  1171. $result = chado_select_record('tripal_obo_temp', array('stanza'), $values);
  1172. if (count($result) == 0) {
  1173. return FALSE;
  1174. }
  1175. return unserialize(base64_decode($result[0]->stanza));
  1176. }
  1177. /**
  1178. * Adds the synonyms to a term
  1179. *
  1180. * @param term
  1181. * An array representing the cvterm. It must have a 'synonym' key/value pair.
  1182. * @param cvterm
  1183. * The database object of the cvterm to which the synonym will be added.
  1184. *
  1185. * @ingroup tripal_obo_loader
  1186. */
  1187. private function addSynonym($term, $cvterm) {
  1188. // make sure we have a 'synonym_type' vocabulary
  1189. $syncv = tripal_insert_cv(
  1190. 'synonym_type',
  1191. 'A local vocabulary added for synonym types.'
  1192. );
  1193. // now add the synonyms
  1194. if (array_key_exists('synonym', $term)) {
  1195. foreach ($term['synonym'] as $synonym) {
  1196. // separate out the synonym definition and the synonym type
  1197. $def = preg_replace('/^\s*"(.*)"\s*.*$/', '\1', $synonym);
  1198. // the scope will be 'EXACT', etc...
  1199. $scope = drupal_strtolower(preg_replace('/^.*"\s+(.*?)\s+.*$/', '\1', $synonym));
  1200. if (!$scope) { // if no scope then default to 'exact'
  1201. $scope = 'exact';
  1202. }
  1203. // make sure the synonym type exists in the 'synonym_type' vocabulary
  1204. $values = array(
  1205. 'name' => $scope,
  1206. 'cv_id' => array(
  1207. 'name' => 'synonym_type',
  1208. ),
  1209. );
  1210. $syntype = tripal_get_cvterm($values);
  1211. // if it doesn't exist then add it
  1212. if (!$syntype) {
  1213. // build a 'term' object so we can add the missing term
  1214. $term = array(
  1215. 'name' => $scope,
  1216. 'id' => "synonym_type:$scope",
  1217. 'definition' => '',
  1218. 'is_obsolete' => 0,
  1219. 'cv_name' => $syncv->name,
  1220. 'is_relationship' => FALSE
  1221. );
  1222. $syntype = tripal_insert_cvterm($term, array('update_existing' => TRUE));
  1223. if (!$syntype) {
  1224. throw new Exception("Cannot add synonym type: internal:$scope");
  1225. }
  1226. }
  1227. // make sure the synonym doesn't already exists
  1228. $values = array(
  1229. 'cvterm_id' => $cvterm->cvterm_id,
  1230. 'synonym' => $def
  1231. );
  1232. $results = chado_select_record('cvtermsynonym', array('*'), $values);
  1233. if (count($results) == 0) {
  1234. $values = array(
  1235. 'cvterm_id' => $cvterm->cvterm_id,
  1236. 'synonym' => $def,
  1237. 'type_id' => $syntype->cvterm_id
  1238. );
  1239. $options = array('return_record' => FALSE);
  1240. $success = chado_insert_record('cvtermsynonym', $values, $options);
  1241. if (!$success) {
  1242. throw new Exception("Failed to insert the synonym for term: $cvterm->name ($def)");
  1243. }
  1244. }
  1245. // now add the dbxrefs for the synonym if we have a comma in the middle
  1246. // of a description then this will cause problems when splitting os lets
  1247. // just change it so it won't mess up our splitting and then set it back
  1248. // later.
  1249. /**
  1250. $synonym = preg_replace('/(".*?),\s(.*?")/','$1,_$2',$synonym);
  1251. $dbxrefs = preg_split("/, /",preg_replace('/^.*\[(.*?)\]$/','\1',$synonym));
  1252. foreach ($dbxrefs as $dbxref) {
  1253. $dbxref = preg_replace('/,_/',", ",$dbxref);
  1254. if ($dbxref) {
  1255. $this->addCvtermDbxref($syn,$dbxref);
  1256. }
  1257. }
  1258. */
  1259. }
  1260. }
  1261. return TRUE;
  1262. }
  1263. /**
  1264. * Parse the OBO file and populate the templ loading table
  1265. *
  1266. * @param $file
  1267. * The path on the file system where the ontology can be found
  1268. * @param $header
  1269. * An array passed by reference that will be populated with the header
  1270. * information from the OBO file
  1271. *
  1272. * @ingroup tripal_obo_loader
  1273. */
  1274. private function parse($obo_file, &$header) {
  1275. $in_header = 1;
  1276. $stanza = array();
  1277. $default_db = '';
  1278. $line_num = 0;
  1279. $num_read = 0;
  1280. $type = '';
  1281. $filesize = filesize($obo_file);
  1282. $this->setTotalItems($filesize);
  1283. $this->setItemsHandled(0);
  1284. // iterate through the lines in the OBO file and parse the stanzas
  1285. $fh = fopen($obo_file, 'r');
  1286. while ($line = fgets($fh)) {
  1287. $line_num++;
  1288. $size = drupal_strlen($line);
  1289. $num_read += $size;
  1290. $line = trim($line);
  1291. $this->setItemsHandled($num_read);
  1292. // remove newlines
  1293. $line = rtrim($line);
  1294. // remove any special characters that may be hiding
  1295. $line = preg_replace('/[^(\x20-\x7F)]*/', '', $line);
  1296. // skip empty lines
  1297. if (strcmp($line, '') == 0) {
  1298. continue;
  1299. }
  1300. //remove comments from end of lines
  1301. $line = preg_replace('/^(.*?)\!.*$/', '\1', $line); // TODO: if the explamation is escaped
  1302. // Remove annotations surrounded by brackets. These are found
  1303. // in the Trait Ontology (e.g. TO:1000023 {is_inferred="true"})
  1304. // That construct has useful info, but it is not in the OBO 1.4 format
  1305. // specifications.
  1306. $line = preg_replace('/\{.*?\}/', '', $line);
  1307. // at the first stanza we're out of header
  1308. if (preg_match('/^\s*\[/', $line)) {
  1309. $in_header = 0;
  1310. // store the stanza we just finished reading
  1311. if (sizeof($stanza) > 0) {
  1312. // add the term to the temp table
  1313. $values = array(
  1314. 'id' => $stanza['id'][0],
  1315. 'stanza' => base64_encode(serialize($stanza)),
  1316. 'type' => $type,
  1317. );
  1318. $success = chado_insert_record('tripal_obo_temp', $values);
  1319. if (!$success) {
  1320. throw new Exception("Cannot insert stanza into temporary table.");
  1321. }
  1322. }
  1323. // get the stanza type: Term, Typedef or Instance
  1324. $type = preg_replace('/^\s*\[\s*(.+?)\s*\]\s*$/', '\1', $line);
  1325. // start fresh with a new array
  1326. $stanza = array();
  1327. continue;
  1328. }
  1329. // break apart the line into the tag and value but ignore any escaped colons
  1330. preg_replace("/\\:/", "|-|-|", $line); // temporarily replace escaped colons
  1331. $pair = explode(":", $line, 2);
  1332. $tag = $pair[0];
  1333. $value = ltrim(rtrim($pair[1]));// remove surrounding spaces
  1334. // if this is the ID then look for the default DB
  1335. $matches = array();
  1336. if ($tag == 'id' and preg_match('/^(.+?):.*$/', $value, $matches)) {
  1337. $default_db = $matches[1];
  1338. }
  1339. $tag = preg_replace("/\|-\|-\|/", "\:", $tag); // return the escaped colon
  1340. $value = preg_replace("/\|-\|-\|/", "\:", $value);
  1341. if ($in_header) {
  1342. if (!array_key_exists($tag, $header)) {
  1343. $header[$tag] = array();
  1344. }
  1345. $header[$tag][] = $value;
  1346. }
  1347. else {
  1348. if (!array_key_exists($tag, $stanza)) {
  1349. $stanza[$tag] = array();
  1350. }
  1351. $stanza[$tag][] = $value;
  1352. }
  1353. }
  1354. // now add the last term in the file
  1355. if (sizeof($stanza) > 0) {
  1356. $values = array(
  1357. 'id' => $stanza['id'][0],
  1358. 'stanza' => base64_encode(serialize($stanza)),
  1359. 'type' => $type,
  1360. );
  1361. chado_insert_record('tripal_obo_temp', $values);
  1362. if (!$success) {
  1363. throw new Exception("Cannot insert stanza into temporary table.");
  1364. }
  1365. $this->setItemsHandled($num_read);
  1366. }
  1367. return $default_db;
  1368. }
  1369. /**
  1370. * Adds a database reference to a cvterm
  1371. *
  1372. * @param cvterm
  1373. * The database object of the cvterm to which the synonym will be added.
  1374. * @param xref
  1375. * The cross refernce. It should be of the form from the OBO specification
  1376. *
  1377. * @ingroup tripal_obo_loader
  1378. */
  1379. private function addCvtermDbxref($cvterm, $xref) {
  1380. print_r('addCvtermDbxref');
  1381. print_r($cvterm);
  1382. $dbname = preg_replace('/^(.+?):.*$/', '$1', $xref);
  1383. $accession = preg_replace('/^.+?:\s*(.*?)(\{.+$|\[.+$|\s.+$|\".+$|$)/', '$1', $xref);
  1384. $description = preg_replace('/^.+?\"(.+?)\".*?$/', '$1', $xref);
  1385. $dbxrefs = preg_replace('/^.+?\[(.+?)\].*?$/', '$1', $xref);
  1386. if (!$accession) {
  1387. throw new Exception("Cannot add a dbxref without an accession: '$xref'");
  1388. }
  1389. // if the xref is a database link, handle that specially
  1390. if (strcmp($dbname, 'http') == 0) {
  1391. $accession = $xref;
  1392. $dbname = 'URL';
  1393. }
  1394. // add the database
  1395. $db = tripal_insert_db(array('name' => $dbname));
  1396. if (!$db) {
  1397. throw new Exception("Cannot find database '$dbname' in Chado.");
  1398. }
  1399. // now add the dbxref
  1400. $dbxref = $this->addDbxref($db->db_id, $accession, '', $description);
  1401. if (!$dbxref) {
  1402. throw new Exception("Cannot find or add the database reference (dbxref)");
  1403. }
  1404. // finally add the cvterm_dbxref but first check to make sure it exists
  1405. $values = array(
  1406. 'cvterm_id' => $cvterm->cvterm_id,
  1407. 'dbxref_id' => $dbxref->dbxref_id,
  1408. );
  1409. $result = chado_select_record('cvterm_dbxref', array('*'), $values);
  1410. if (count($result) == 0) {
  1411. $ins_options = array('return_record' => FALSE);
  1412. $result = chado_insert_record('cvterm_dbxref', $values, $ins_options);
  1413. if (!$result) {
  1414. throw new Exception("Cannot add cvterm_dbxref: $xref");
  1415. }
  1416. }
  1417. return TRUE;
  1418. }
  1419. /**
  1420. * Adds a property to a cvterm
  1421. *
  1422. * @param cvterm
  1423. * A database object for the cvterm to which properties will be added
  1424. * @param $property
  1425. * The name of the property to add
  1426. * @param $value
  1427. * The value of the property
  1428. * @param rank
  1429. * The rank of the property
  1430. *
  1431. * @ingroup tripal_obo_loader
  1432. */
  1433. private function addCvtermProp($cvterm, $property, $value, $rank) {
  1434. // make sure the 'cvterm_property_type' CV exists
  1435. $cv = tripal_insert_cv('cvterm_property_type', '');
  1436. if (!$cv) {
  1437. throw new Exception("Cannot add/find cvterm_property_type cvterm");
  1438. }
  1439. // get the property type cvterm. If it doesn't exist then we want to add it
  1440. $values = array(
  1441. 'name' => $property,
  1442. 'cv_id' => $cv->cv_id,
  1443. );
  1444. $results = chado_select_record('cvterm', array('*'), $values);
  1445. if (count($results) == 0) {
  1446. $term = array(
  1447. 'name' => $property,
  1448. 'id' => "internal:$property",
  1449. 'definition' => '',
  1450. 'is_obsolete' => 0,
  1451. 'cv_name' => $cv->name,
  1452. 'is_relationship' => FALSE,
  1453. );
  1454. $cvproptype = tripal_insert_cvterm($term, array('update_existing' => FALSE));
  1455. if (!$cvproptype) {
  1456. throw new Exception("Cannot add cvterm property: internal:$property");
  1457. }
  1458. }
  1459. else {
  1460. $cvproptype = $results[0];
  1461. }
  1462. // remove any properties that currently exist for this term. We'll reset them
  1463. if ($rank == 0) {
  1464. $values = array('cvterm_id' => $cvterm->cvterm_id);
  1465. $success = chado_delete_record('cvtermprop', $values);
  1466. if (!$success) {
  1467. throw new Exception("Could not remove existing properties to update property $property for term\n");
  1468. }
  1469. }
  1470. // now add the property
  1471. $values = array(
  1472. 'cvterm_id' => $cvterm->cvterm_id,
  1473. 'type_id' => $cvproptype->cvterm_id,
  1474. 'value' => $value,
  1475. 'rank' => $rank,
  1476. );
  1477. $options = array('return_record' => FALSE);
  1478. $result = chado_insert_record('cvtermprop', $values, $options);
  1479. if (!$result) {
  1480. throw new Exception("Could not add property $property for term\n");
  1481. }
  1482. return TRUE;
  1483. }
  1484. /**
  1485. * Adds a database cross reference to a cvterm
  1486. *
  1487. * @param db_id
  1488. * The database ID of the cross reference
  1489. * @param accession
  1490. * The cross reference's accession
  1491. * @param $version
  1492. * The version of the dbxref
  1493. * @param $description
  1494. * The description of the cross reference
  1495. *
  1496. * @ingroup tripal_obo_loader
  1497. */
  1498. private function addDbxref($db_id, $accession, $version='', $description='') {
  1499. // check to see if the dbxref exists if not, add it
  1500. $values = array(
  1501. 'db_id' => $db_id,
  1502. 'accession' => $accession,
  1503. );
  1504. $result = chado_select_record('dbxref', array('dbxref_id'), $values);
  1505. if (count($result) == 0) {
  1506. $ins_values = array(
  1507. 'db_id' => $db_id,
  1508. 'accession' => $accession,
  1509. 'version' => $version,
  1510. 'description' => $description,
  1511. );
  1512. $ins_options = array('return_record' => FALSE);
  1513. $result = chado_insert_record('dbxref', $ins_values, $ins_options);
  1514. if (!$result) {
  1515. throw new Exception("Failed to insert the dbxref record $accession");
  1516. }
  1517. $result = chado_select_record('dbxref', array('dbxref_id'), $values);
  1518. }
  1519. return $result[0];
  1520. }
  1521. /**
  1522. * API call to Ontology Lookup Service provided by
  1523. * https://www.ebi.ac.uk/ols/docs/api#resources-terms
  1524. *
  1525. * @param accession
  1526. * Accession term for query
  1527. * @param type_of_search
  1528. * Either ontology, term, query, or query-non-local
  1529. *
  1530. * @ingroup tripal_obo_loader
  1531. */
  1532. private function oboEbiLookup($accession, $type_of_search) {
  1533. //Grab just the ontology from the $accession.
  1534. $parts = explode(':', $accession);
  1535. $ontology = strtolower($parts[0]);
  1536. $ontology = preg_replace('/\s+/', '', $ontology);
  1537. if ($type_of_search == 'ontology') {
  1538. $options = array();
  1539. $full_url = 'http://www.ebi.ac.uk/ols/api/ontologies/' . $ontology;
  1540. $response = drupal_http_request($full_url, $options);
  1541. if(!empty($response)){
  1542. $response = drupal_json_decode($response->data);
  1543. }
  1544. }
  1545. elseif ($type_of_search == 'term') {
  1546. //The IRI of the terms, this value must be double URL encoded
  1547. $iri = urlencode(urlencode("http://purl.obolibrary.org/obo/" . str_replace(':' , '_', $accession)));
  1548. $options = array();
  1549. $full_url = 'http://www.ebi.ac.uk/ols/api/ontologies/' . $ontology . '/' . 'terms/' . $iri;
  1550. $response = drupal_http_request($full_url, $options);
  1551. if(!empty($response)){
  1552. $response = drupal_json_decode($response->data);
  1553. }
  1554. }
  1555. elseif($type_of_search == 'query') {
  1556. $options = array();
  1557. $full_url = 'http://www.ebi.ac.uk/ols/api/search?q=' . $accession . '&queryFields=obo_id&local=true';
  1558. $response = drupal_http_request($full_url, $options);
  1559. if(!empty($response)){
  1560. $response = drupal_json_decode($response->data);
  1561. }
  1562. }
  1563. elseif($type_of_search == 'query-non-local') {
  1564. $options = array();
  1565. $full_url = 'http://www.ebi.ac.uk/ols/api/search?q=' . $accession . '&queryFields=obo_id';
  1566. $response = drupal_http_request($full_url, $options);
  1567. if(!empty($response)){
  1568. $response = drupal_json_decode($response->data);
  1569. }
  1570. }
  1571. return $response;
  1572. }
  1573. }
  1574. /**
  1575. * Ajax callback for the OBOImporter::form() function.
  1576. */
  1577. function tripal_cv_obo_form_ajax_callback($form, $form_state) {
  1578. return $form['obo_existing'];
  1579. }