OBOImporter.inc 64 KB

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