瀏覽代碼

Added support for remote files with TripalImporter class

Stephen Ficklin 8 年之前
父節點
當前提交
627c3caa93

+ 33 - 8
tripal/api/tripal.importer.api.inc

@@ -97,9 +97,6 @@ function tripal_run_importer($import_id, TripalJob $job = NULL) {
 
   if ($details) {
     $details->arguments = unserialize($details->arguments);
-    if ($details->job_id) {
-      $details->job = tripal_get_job($details->job_id);
-    }
     if ($details->fid) {
       $details->file = file_load($details->fid);
     }
@@ -112,17 +109,45 @@ function tripal_run_importer($import_id, TripalJob $job = NULL) {
       $loader = new $class($job);
       try {
 
-        // begin the transaction
-        $transaction = db_transaction();
-        print "\nNOTE: Loading of this file is performed using a database transaction. \n" .
-            "If the load fails or is terminated prematurely then the entire set of \n" .
-            "insertions/updates is rolled back and will not be found in the database\n\n";
+        // If the file is provided via a URL then download the file.
+        if (!empty($details->arguments['file_url'])) {
+          // begin the transaction
+          $transaction = db_transaction();
+          print "\nNOTE: Loading of this file is performed using a database transaction. \n" .
+              "If the load fails or is terminated prematurely then the entire set of \n" .
+              "insertions/updates is rolled back and will not be found in the database\n\n";
+
+          $loader->logMessage('Downloading file from remote location: !url', array('!url' => $details->arguments['file_url']));
+
+          // Create a temporary file.
+          $temp = tempnam("temporary://", 'import_');
+          $url_fh = fopen($details->arguments['file_url'], "r");
+          $tmp_fh = fopen($temp, "w");
+          if (!$url_fh) {
+            throw new Exception(t("Unable to download the remote file at !url. Could a firewall be blocking outgoing connections?",
+                array('!url', $details->arguments['file_url'])));
+          }
+          // Write the contents of the remote file to the temp file.
+          while (!feof($url_fh)) {
+            fwrite($tmp_fh, fread($url_fh, 255), 255);
+          }
+          // Set the path to the file for the importer to use.
+          $details->arguments['file_path'] = $temp;
+        }
+
         $loader->preRun($details);
         $loader->run($details);
         $loader->postRun($details);
         if ($job) {
           $job->logMessage("Done");
         }
+
+        // Remove the temp file
+        if (!empty($details->arguments['file_url'])) {
+          $loader->logMessage('Removing downloaded file...');
+          unlink($temp);
+        }
+
         print "\nDone\n";
 
         // Check for new fields and notify the user.

+ 3 - 3
tripal/api/tripal.jobs.api.inc

@@ -363,8 +363,7 @@ function tripal_launch_job($do_parallel = 0, $job_id = NULL, $max_jobs = -1, $si
       WHERE
         TJ.start_time IS NULL AND
         TJ.end_time IS NULL AND
-        TJ.job_id = :job_id AND
-        TJ.status =! 'Cancelled'
+        TJ.job_id = :job_id
       ORDER BY priority ASC, job_id ASC
     ";
     $jobs = db_query($sql, array(':job_id' => $job_id));
@@ -375,7 +374,8 @@ function tripal_launch_job($do_parallel = 0, $job_id = NULL, $max_jobs = -1, $si
       FROM {tripal_jobs} TJ
       WHERE
         TJ.start_time IS NULL AND
-        TJ.end_time IS NULL
+        TJ.end_time IS NULL AND
+        NOT TJ.status = 'Cancelled'
       ORDER BY priority ASC,job_id ASC
     ";
     $jobs = db_query($sql);

+ 70 - 10
tripal/includes/TripalImporter.inc

@@ -68,9 +68,29 @@ class TripalImporter {
    */
   public static $button_text = 'Import File';
 
+  /**
+   * Indicates the methods that the file uploader will support.
+   */
+  public static $methods = array(
+    // Allow the user to upload a file to the server.
+    'file_upload' => TRUE,
+    // Allow the user to provide the path on the Tripal server for the file.
+    'file_local' => TRUE,
+    // Allow the user to provide a remote URL for the file.
+    'file_remote' => TRUE,
+  );
+
+  /**
+   * Indicates if the file must be provided.  An example when it may not be
+   * necessary to require that the user provide a file for uploading if the
+   * loader keeps track of previous files and makes those available for
+   * selection.
+   */
+  public static $file_required = TRUE;
+
 
   // --------------------------------------------------------------------------
-  //                  PRIVATE MEMBERS -- DO NOT EDIT
+  //                  PRIVATE MEMBERS -- DO NOT EDIT or OVERRIDE
   // --------------------------------------------------------------------------
   /**
    * The job that this importer is associated with.  This is needed for
@@ -119,29 +139,59 @@ class TripalImporter {
     $this->job = $job;
   }
 
-  // --------------------------------------------------------------------------
-  //                     PROTECTED FUNCTIONS
-  // --------------------------------------------------------------------------
-
-
   // --------------------------------------------------------------------------
   //                     OVERRIDEABLE FUNCTIONS
   // --------------------------------------------------------------------------
 
+  /**
+   * Provides form elements to be added to the loader form.
+   *
+   * These form elements are added after the file uploader section that
+   * is automaticaly provided by the TripalImporter.
+   *
+   * @return
+   *   A $form array.
+   */
   public function form($form, &$form_state) {
     return $form;
   }
+
+  /**
+   * Handles submission of the form elements.
+   *
+   * The form elements provided in the implementation of the form() function
+   * can be used for special submit if needed.
+   */
   public function formSubmit($form, &$form_state) {
 
   }
+
+  /**
+   * Handles validation of the form elements.
+   *
+   * The form elements provided in the implementation of the form() function
+   * should be validated using this function.
+   */
   public function formValidate($form, &$form_state) {
 
   }
+
+  /**
+   * Executes tasks that should be run prior to execution of the run() function.
+   */
   public function preRun($details) {
 
   }
+  /**
+   * Performs the import.
+   *
+   * This function must be overrriden by a child class to perform the import.
+   */
   public function run($details) {
   }
+  /**
+   * Executes tasks that should be run after execution of the run() function.
+   */
   public function postRun($details) {
 
   }
@@ -174,7 +224,7 @@ class TripalImporter {
    *     - TRIPAL_INFO: (default) Informational messages.
    *     - TRIPAL_DEBUG: Debug-level messages.
    */
-  protected function logMessage($message, $variables = array(), $severity = TRIPAL_INFO) {
+  public function logMessage($message, $variables = array(), $severity = TRIPAL_INFO) {
     // Generate a translated message.
     $tmessage = t($message, $variables);
 
@@ -215,7 +265,8 @@ class TripalImporter {
    * @param unknown $num_handled
    */
   protected function addItemsHandled($num_handled) {
-    $this->setItemsHandled($this->num_handled += $num_handled);
+    $items_handled = $this->num_handled = $this->num_handled + $num_handled;
+    $this->setItemsHandled($items_handled);
   }
   /**
    * Sets the number of items that have been processed.
@@ -233,12 +284,19 @@ class TripalImporter {
     // First set the number of items handled.
     $this->num_handled = $total_handled;
 
+    if ($total_handled == 0) {
+      $memory = number_format(memory_get_usage());
+      print "Percent complete: 0%. Memory: " . $memory . " bytes.\r";
+      return;
+    }
+
     // Now see if we need to report to the user the percent done.  A message
     // will be printed on the command-line if the job is run there.
     $percent = sprintf("%.2f", ($this->num_handled / $this->total_items) * 100);
     $diff = $percent - $this->prev_update;
-    if ($diff >= $interval) {
-      $this->prev_update = $diff;
+
+    if ($diff >= $this->interval) {
+
       $memory = number_format(memory_get_usage());
       print "Percent complete: " . $percent . "%. Memory: " . $memory . " bytes.\r";
 
@@ -246,6 +304,8 @@ class TripalImporter {
       if ($this->job) {
         $this->job->setProgress($percent);
       }
+
+      $this->prev_update = $diff;
     }
   }
 

+ 1 - 0
tripal/includes/TripalJob.inc

@@ -463,6 +463,7 @@ class TripalJob {
     // Report this message to watchdog or set a message.
     if ($severity == TRIPAL_CRITICAL or $severity == TRIPAL_ERROR) {
       tripal_report_error('tripal_job', $severity, $message, $variables);
+      $this->job->status = 'Error';
     }
   }
 }

+ 109 - 51
tripal/includes/tripal.importer.inc

@@ -12,29 +12,48 @@ function tripal_get_importer_form($form, &$form_state, $class) {
     '#type' => 'value',
     '#value' => $class,
   );
-  $form['file'] = array(
-    '#type' => 'fieldset',
-    '#title' => t($class::$upload_title),
-    '#description' => t($class::$upload_description) . ' Either upload the file or if the file exists on the Tripal server please provide the full path.'
-  );
-  $form['file']['file_upload']= array(
-    '#type' => 'html5_file',
-    '#title' => '',
-    '#description' => 'Remember to click the "Upload" button below to send ' .
-      'your file to the server.  This interface is capable of uploading very ' .
-      'large files.  If you are disconnected you can return, reload the file and it ' .
-      'will resume where it left off.  Once the file is uploaded the "Upload '.
-      'Progress" will indicate "Complete".  If the file is already present on the server ' .
-      'then the status will quickly update to "Complete".',
-    '#usage_type' => 'tripal_importer',
-    '#usage_id' => 0,
-  );
 
-  $form['file']['file_local']= array(
-    '#type'          => 'textfield',
-    '#title'         => '',
-    '#description'   => t('If the file is local to the Tripal server please provide the full path here.'),
-  );
+  if ((array_key_exists('file_upload', $class::$methods) and $class::$methods['file_upload'] == TRUE) or
+      (array_key_exists('file_local', $class::$methods) and $class::$methods['file_local'] == TRUE) or
+      (array_key_exists('file_remote', $class::$methods) and $class::$methods['file_remote'] == TRUE)) {
+    $form['file'] = array(
+      '#type' => 'fieldset',
+      '#title' => t($class::$upload_title),
+      '#description' => t($class::$upload_description)
+    );
+  }
+
+  if (array_key_exists('file_upload', $class::$methods) and $class::$methods['file_upload'] == TRUE) {
+    $form['file']['file_upload']= array(
+      '#type' => 'html5_file',
+      '#title' => '',
+      '#description' => 'Remember to click the "Upload" button below to send ' .
+        'your file to the server.  This interface is capable of uploading very ' .
+        'large files.  If you are disconnected you can return, reload the file and it ' .
+        'will resume where it left off.  Once the file is uploaded the "Upload '.
+        'Progress" will indicate "Complete".  If the file is already present on the server ' .
+        'then the status will quickly update to "Complete".',
+      '#usage_type' => 'tripal_importer',
+      '#usage_id' => 0,
+    );
+  }
+
+  if (array_key_exists('file_local', $class::$methods) and $class::$methods['file_local'] == TRUE) {
+    $form['file']['file_local']= array(
+      '#title' => t('Server path'),
+      '#type' => 'textfield',
+      '#maxlength' => 5120,
+      '#description'  => t('If the file is local to the Tripal server please provide the full path here.'),
+    );
+  }
+  if (array_key_exists('file_remote', $class::$methods) and $class::$methods['file_remote'] == TRUE) {
+    $form['file']['file_remote']= array(
+      '#title' => t('Remote path'),
+      '#type' => 'textfield',
+      '#maxlength' => 5102,
+      '#description'  => t('If the file is available via a remote URL please provide the full URL here.  The file will be downloaded when the importer job is executed.'),
+    );
+  }
 
   if ($class::$use_analysis) {
     // get the list of analyses
@@ -46,15 +65,15 @@ function tripal_get_importer_form($form, &$form_state, $class) {
       $analyses[$analysis->analysis_id] = "$analysis->name ($analysis->program $analysis->programversion, $analysis->sourcename)";
     }
     $form['analysis_id'] = array(
-      '#title'       => t('Analysis'),
-      '#type'        => t('select'),
+      '#title'  => t('Analysis'),
+      '#type' => t('select'),
       '#description' => t('Choose the analysis to which the uploaded data will be associated. ' .
         'Why specify an analysis for a data load?  All data comes from some place, even if ' .
         'downloaded from a website. By specifying analysis details for all data imports it ' .
         'provides provenance and helps end user to reproduce the data set if needed. At ' .
         'a minimum it indicates the source of the data.'),
-      '#required'    => $class::$require_analysis,
-      '#options'     => $analyses,
+      '#required' => $class::$require_analysis,
+      '#options' => $analyses,
     );
   }
 
@@ -76,29 +95,42 @@ function tripal_get_importer_form($form, &$form_state, $class) {
  */
 function tripal_get_importer_form_validate($form, &$form_state) {
   $class = $form_state['values']['importer_class'];
+  tripal_load_include_importer_class($class);
 
-  // Get the form values for the file.
-  $file_local = trim($form_state['values']['file_local']);
-  $file_upload = trim($form_state['values']['file_upload']);
+  $file_local = NULL;
+  $file_upload = NULL;
+  $file_remote = NULL;
 
-  // If the file is local make sure it exists on the local filesystem.
-  if ($file_local) {
-    // check to see if the file is located local to Drupal
-    $file_local = trim($file_local);
-    $dfile = $_SERVER['DOCUMENT_ROOT'] . base_path() . $file_local;
-    if (!file_exists($dfile)) {
-      // if not local to Drupal, the file must be someplace else, just use
-      // the full path provided
-      $dfile = $file_local;
-    }
-    if (!file_exists($dfile)) {
-      form_set_error('file_local', t("Cannot find the file on the system. Check that the file exists or that the web server has permissions to read the file."));
+  // Get the form values for the file.
+  if (array_key_exists('file_local', $class::$methods) and $class::$methods['file_local'] == TRUE) {
+    $file_local = trim($form_state['values']['file_local']);
+    // If the file is local make sure it exists on the local filesystem.
+    if ($file_local) {
+      // check to see if the file is located local to Drupal
+      $file_local = trim($file_local);
+      $dfile = $_SERVER['DOCUMENT_ROOT'] . base_path() . $file_local;
+      if (!file_exists($dfile)) {
+        // if not local to Drupal, the file must be someplace else, just use
+        // the full path provided
+        $dfile = $file_local;
+      }
+      if (!file_exists($dfile)) {
+        form_set_error('file_local', t("Cannot find the file on the system. Check that the file exists or that the web server has permissions to read the file."));
+      }
     }
   }
+  if (array_key_exists('file_upload', $class::$methods) and $class::$methods['file_upload'] == TRUE) {
+    $file_upload = trim($form_state['values']['file_upload']);
+  }
+  if (array_key_exists('file_remote', $class::$methods) and $class::$methods['file_remote'] == TRUE) {
+    $file_remote = trim($form_state['values']['file_remote']);
+  }
+
+
 
   // The user must provide at least an uploaded file or a local file path.
-  if (!$file_upload and !$file_local) {
-    form_set_error('file_local', t("You must provide a file either by uploading one or specifying a path on the Tripal server.  Did you click the 'Upload' button?"));
+  if ($class::$file_required == TRUE and !$file_upload and !$file_local and !$file_remote) {
+    form_set_error('file_local', t("You must provide a file."));
   }
 
   // Now allow the loader to do validation of it's form additions.
@@ -113,6 +145,8 @@ function tripal_get_importer_form_submit($form, &$form_state) {
   global $user;
 
   $arguments = $form_state['values'];
+  $class = $form_state['values']['importer_class'];
+  tripal_load_include_importer_class($class);
 
   // Remove the file_local and file_upload args. We'll add in a new
   // full file path and the fid instead.
@@ -124,9 +158,21 @@ function tripal_get_importer_form_submit($form, &$form_state) {
   unset($arguments['op']);
   unset($arguments['button']);
 
-  $file_local = trim($form_state['values']['file_local']);
-  $file_upload = trim($form_state['values']['file_upload']);
-  $class = $form_state['values']['importer_class'];
+
+  $file_local = NULL;
+  $file_upload = NULL;
+  $file_remote = NULL;
+
+  // Get the form values for the file.
+  if (array_key_exists('file_local', $class::$methods) and $class::$methods['file_local'] == TRUE) {
+    $file_local = trim($form_state['values']['file_local']);
+  }
+  if (array_key_exists('file_upload', $class::$methods) and $class::$methods['file_upload'] == TRUE) {
+    $file_upload = trim($form_state['values']['file_upload']);
+  }
+  if (array_key_exists('file_remote', $class::$methods) and $class::$methods['file_remote'] == TRUE) {
+    $file_remote = trim($form_state['values']['file_remote']);
+  }
 
   // Sumbit a job for this loader.
   $fname = '';
@@ -134,6 +180,7 @@ function tripal_get_importer_form_submit($form, &$form_state) {
   if ($file_local) {
     $fname = preg_replace("/.*\/(.*)/", "$1", $file_local);
     $arguments['file_path'] = $file_local;
+    $arguments['file_url'] = '';
     $arguments['fid'] = NULL;
   }
   if ($file_upload) {
@@ -141,11 +188,26 @@ function tripal_get_importer_form_submit($form, &$form_state) {
     $file = file_load($fid);
     $fname = $file->filename;
     $arguments['file_path'] =  base_path() . drupal_realpath($file->uri);
+    $arguments['file_url'] = '';
     $arguments['fid'] = $file_upload;
   }
-
+  if ($file_remote) {
+    $arguments['file_path'] = '';
+    $arguments['file_url'] = $file_remote;
+    $arguments['fid'] = NULL;
+  }
   try {
 
+    // Now allow the loader to do it's own submit if needed.
+    $importer = new $class();
+    $importer->formSubmit($form, $form_state);
+
+    // If the importer wants to rebuild the form for some reason then let's
+    // not add a job.
+    if (array_key_exists('rebuild', $form_state) and $form_state['rebuild'] == TRUE) {
+      return;
+    }
+
     $values = array(
       'uid' => $user->uid,
       'class' => $class,
@@ -175,10 +237,6 @@ function tripal_get_importer_form_submit($form, &$form_state) {
       ))
       ->condition('import_id', $import_id)
       ->execute();
-
-    // Now allow the loader to do it's own submit if needed.
-    $importer = new $class();
-    $importer->formSubmit($form, $form_state);
   }
   catch (Exception $e) {
     drupal_set_message('Cannot submit import: ' . $e->getMessage(), 'error');

+ 10 - 10
tripal/tripal.module

@@ -121,16 +121,16 @@ function tripal_menu() {
     'file path' => drupal_get_path('module', 'tripal'),
   );
 
-  $items['admin/tripal/loaders/obo_loader'] = array(
-    'title' => 'OBO Controlled Vocabulary Loader',
-    'description' => t("Import vocabularies and terms in OBO format."),
-    'access arguments' => array('administer tripal'),
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('tripal_vocabulary_import_form'),
-    'file' => 'includes/tripal.admin.inc',
-    'file path' => drupal_get_path('module', 'tripal'),
-    'type' => MENU_NORMAL_ITEM,
-  );
+//   $items['admin/tripal/loaders/obo_loader'] = array(
+//     'title' => 'OBO Controlled Vocabulary Loader',
+//     'description' => t("Import vocabularies and terms in OBO format."),
+//     'access arguments' => array('administer tripal'),
+//     'page callback' => 'drupal_get_form',
+//     'page arguments' => array('tripal_vocabulary_import_form'),
+//     'file' => 'includes/tripal.admin.inc',
+//     'file path' => drupal_get_path('module', 'tripal'),
+//     'type' => MENU_NORMAL_ITEM,
+//   );
 
   $items['admin/tripal/extension'] = array(
     'title' => 'Extensions',

+ 3 - 1
tripal_chado/api/modules/tripal_chado.cv.api.inc

@@ -432,7 +432,7 @@ function tripal_update_cvtermpath_loop($origin, $child_id, $cv_id, $type_id, $de
   $count =  db_query(
     'SELECT *
      FROM cvtermpath
-     WHERE cv_id = :cvid 
+     WHERE cv_id = :cvid
       AND object_id = :origin
       AND subject_id = :child_id
       AND pathdistance = :depth
@@ -1182,6 +1182,8 @@ function tripal_insert_cvterm($term, $options = array()) {
 }
 
 /**
+ * TODO: deprecate this function
+ *
  * This function allows other modules to programatically
  * submit an ontology for loading into Chado.
  *

+ 1 - 1
tripal_chado/includes/TripalImporter/FASTAImporter.inc

@@ -10,7 +10,7 @@ class FASTAImporter extends TripalImporter {
    * The machine name for this loader. This name will be used to construct
    * the URL for the loader.
    */
-  public static $machine_name = 'fasta_loader';
+  public static $machine_name = 'chado_fasta_loader';
 
   /**
    * A brief description for this loader.  This description will be

+ 1 - 1
tripal_chado/includes/TripalImporter/GFF3Importer.inc

@@ -12,7 +12,7 @@ class GFF3Importer extends TripalImporter {
    * The machine name for this loader. This name will be used to construct
    * the URL for the loader.
    */
-  public static $machine_name = 'gff3_loader';
+  public static $machine_name = 'chado_gff3_loader';
 
   /**
    * A brief description for this loader.  This description will be

+ 1384 - 0
tripal_chado/includes/TripalImporter/OBOImporter.inc

@@ -0,0 +1,1384 @@
+<?php
+
+class OBOImporter extends TripalImporter {
+
+  // --------------------------------------------------------------------------
+  //                     EDITABLE STATIC CONSTANTS
+  //
+  // The following constants SHOULD be set for each descendent class.  They are
+  // used by the static functions to provide information to Drupal about
+  // the field and it's default widget and formatter.
+  // --------------------------------------------------------------------------
+
+  /**
+   * The name of this loader.  This name will be presented to the site
+   * user.
+   */
+  public static $name = 'Chado OBO Loader';
+
+  /**
+   * The machine name for this loader. This name will be used to construct
+   * the URL for the loader.
+   */
+  public static $machine_name = 'chado_obo_loader';
+
+  /**
+   * A brief description for this loader.  This description will be
+   * presented to the site user.
+   */
+  public static $description = 'Import vocabularies and terms in OBO format.';
+
+  /**
+   * An array containing the extensions of allowed file types.
+   */
+  public static $file_types = array('obo');
+
+
+  /**
+   * Provides information to the user about the file upload.  Typically this
+   * may include a description of the file types allowed.
+   */
+  public static $upload_description = 'Please provide the details for importing a new OBO file. The file must have a .obo extension.';
+
+  /**
+   * The title that should appear above the upload button.
+   */
+  public static $upload_title = 'New OBO File';
+
+  /**
+   * If the loader should require an analysis record.  To maintain provenance
+   * we should always indiate where the data we are uploading comes from.
+   * The method that Tripal attempts to use for this by associating upload files
+   * with an analysis record.  The analysis record provides the details for
+   * how the file was created or obtained. Set this to FALSE if the loader
+   * should not require an analysis when loading. if $use_analysis is set to
+   * true then the form values will have an 'analysis_id' key in the $form_state
+   * array on submitted forms.
+   */
+  public static $use_analysis = FALSE;
+
+  /**
+   * If the $use_analysis value is set above then this value indicates if the
+   * analysis should be required.
+   */
+  public static $require_analysis = TRUE;
+
+  /**
+   * Text that should appear on the button at the bottom of the importer
+   * form.
+   */
+  public static $button_text = 'Import OBO File';
+
+  /**
+   * Indicates the methods that the file uploader will support.
+   */
+  public static $methods = array(
+    // Allow the user to upload a file to the server.
+    'file_upload' => FALSE,
+    // Allow the user to provide the path on the Tripal server for the file.
+    'file_local' => FALSE,
+    // Allow the user to provide a remote URL for the file.
+    'file_remote' => FALSE,
+  );
+
+  public static $file_required = FALSE;
+
+
+  /**
+   * @see TripalImporter::form()
+   */
+  public function form($form, &$form_state) {
+
+    // get a list of db from chado for user to choose
+    $sql = "SELECT * FROM {tripal_cv_obo} ORDER BY name";
+    $results = db_query($sql);
+
+    $obos = array();
+    $obos[] = 'Select a Vocabulary';
+    foreach ($results as $obo) {
+      $obos[$obo->obo_id] = $obo->name;
+    }
+
+    $obo_id = '';
+    if (array_key_exists('values', $form_state)) {
+      $obo_id = array_key_exists('obo_id', $form_state['values']) ? $form_state['values']['obo_id'] : '';
+    }
+
+
+    $form['instructions']['info'] = array(
+      '#type' => 'item',
+      '#markup' => t('This page allows you to load vocabularies and ontologies
+        that are in OBO format. Once loaded, the terms from these
+        vocabularies can be used to create content.
+        You may use the form below to either reload a vocabulary that is already
+        loaded (as when new updates to that vocabulary are available) or load a new
+        vocabulary.'),
+    );
+
+    $form['obo_existing'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Use a Saved Ontology OBO Reference'),
+      '#prefix' => '<span id="obo-existing-fieldset">',
+      '#suffix' => '</span>'
+    );
+
+    $form['obo_existing']['existing_instructions']= array(
+      '#type' => 'item',
+      '#markup' => t('The vocabularies listed in the select box below have bene pre-populated
+        upon installation of Tripal or have been previously loaded.  Select one to edit
+        its settings or submit for loading.  You may reload any vocabulary that has
+        already been loaded to retrieve any new updates.'),
+    );
+
+    $form['obo_existing']['obo_id'] = array(
+      '#title' => t('Ontology OBO File Reference'),
+      '#type' => 'select',
+      '#options' => $obos,
+      '#ajax' => array(
+        'callback' => 'tripal_cv_obo_form_ajax_callback',
+        'wrapper' => 'obo-existing-fieldset',
+      ),
+      '#description' => t('Select a vocabulary to import.')
+    );
+
+    // If the user has selected an OBO ID then get the form elements for
+    // updating.
+    if ($obo_id) {
+      $uobo_name = '';
+      $uobo_url = '';
+      $uobo_file = '';
+
+      $vocab = db_select('tripal_cv_obo', 't')
+        ->fields('t', array('name', 'path'))
+        ->condition('obo_id', $obo_id)
+        ->execute()
+        ->fetchObject();
+      $uobo_name = $vocab->name;
+      if (preg_match('/^http/', $vocab->path)) {
+        $uobo_url = $vocab->path;
+      }
+      else {
+        $uobo_file = trim($vocab->path);
+        $matches = array();
+        if (preg_match('/\{(.*?)\}/', $uobo_file, $matches)) {
+          $modpath = drupal_get_path('module', $matches[1]);
+          $uobo_file = preg_replace('/\{.*?\}/', $modpath, $uobo_file);
+        }
+      }
+      // We don't want the previous value to remain. We want the new default to
+      // show up, so remove the input values
+      unset($form_state['input']['uobo_name']);
+      unset($form_state['input']['uobo_url']);
+      unset($form_state['input']['uobo_file']);
+
+      $form['obo_existing']['uobo_name']= array(
+        '#type'          => 'textfield',
+        '#title'         => t('Vocabulary Name'),
+        '#description'   => t('Please provide a name for this vocabulary.  After upload, this name will appear in the drop down
+                             list above for use again later.'),
+        '#default_value'  => $uobo_name,
+      );
+
+      $form['obo_existing']['uobo_url']= array(
+        '#type'          => 'textfield',
+        '#title'         => t('Remote URL'),
+        '#description'   => t('Please enter a URL for the online OBO file.  The file will be downloaded and parsed.
+                             (e.g. http://www.obofoundry.org/ro/ro.obo'),
+        '#default_value' => $uobo_url,
+      );
+
+      $form['obo_existing']['uobo_file']= array(
+        '#type'          => 'textfield',
+        '#title'         => t('Local File'),
+        '#description'   => t('Please enter the file system path for an OBO
+          definition file. If entering a path relative to
+          the Drupal installation you may use a relative path that excludes the
+          Drupal installation directory (e.g. sites/default/files/xyz.obo). Note
+          that Drupal relative paths have no preceeding slash.
+          Otherwise, please provide the full path on the filesystem.  The path
+          must be accessible to the web server on which this Drupal instance is running.'),
+        '#default_value' => $uobo_file,
+      );
+      $form['obo_existing']['update_obo_details'] = array(
+        '#type' => 'submit',
+        '#value' => 'Update Ontology Details',
+        '#name' => 'update_obo_details'
+      );
+    }
+
+    $form['obo_new'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Add a New Ontology OBO Reference'),
+      '#collapsible' => TRUE,
+      '#collapsed' => TRUE,
+    );
+
+    $form['obo_new']['path_instructions']= array(
+      '#value' => t('Provide the name and path for the OBO file.  If the vocabulary OBO file
+                     is stored local to the server provide a file name. If the vocabulry is stored remotely,
+                     provide a URL.  Only provide a URL or a local file, not both.'),
+    );
+
+    $form['obo_new']['obo_name']= array(
+      '#type'          => 'textfield',
+      '#title'         => t('New Vocabulary Name'),
+      '#description'   => t('Please provide a name for this vocabulary.  After upload, this name will appear in the drop down
+                             list above for use again later.'),
+    );
+
+    $form['obo_new']['obo_url']= array(
+      '#type'          => 'textfield',
+      '#title'         => t('Remote URL'),
+      '#description'   => t('Please enter a URL for the online OBO file.  The file will be downloaded and parsed.
+                             (e.g. http://www.obofoundry.org/ro/ro.obo'),
+    );
+
+    $form['obo_new']['obo_file']= array(
+      '#type'  => 'textfield',
+      '#title'  => t('Local File'),
+      '#description' => t('Please enter the file system path for an OBO
+          definition file. If entering a path relative to
+          the Drupal installation you may use a relative path that excludes the
+          Drupal installation directory (e.g. sites/default/files/xyz.obo). Note
+          that Drupal relative paths have no preceeding slash.
+          Otherwise, please provide the full path on the filesystem.  The path
+          must be accessible to the web server on which this Drupal instance is running.'),
+    );
+
+    return $form;
+  }
+  /**
+   * @see TripalImporter::formSubmit()
+   */
+  public function formSubmit($form, &$form_state) {
+
+    $obo_id    = $form_state['values']['obo_id'];
+    $obo_name  = trim($form_state['values']['obo_name']);
+    $obo_url   = trim($form_state['values']['obo_url']);
+    $obo_file  = trim($form_state['values']['obo_file']);
+    $uobo_name  = array_key_exists('uobo_name', $form_state['values']) ? trim($form_state['values']['uobo_name']) : '';
+    $uobo_url   = array_key_exists('uobo_url', $form_state['values']) ? trim($form_state['values']['uobo_url']) : '';
+    $uobo_file  = array_key_exists('uobo_file', $form_state['values']) ? trim($form_state['values']['uobo_file']) : '';
+
+    // If the user requested to alter the details then do that.
+    if ($form_state['clicked_button']['#name'] == 'update_obo_details') {
+      $form_state['rebuild'] = TRUE;
+      $success = db_update('tripal_cv_obo')
+        ->fields(array(
+          'name' => $uobo_name,
+          'path' => $uobo_url ? $uobo_url : $uobo_file,
+        ))
+        ->condition('obo_id', $obo_id)
+        ->execute();
+      if ($success) {
+        drupal_set_message(t("The vocabulary %vocab has been updated.", array('%vocab' => $uobo_name)));
+      }
+      else {
+        drupal_set_message(t("The vocabulary %vocab could not be updated.", array('%vocab' => $uobo_name)), 'error');
+      }
+
+    }
+    else if (!empty($obo_name)) {
+      $obo_id = db_insert('tripal_cv_obo')
+        ->fields(array(
+          'name' => $obo_name,
+          'path' => $obo_url ? $obo_url : $obo_file,
+        ))
+        ->execute();
+        // Add the obo_id to the form_state vaules.
+        $form_state['values']['obo_id'] = $obo_id;
+      if ($success) {
+        drupal_set_message(t("The vocabulary %vocab has been added.", array('%vocab' => $obo_name)));
+      }
+      else {
+        $form_state['rebuild'] = TRUE;
+        drupal_set_message(t("The vocabulary %vocab could not be added.", array('%vocab' => $obo_name)), 'error');
+      }
+    }
+  }
+  /**
+   * @see TripalImporter::formValidate()
+   */
+  public function formValidate($form, &$form_state) {
+    $obo_id    = $form_state['values']['obo_id'];
+    $obo_name  = trim($form_state['values']['obo_name']);
+    $obo_url   = trim($form_state['values']['obo_url']);
+    $obo_file  = trim($form_state['values']['obo_file']);
+    $uobo_name  = array_key_exists('uobo_name', $form_state['values']) ? trim($form_state['values']['uobo_name']) : '';
+    $uobo_url   = array_key_exists('uobo_url', $form_state['values']) ? trim($form_state['values']['uobo_url']) : '';
+    $uobo_file  = array_key_exists('uobo_file', $form_state['values']) ? trim($form_state['values']['uobo_file']) : '';
+
+    // Make sure if the name is changed it doesn't conflict with another OBO.
+    if ($form_state['clicked_button']['#name'] == 'update_obo_details' or
+        $form_state['clicked_button']['#name'] == 'update_load_obo') {
+      // Get the current record
+      $vocab = db_select('tripal_cv_obo', 't')
+      ->fields('t', array('obo_id', 'name', 'path'))
+      ->condition('name', $uobo_name)
+      ->execute()
+      ->fetchObject();
+      if ($vocab and $vocab->obo_id != $obo_id) {
+        form_set_error('uobo_name', 'The vocabulary name must be different from existing vocabularies');
+      }
+      // Make sure the file exists. First check if it is a relative path
+      $dfile = $_SERVER['DOCUMENT_ROOT'] . base_path() . $uobo_file;
+      if (!file_exists($dfile)) {
+        if (!file_exists($uobo_file)) {
+          form_set_error('uobo_file', 'The specified path does not exist or cannot be read.');
+        }
+      }
+      if (!$uobo_url and !$uobo_file) {
+        form_set_error('uobo_url', 'Please provide a URL or a path for the vocabulary.');
+      }
+      if ($uobo_url and $uobo_file) {
+        form_set_error('uobo_url', 'Please provide only a URL or a path for the vocabulary, but not both.');
+      }
+    }
+    if ($form_state['clicked_button']['#name'] == 'add_new_obo') {
+      // Get the current record
+      $vocab = db_select('tripal_cv_obo', 't')
+      ->fields('t', array('obo_id', 'name', 'path'))
+      ->condition('name', $obo_name)
+      ->execute()
+      ->fetchObject();
+      if ($vocab) {
+        form_set_error('obo_name', 'The vocabulary name must be different from existing vocabularies');
+      }
+      // Make sure the file exists. First check if it is a relative path
+      $dfile = $_SERVER['DOCUMENT_ROOT'] . base_path() . $obo_file;
+      if (!file_exists($dfile)) {
+        if (!file_exists($obo_file)) {
+          form_set_error('obo_file', 'The specified path does not exist or cannot be read.');
+        }
+      }
+      if (!$obo_url and !$obo_file) {
+        form_set_error('obo_url', 'Please provide a URL or a path for the vocabulary.');
+      }
+      if ($obo_url and $obo_file) {
+        form_set_error('obo_url', 'Please provide only a URL or a path for the vocabulary, but not both.');
+      }
+    }
+  }
+  /**
+   * @see TripalImporter::run()
+   */
+  public function run($details) {
+
+    $arguments = $details->arguments;
+    $obo_id = $arguments['obo_id'];
+
+    $this->loadOBO_v1_2_id($obo_id);
+  }
+
+  /**
+   * A wrapper function for importing the user specified OBO file into Chado by
+   * specifying the obo_id of the OBO. It requires that the file be in OBO v1.2
+   * compatible format.  This function is typically executed via the Tripal jobs
+   * management after a user submits a job via the Load Onotloies form.
+   *
+   * @param $obo_id
+   *   An obo_id from the tripal_cv_obo file that specifies which OBO file to import
+   * @ingroup tripal_obo_loader
+   */
+  private function loadOBO_v1_2_id($obo_id) {
+
+    // Get the OBO reference.
+    $sql = "SELECT * FROM {tripal_cv_obo} WHERE obo_id = :obo_id";
+    $obo = db_query($sql, array(':obo_id' => $obo_id))->fetchObject();
+
+    // Convert the module name to the real path if present
+    if (preg_match("/\{(.*?)\}/", $obo->path, $matches)) {
+      $module = $matches[1];
+      $path = drupal_realpath(drupal_get_path('module', $module));
+      $obo->path = preg_replace("/\{.*?\}/", $path, $obo->path);
+    }
+
+    // if the reference is for a remote URL then run the URL processing function
+    if (preg_match("/^https:\/\//", $obo->path) or
+        preg_match("/^http:\/\//", $obo->path) or
+        preg_match("/^ftp:\/\//", $obo->path)) {
+      $this->loadOBO_v1_2_url($obo->name, $obo->path, 0);
+    }
+    // if the reference is for a local file then run the file processing function
+    else {
+      // check to see if the file is located local to Drupal
+      $dfile = $_SERVER['DOCUMENT_ROOT'] . base_path() . $obo->path;
+      if (file_exists($dfile)) {
+        $this->loadOBO_v1_2_file($obo->name, $dfile, 0);
+      }
+      // if not local to Drupal, the file must be someplace else, just use
+      // the full path provided
+      else {
+        if (file_exists($obo->path)) {
+          $this->loadOBO_v1_2_file($obo->name, $obo->path, 0);
+        }
+        else {
+          print "ERROR: could not find OBO file: '$obo->path'\n";
+        }
+      }
+    }
+  }
+
+  /**
+   * A wrapper function for importing the user specified OBO file into Chado by
+   * specifying the filename and path of the OBO. It requires that the file be in OBO v1.2
+   * compatible format.  This function is typically executed via the Tripal jobs
+   * management after a user submits a job via the Load Onotloies form.
+   *
+   * @param $obo_name
+   *   The name of the OBO (typially the ontology or controlled vocabulary name)
+   * @param $file
+   *   The path on the file system where the ontology can be found
+   * @param $is_new
+   *   Set to TRUE if this is a new ontology that does not yet exist in the
+   *   tripal_cv_obo table.  If TRUE the OBO will be added to the table.
+   *
+   * @ingroup tripal_obo_loader
+   */
+  private function loadOBO_v1_2_file($obo_name, $file, $is_new = TRUE) {
+    $newcvs = array();
+
+    if ($is_new) {
+      tripal_insert_obo($obo_name, $file);
+    }
+
+    $success = $this->loadOBO_v1_2($file, $newcvs);
+    if ($success) {
+      // update the cvtermpath table
+      $this->load_cvtermpath($newcvs);
+      print "\nDone\n";
+    }
+  }
+
+  /**
+   * A wrapper function for importing the user specified OBO file into Chado by
+   * specifying the remote URL of the OBO. It requires that the file be in OBO v1.2
+   * compatible format.  This function is typically executed via the Tripal jobs
+   * management after a user submits a job via the Load Onotloies form.
+   *
+   * @param $obo_name
+   *   The name of the OBO (typially the ontology or controlled vocabulary name)
+   * @param $url
+   *   The remote URL of the OBO file.
+   * @param $is_new
+   *   Set to TRUE if this is a new ontology that does not yet exist in the
+   *   tripal_cv_obo table.  If TRUE the OBO will be added to the table.
+   *
+   * @ingroup tripal_obo_loader
+   */
+  private function loadOBO_v1_2_url($obo_name, $url, $is_new = TRUE) {
+
+    $newcvs = array();
+
+    // first download the OBO
+    $temp = tempnam(sys_get_temp_dir(), 'obo_');
+    print "Downloading URL $url, saving to $temp\n";
+    $url_fh = fopen($url, "r");
+    $obo_fh = fopen($temp, "w");
+    if (!$url_fh) {
+      throw new Excpetion("Unable to download the remote OBO file at $url. Could a firewall be blocking outgoing connections? " .
+          " if you are unable to download the file you may manually downlod the OBO file and use the web interface to " .
+          " specify the location of the file on your server.");
+
+    }
+    while (!feof($url_fh)) {
+      fwrite($obo_fh, fread($url_fh, 255), 255);
+    }
+    fclose($url_fh);
+    fclose($obo_fh);
+
+    if ($is_new) {
+      tripal_insert_obo($obo_name, $url);
+    }
+
+    // second, parse the OBO
+    $success = $this->loadOBO_v1_2($temp, $newcvs);
+    if ($success) {
+
+      // update the cvtermpath table
+      $this->load_cvtermpath($newcvs);
+      print "Done\n";
+    }
+    // now remove the temp file
+    unlink($temp);
+  }
+
+  /**
+   * A function for executing the cvtermpath function of Chado.  This function
+   * populates the cvtermpath table of Chado for quick lookup of term
+   * relationships
+   *
+   * @param $newcvs
+   *   An associative array of controlled vocabularies to update.  The key must be
+   *   the name of the vocabulary and the value the cv_id from the cv table of chado.
+   *
+   * @ingroup tripal_obo_loader
+   */
+  private function load_cvtermpath($newcvs) {
+
+    print "\nUpdating cvtermpath table.  This may take a while...\n";
+    foreach ($newcvs as $namespace => $cvid) {
+      tripal_update_cvtermpath($cvid);
+    }
+  }
+
+  /**
+   * Imports a given OBO file into Chado.  This function is usually called by
+   * one of three wrapper functions:  loadOBO_v1_2_id,
+   * loadOBO_v1_2_file or tirpal_cv_load_obo_v1_2_url. But, it can
+   * be called directly if the full path to an OBO file is available on the
+   * file system.
+   *
+   * @param $flie
+   *   The full path to the OBO file on the file system
+   * @param $newcvs
+   *   An empty array passed by reference that upon return will contain the list
+   *   of newly added vocabularies.  The key will contain the CV name and the
+   *   value the new cv_id
+   *
+   *
+   * @ingroup tripal_obo_loader
+   */
+  private function loadOBO_v1_2($file, &$newcvs) {
+
+    $header = array();
+
+    // make sure our temporary table exists
+    $ret = array();
+
+    // empty the temp table
+    $sql = "DELETE FROM {tripal_obo_temp}";
+    chado_query($sql);
+
+    $this->logMessage("Step 1: Preloading File $file...");
+
+    // parse the obo file
+    $default_db = $this->parse($file, $header);
+
+    // add the CV for this ontology to the database.  The v1.2 definition
+    // specifies a 'default-namespace' to be used if a 'namespace' is not
+    // present for each stanza.  Some ontologies have adopted the v1.4 method
+    // in their v1.2 files and not including it.
+    if (array_key_exists('default-namespace', $header)) {
+      $defaultcv = tripal_insert_cv($header['default-namespace'][0], '');
+      if (!$defaultcv) {
+        throw new Excpetion('Cannot add namespace ' . $header['default-namespace'][0]);
+      }
+      $newcvs[$header['default-namespace'][0]] = $defaultcv->cv_id;
+    }
+    // if the 'default-namespace' is missing
+    else {
+
+      // look to see if an 'ontology' key is present.  It is part of the v1.4
+      // specification so it shouldn't be in the file, but just in case
+      if (array_key_exists('ontology', $header)) {
+        $defaultcv = tripal_insert_cv(strtoupper($header['ontology'][0]), '');
+        if (!$defaultcv) {
+          throw new Excpetion('Cannot add namespace ' . strtoupper($header['ontology'][0]));
+        }
+        $newcvs[strtoupper(strtoupper($header['ontology'][0]))] = $defaultcv->cv_id;
+      }
+      else {
+        throw new Excpetion("Could not find a namespace for this OBO file.");
+      }
+      $this->logMessage("This OBO is missing the 'default-namespace' header. It is not possible to determine which vocabulary terms without a 'namespace' key should go.  Instead, those terms will be placed in the '%vocab' vocabulary.",
+          array('%vocab' => $defaultcv->name), TRIPAL_WARNING);
+    }
+
+    // add any typedefs to the vocabulary first
+    $this->logMessage("Step 2: Loading type defs...");
+    $this->loadTypeDefs($defaultcv, $newcvs, $default_db);
+
+    // next add terms to the vocabulary
+    $this->logMessage("Step 3: Loading terms...");
+    if (!$this->processTerms($defaultcv, $newcvs, $default_db)) {
+      throw new Exception('Cannot add terms from this ontology');
+    }
+
+  }
+
+  /**
+   * OBO files are divided into a typedefs terms section and vocabulary terms section.
+   * This function loads the typedef terms from the OBO.
+   *
+   * @param $defaultcv
+   *   A database object containing a record from the cv table for the
+   *   default controlled vocabulary
+   * @param $newcvs
+   *   An associative array of controlled vocabularies for this OBO.  The key must be
+   *   the name of the vocabulary and the value the cv_id from the cv table of chado.
+   * @param $default_db
+   *   The name of the default database.
+   *
+   * @ingroup tripal_obo_loader
+   */
+  private function loadTypeDefs($defaultcv, $newcvs, $default_db) {
+    $sql = "SELECT * FROM {tripal_obo_temp} WHERE type = 'Typedef' ";
+    $typedefs = chado_query($sql);
+
+    $sql = "
+      SELECT count(*) as num_terms
+      FROM {tripal_obo_temp}
+      WHERE type = 'Typedef'
+    ";
+    $result = chado_query($sql)->fetchObject();
+    $count = $result->num_terms;
+    $this->setTotalItems($count);
+    $this->setItemsHandled(0);
+
+    $i = 0;
+    foreach ($typedefs as $typedef) {
+      $this->setItemsHandled($i);
+      $term = unserialize(base64_decode($typedef->stanza));
+      $this->processTerm($term, $defaultcv->name, 1, $newcvs, $default_db);
+      $i++;
+    }
+
+    $this->setItemsHandled($i);
+    return 1;
+  }
+
+  /**
+   * OBO files are divided into a typedefs section and a terms section.
+   *
+   * This function loads the typedef terms from the OBO.
+   *
+   * @param $defaultcv
+   *   A database object containing a record from the cv table for the
+   *   default controlled vocabulary
+   * @param $newcvs
+   *   An associative array of controlled vocabularies for this OBO.  The key must be
+   *   the name of the vocabulary and the value the cv_id from the cv table of chado.
+   * @param $default_db
+   *   The name of the default database.
+   */
+  private function processTerms($defaultcv, &$newcvs, $default_db) {
+
+    $i = 0;
+
+    // iterate through each term from the OBO file and add it
+    $sql = "
+      SELECT * FROM {tripal_obo_temp}
+      WHERE type = 'Term'
+      ORDER BY id
+    ";
+    $terms = chado_query($sql);
+
+    $sql = "
+      SELECT count(*) as num_terms
+      FROM {tripal_obo_temp}
+      WHERE type = 'Term'
+    ";
+    $result = chado_query($sql)->fetchObject();
+    $count = $result->num_terms;
+    $this->setTotalItems($count);
+    $this->setItemsHandled(0);
+
+    // Iterate through the terms.
+    foreach ($terms as $t) {
+      $term = unserialize(base64_decode($t->stanza));
+      $this->setItemsHandled($i);
+
+      // add/update this term
+      if (!$this->processTerm($term, $defaultcv->name, 0, $newcvs, $default_db)) {
+        throw new Excpetion("Failed to process terms from the ontology");
+      }
+
+      $i++;
+    }
+    $this->setItemsHandled($i);
+    return 1;
+  }
+
+  /**
+   * Uses the provided term array to add/update information to Chado about the
+   * term including the term, dbxref, synonyms, properties, and relationships.
+   *
+   * @param $term
+   *   An array representing the cvterm.
+   * @param $defaultcv
+   *   The name of the default controlled vocabulary
+   * @is_relationship
+   *   Set to 1 if this term is a relationship term
+   * @default_db
+   *   The name of the default database.
+   *
+   * @ingroup tripal_obo_loader
+   */
+  private function processTerm($term, $defaultcv, $is_relationship = 0, &$newcvs, $default_db) {
+
+    // make sure we have a namespace for this term
+    if (!array_key_exists('namespace', $term) and !($defaultcv or $defaultcv == '')) {
+      throw new Excpetion("Cannot add the term: no namespace defined. " . $term['id'][0]);
+    }
+
+    // construct the term array for sending to the tripal_chado_add_cvterm function
+    // for adding a new cvterm
+    $t = array();
+    $t['id'] = $term['id'][0];
+    $t['name'] = $term['name'][0];
+    if (array_key_exists('def', $term)) {
+      $t['definition'] = $term['def'][0];
+    }
+    if (array_key_exists('subset', $term)) {
+      $t['subset'] = $term['subset'][0];
+    }
+    if (array_key_exists('namespace', $term)) {
+      $t['namespace'] = $term['namespace'][0];
+    }
+    if (array_key_exists('is_obsolete', $term)) {
+      $t['is_obsolete'] = $term['is_obsolete'][0];
+    }
+
+    $t['cv_name'] = $defaultcv;
+    $t['is_relationship'] = $is_relationship;
+    $t['db_name'] = $default_db;
+
+    // add the cvterm
+    $cvterm = tripal_insert_cvterm($t, array('update_existing' => TRUE));
+    if (!$cvterm) {
+      throw new Excpetion("Cannot add the term " . $term['id'][0]);
+    }
+
+    if (array_key_exists('namespace', $term)) {
+      $newcvs[$term['namespace'][0]] = $cvterm->cv_id;
+    }
+
+    // now handle other properites
+    if (array_key_exists('is_anonymous', $term)) {
+      //print "WARNING: unhandled tag: is_anonymous\n";
+    }
+    if (array_key_exists('alt_id', $term)) {
+      foreach ($term['alt_id'] as $alt_id) {
+        if (!$this->addCvtermDbxref($cvterm, $alt_id)) {
+          throw new Excpetion("Cannot add alternate id $alt_id");
+        }
+      }
+    }
+
+    if (array_key_exists('subset', $term)) {
+      //print "WARNING: unhandled tag: subset\n";
+    }
+    // add synonyms for this cvterm
+    if (array_key_exists('synonym', $term)) {
+      if (!$this->addSynonym($term, $cvterm)) {
+        throw new Excpetion("Cannot add synonyms");
+      }
+    }
+
+    // reformat the deprecated 'exact_synonym, narrow_synonym, and broad_synonym'
+    // types to be of the v1.2 standard
+    if (array_key_exists('exact_synonym', $term) or array_key_exists('narrow_synonym', $term) or array_key_exists('broad_synonym', $term)) {
+      if (array_key_exists('exact_synonym', $term)) {
+        foreach ($term['exact_synonym'] as $synonym) {
+          $new = preg_replace('/^\s*(\".+?\")(.*?)$/', '$1 EXACT $2', $synonym);
+          $term['synonym'][] = $new;
+        }
+      }
+      if (array_key_exists('narrow_synonym', $term)) {
+        foreach ($term['narrow_synonym'] as $synonym) {
+          $new = preg_replace('/^\s*(\".+?\")(.*?)$/', '$1 NARROW $2', $synonym);
+          $term['synonym'][] = $new;
+        }
+      }
+      if (array_key_exists('broad_synonym', $term)) {
+        foreach ($term['broad_synonym'] as $synonym) {
+          $new = preg_replace('/^\s*(\".+?\")(.*?)$/', '$1 BROAD $2', $synonym);
+          $term['synonym'][] = $new;
+        }
+      }
+
+      if (!$this->addSynonym($term, $cvterm)) {
+        throw new Excpetion("Cannot add/update synonyms");
+      }
+    }
+
+    // add the comment to the cvtermprop table
+    if (array_key_exists('comment', $term)) {
+      $comments = $term['comment'];
+      $j = 0;
+      foreach ($comments as $comment) {
+        if (!$this->addCvtermProp($cvterm, 'comment', $comment, $j)) {
+          throw new Excpetion("Cannot add/update cvterm property");
+        }
+        $j++;
+      }
+    }
+
+    // add any other external dbxrefs
+    if (array_key_exists('xref', $term)) {
+      foreach ($term['xref'] as $xref) {
+        if (!$this->addCvtermDbxref($cvterm, $xref)) {
+          throw new Excpetion("Cannot add/update cvterm database reference (dbxref).");
+        }
+      }
+    }
+
+    if (array_key_exists('xref_analog', $term)) {
+      foreach ($term['xref_analog'] as $xref) {
+        if (!$this->addCvtermDbxref($cvterm, $xref)) {
+          throw new Excpetion("Cannot add/update cvterm database reference (dbxref).");
+        }
+      }
+    }
+    if (array_key_exists('xref_unk', $term)) {
+      foreach ($term['xref_unk'] as $xref) {
+        if (!$this->addCvtermDbxref($cvterm, $xref)) {
+          throw new Excpetion("Cannot add/update cvterm database reference (dbxref).");
+        }
+      }
+    }
+
+    // add is_a relationships for this cvterm
+    if (array_key_exists('is_a', $term)) {
+      foreach ($term['is_a'] as $is_a) {
+        if (!$this->addRelationship($cvterm, $defaultcv, 'is_a', $is_a, $is_relationship, $default_db)) {
+          throw new Excpetion("Cannot add relationship is_a: $is_a");
+        }
+      }
+    }
+
+    if (array_key_exists('intersection_of', $term)) {
+      //print "WARNING: unhandled tag: intersection_of\n";
+    }
+    if (array_key_exists('union_of', $term)) {
+      //print "WARNING: unhandled tag: union_on\n";
+    }
+    if (array_key_exists('disjoint_from', $term)) {
+      //print "WARNING: unhandled tag: disjoint_from\n";
+    }
+    if (array_key_exists('relationship', $term)) {
+      foreach ($term['relationship'] as $value) {
+        $rel = preg_replace('/^(.+?)\s.+?$/', '\1', $value);
+        $object = preg_replace('/^.+?\s(.+?)$/', '\1', $value);
+        // The Gene Ontology uses 'has_part' for transitive relationships, but
+        // it specifically indicates that 'has_part' should not be used for
+        // grouping annotations.  Unfortunately, this means that when we
+        // try to popoulate the cvtermpath table a 'has_part' relationships
+        // will be used for exactly that purpose: to group annotations.  This
+        // doesn't seem to the be the case for other vocabularies such as the
+        // sequence ontology that uses has_part as primary relationship between
+        // terms. So, when loading the GO, we'll not include has_part
+        // relationships.
+        /*if ($rel == 'has_part' and $cvterm->dbxref_id->db_id->name == 'GO') {
+        continue;
+        }*/
+        if (!$this->addRelationship($cvterm, $defaultcv, $rel, $object, $is_relationship, $default_db)) {
+          throw new Excpetion("Cannot add relationship $rel: $object");
+        }
+      }
+    }
+    if (array_key_exists('replaced_by', $term)) {
+      //print "WARNING: unhandled tag: replaced_by\n";
+    }
+    if (array_key_exists('consider', $term)) {
+      //print "WARNING: unhandled tag: consider\n";
+    }
+    if (array_key_exists('use_term', $term)) {
+      //print "WARNING: unhandled tag: user_term\n";
+    }
+    if (array_key_exists('builtin', $term)) {
+      //print "WARNING: unhandled tag: builtin\n";
+    }
+    return 1;
+  }
+
+  /**
+   * Adds a cvterm relationship
+   *
+   * @param $cvterm
+   *   A database object for the cvterm
+   * @param $rel
+   *   The relationship name
+   * @param $objname
+   *   The relationship term name
+   * @param $defaultcv
+   *   A database object containing a record from the cv table for the
+   *   default controlled vocabulary
+   * @object_is_relationship
+   *   Set to 1 if this term is a relationship term
+   * @default_db
+   *   The name of the default database.
+   *
+   * @ingroup tripal_obo_loader
+   */
+  private function addRelationship($cvterm, $defaultcv, $rel,
+      $objname, $object_is_relationship = 0, $default_db = 'OBO_REL') {
+
+    // make sure the relationship cvterm exists
+    $term = array(
+      'name' => $rel,
+      'id' => "$default_db:$rel",
+      'definition' => '',
+      'is_obsolete' => 0,
+      'cv_name' => $defaultcv,
+      'is_relationship' => TRUE,
+      'db_naame' => $default_db
+    );
+    $relcvterm = tripal_insert_cvterm($term, array('update_existing' => FALSE));
+
+    if (!$relcvterm) {
+      // if the relationship term couldn't be found in the default_db provided
+      // then do on more check to find it in the relationship ontology
+      $term = array(
+        'name' => $rel,
+        'id' => "OBO_REL:$rel",
+        'definition' => '',
+        'is_obsolete' => 0,
+        'cv_name' => $defaultcv,
+        'is_relationship' => TRUE,
+        'db_name' => 'OBO_REL'
+      );
+      $relcvterm = tripal_insert_cvterm($term, array('update_existing' => FALSE));
+      if (!$relcvterm) {
+        throw new Excpetion("Cannot find the relationship term in the current ontology or in the relationship ontology: $rel\n");
+      }
+    }
+
+    // get the object term
+    $oterm = $this->getTerm($objname);
+    if (!$oterm) {
+      throw new Excpetion("Could not find object term $objname\n");
+    }
+
+    $objterm = array();
+    $objterm['id']            = $oterm['id'][0];
+    $objterm['name']          = $oterm['name'][0];
+    if (array_key_exists('def', $oterm)) {
+      $objterm['definition']           = $oterm['def'][0];
+    }
+    if (array_key_exists('subset', $oterm)) {
+      $objterm['subset']      = $oterm['subset'][0];
+    }
+    if (array_key_exists('namespace', $oterm)) {
+      $objterm['namespace']   = $oterm['namespace'][0];
+    }
+    if (array_key_exists('is_obsolete', $oterm)) {
+      $objterm['is_obsolete'] = $oterm['is_obsolete'][0];
+    }
+
+    $objterm['cv_name' ] = $defaultcv;
+    $objterm['is_relationship'] = $object_is_relationship;
+    $objterm['db_name'] = $default_db;
+
+    $objcvterm = tripal_insert_cvterm($objterm, array('update_existing' => TRUE));
+    if (!$objcvterm) {
+      throw new Excpetion("Cannot add cvterm " . $oterm['name'][0]);
+    }
+
+    // check to see if the cvterm_relationship already exists, if not add it
+    $values = array(
+      'type_id'    => $relcvterm->cvterm_id,
+      'subject_id' => $cvterm->cvterm_id,
+      'object_id'  => $objcvterm->cvterm_id
+    );
+    $result = chado_select_record('cvterm_relationship', array('*'), $values);
+    if (count($result) == 0) {
+      $options = array('return_record' => FALSE);
+      $success = chado_insert_record('cvterm_relationship', $values, $options);
+      if (!$success) {
+        throw new Excpetion("Cannot add term relationship: '$cvterm->name' $rel '$objcvterm->name'");
+      }
+    }
+
+    return TRUE;
+  }
+
+  /**
+   * Retreives the term array from the temp loading table for a given term id.
+   *
+   * @param id
+   *   The id of the term to retrieve
+   *
+   * @ingroup tripal_obo_loader
+   */
+  private function getTerm($id) {
+    $values = array('id' => $id);
+    $result = chado_select_record('tripal_obo_temp', array('stanza'), $values);
+    if (count($result) == 0) {
+      return FALSE;
+    }
+    return unserialize(base64_decode($result[0]->stanza));
+  }
+
+  /**
+   * Adds the synonyms to a term
+   *
+   * @param term
+   *   An array representing the cvterm. It must have a 'synonym' key/value pair.
+   * @param cvterm
+   *   The database object of the cvterm to which the synonym will be added.
+   *
+   * @ingroup tripal_obo_loader
+   */
+  private function addSynonym($term, $cvterm) {
+
+    // make sure we have a 'synonym_type' vocabulary
+    $syncv = tripal_insert_cv(
+        'synonym_type',
+        'A local vocabulary added for synonym types.'
+    );
+
+    // now add the synonyms
+    if (array_key_exists('synonym', $term)) {
+      foreach ($term['synonym'] as $synonym) {
+
+        // separate out the synonym definition and the synonym type
+        $def = preg_replace('/^\s*"(.*)"\s*.*$/', '\1', $synonym);
+        // the scope will be 'EXACT', etc...
+        $scope = drupal_strtolower(preg_replace('/^.*"\s+(.*?)\s+.*$/', '\1', $synonym));
+        if (!$scope) {  // if no scope then default to 'exact'
+          $scope = 'exact';
+        }
+
+        // make sure the synonym type exists in the 'synonym_type' vocabulary
+        $values = array(
+          'name' => $scope,
+          'cv_id' => array(
+            'name' => 'synonym_type',
+          ),
+        );
+        $syntype = tripal_get_cvterm($values);
+
+        // if it doesn't exist then add it
+        if (!$syntype) {
+          // build a 'term' object so we can add the missing term
+          $term = array(
+            'name' => $scope,
+            'id' => "synonym_type:$scope",
+            'definition' => '',
+            'is_obsolete' => 0,
+            'cv_name' => $syncv->name,
+            'is_relationship' => FALSE
+          );
+          $syntype = tripal_insert_cvterm($term, array('update_existing' => TRUE));
+          if (!$syntype) {
+            throw new Excpetion("Cannot add synonym type: internal:$scope");
+          }
+        }
+
+        // make sure the synonym doesn't already exists
+        $values = array(
+          'cvterm_id' => $cvterm->cvterm_id,
+          'synonym' => $def
+        );
+        $results = chado_select_record('cvtermsynonym', array('*'), $values);
+        if (count($results) == 0) {
+          $values = array(
+            'cvterm_id' => $cvterm->cvterm_id,
+            'synonym' => $def,
+            'type_id' => $syntype->cvterm_id
+          );
+          $options = array('return_record' => FALSE);
+          $success = chado_insert_record('cvtermsynonym', $values, $options);
+          if (!$success) {
+            throw new Excpetion("Failed to insert the synonym for term: $name ($def)");
+          }
+        }
+
+        // now add the dbxrefs for the synonym if we have a comma in the middle
+        // of a description then this will cause problems when splitting os lets
+        // just change it so it won't mess up our splitting and then set it back
+        // later.
+        /**
+        $synonym = preg_replace('/(".*?),\s(.*?")/','$1,_$2',$synonym);
+        $dbxrefs = preg_split("/, /",preg_replace('/^.*\[(.*?)\]$/','\1',$synonym));
+        foreach ($dbxrefs as $dbxref) {
+        $dbxref = preg_replace('/,_/',", ",$dbxref);
+        if ($dbxref) {
+        $this->addCvtermDbxref($syn,$dbxref);
+        }
+        }
+        */
+      }
+    }
+
+    return TRUE;
+  }
+
+  /**
+   * Parse the OBO file and populate the templ loading table
+   *
+   * @param $file
+   *   The path on the file system where the ontology can be found
+   * @param $header
+   *   An array passed by reference that will be populated with the header
+   *   information from the OBO file
+   *
+   * @ingroup tripal_obo_loader
+   */
+  private function parse($obo_file, &$header) {
+    $in_header = 1;
+    $stanza = array();
+    $default_db = '';
+    $line_num = 0;
+    $num_read = 0;
+
+    $filesize = filesize($obo_file);
+    $this->setTotalItems($filesize);
+    $this->setItemsHandled(0);
+
+    // iterate through the lines in the OBO file and parse the stanzas
+    $fh = fopen($obo_file, 'r');
+    while ($line = fgets($fh)) {
+
+      $line_num++;
+      $size = drupal_strlen($line);
+      $num_read += $size;
+      $line = trim($line);
+      $this->setItemsHandled($num_read);
+
+      // remove newlines
+      $line = rtrim($line);
+
+      // remove any special characters that may be hiding
+      $line = preg_replace('/[^(\x20-\x7F)]*/', '', $line);
+
+      // skip empty lines
+      if (strcmp($line, '') == 0) {
+        continue;
+      }
+
+      //remove comments from end of lines
+      $line = preg_replace('/^(.*?)\!.*$/', '\1', $line);  // TODO: if the explamation is escaped
+
+      // at the first stanza we're out of header
+      if (preg_match('/^\s*\[/', $line)) {
+        $in_header = 0;
+
+        // store the stanza we just finished reading
+        if (sizeof($stanza) > 0) {
+          // add the term to the temp table
+          $values = array(
+            'id' => $stanza['id'][0],
+            'stanza' => base64_encode(serialize($stanza)),
+            'type' => $type,
+          );
+          $success = chado_insert_record('tripal_obo_temp', $values);
+          if (!$success) {
+            throw new Exception("Cannot insert stanza into temporary table.");
+          }
+
+        }
+        // get the stanza type:  Term, Typedef or Instance
+        $type = preg_replace('/^\s*\[\s*(.+?)\s*\]\s*$/', '\1', $line);
+
+        // start fresh with a new array
+        $stanza = array();
+        continue;
+      }
+      // break apart the line into the tag and value but ignore any escaped colons
+      preg_replace("/\\:/", "|-|-|", $line); // temporarily replace escaped colons
+      $pair = explode(":", $line, 2);
+      $tag = $pair[0];
+      $value = ltrim(rtrim($pair[1]));// remove surrounding spaces
+
+      // if this is the ID then look for the default DB
+      $matches = array();
+      if ($tag == 'id' and preg_match('/^(.+?):.*$/', $value, $matches)) {
+        $default_db = $matches[1];
+      }
+
+      $tag = preg_replace("/\|-\|-\|/", "\:", $tag); // return the escaped colon
+      $value = preg_replace("/\|-\|-\|/", "\:", $value);
+      if ($in_header) {
+        if (!array_key_exists($tag, $header)) {
+          $header[$tag] = array();
+        }
+        $header[$tag][] = $value;
+      }
+      else {
+        if (!array_key_exists($tag, $stanza)) {
+          $stanza[$tag] = array();
+        }
+        $stanza[$tag][] = $value;
+      }
+    }
+    // now add the last term in the file
+    if (sizeof($stanza) > 0) {
+      $values = array(
+        'id' => $stanza['id'][0],
+        'stanza' => base64_encode(serialize($stanza)),
+        'type' => $type,
+      );
+      chado_insert_record('tripal_obo_temp', $values);
+      if (!$success) {
+        throw new Excpetion("Cannot insert stanza into temporary table.");
+      }
+      $this->setItemsHandled($num_read);
+    }
+    return $default_db;
+  }
+
+  /**
+   * Adds a database reference to a cvterm
+   *
+   * @param cvterm
+   *   The database object of the cvterm to which the synonym will be added.
+   * @param xref
+   *   The cross refernce.  It should be of the form from the OBO specification
+   *
+   * @ingroup tripal_obo_loader
+   */
+  private function addCvtermDbxref($cvterm, $xref) {
+
+    $dbname = preg_replace('/^(.+?):.*$/', '$1', $xref);
+    $accession = preg_replace('/^.+?:\s*(.*?)(\{.+$|\[.+$|\s.+$|\".+$|$)/', '$1', $xref);
+    $description = preg_replace('/^.+?\"(.+?)\".*?$/', '$1', $xref);
+    $dbxrefs = preg_replace('/^.+?\[(.+?)\].*?$/', '$1', $xref);
+
+    if (!$accession) {
+      throw new Excpetion("Cannot add a dbxref without an accession: '$xref'");
+    }
+
+    // if the xref is a database link, handle that specially
+    if (strcmp($dbname, 'http') == 0) {
+      $accession = $xref;
+      $dbname = 'URL';
+    }
+
+    // add the database
+    $db = tripal_insert_db(array('name' => $dbname));
+    if (!$db) {
+      throw new Excpetion("Cannot find database '$dbname' in Chado.");
+    }
+
+    // now add the dbxref
+    $dbxref = $this->addDbxref($db->db_id, $accession, '', $description);
+    if (!$dbxref) {
+      throw new Excpetion("Cannot find or add the database reference (dbxref)");
+    }
+
+    // finally add the cvterm_dbxref but first check to make sure it exists
+    $values = array(
+      'cvterm_id' => $cvterm->cvterm_id,
+      'dbxref_id' => $dbxref->dbxref_id,
+    );
+    $result = chado_select_record('cvterm_dbxref', array('*'), $values);
+    if (count($result) == 0) {
+      $ins_options = array('return_record' => FALSE);
+      $result = chado_insert_record('cvterm_dbxref', $values, $ins_options);
+      if (!$result) {
+        throw new Excpetion("Cannot add cvterm_dbxref: $xref");
+      }
+    }
+
+    return TRUE;
+  }
+
+  /**
+   * Adds a property to a cvterm
+   *
+   * @param cvterm
+   *   A database object for the cvterm to which properties will be added
+   * @param $property
+   *   The name of the property to add
+   * @param $value
+   *   The value of the property
+   * @param rank
+   *   The rank of the property
+   *
+   * @ingroup tripal_obo_loader
+   */
+  private function addCvtermProp($cvterm, $property, $value, $rank) {
+
+    // make sure the 'cvterm_property_type' CV exists
+    $cv = tripal_insert_cv('cvterm_property_type', '');
+    if (!$cv) {
+      throw new Excpetion("Cannot add/find cvterm_property_type cvterm");
+    }
+
+    // get the property type cvterm.  If it doesn't exist then we want to add it
+    $values = array(
+      'name' => $property,
+      'cv_id' => $cv->cv_id,
+    );
+    $results = chado_select_record('cvterm', array('*'), $values);
+    if (count($results) == 0) {
+      $term = array(
+        'name' => $property,
+        'id' => "internal:$property",
+        'definition' => '',
+        'is_obsolete' => 0,
+        'cv_name' => $cv->name,
+        'is_relationship' => FALSE,
+      );
+      $cvproptype = tripal_insert_cvterm($term, array('update_existing' => FALSE));
+      if (!$cvproptype) {
+        throw new Excpetion("Cannot add cvterm property: internal:$property");
+      }
+    }
+    else {
+      $cvproptype = $results[0];
+    }
+
+    // remove any properties that currently exist for this term.  We'll reset them
+    if ($rank == 0) {
+      $values = array('cvterm_id' => $cvterm->cvterm_id);
+      $success = chado_delete_record('cvtermprop', $values);
+      if (!$success) {
+        throw new Excpetion("Could not remove existing properties to update property $property for term\n");
+      }
+    }
+
+    // now add the property
+    $values = array(
+      'cvterm_id' => $cvterm->cvterm_id,
+      'type_id' => $cvproptype->cvterm_id,
+      'value' => $value,
+      'rank' => $rank,
+    );
+    $options = array('return_record' => FALSE);
+    $result = chado_insert_record('cvtermprop', $values, $options);
+    if (!$result) {
+      throw new Excpetion("Could not add property $property for term\n");
+    }
+    return TRUE;
+  }
+
+  /**
+   * Adds a database cross reference to a cvterm
+   *
+   * @param db_id
+   *   The database ID of the cross reference
+   * @param accession
+   *   The cross reference's accession
+   * @param $version
+   *   The version of the dbxref
+   * @param $description
+   *   The description of the cross reference
+   *
+   * @ingroup tripal_obo_loader
+   */
+  private function addDbxref($db_id, $accession, $version='', $description='') {
+
+    // check to see if the dbxref exists if not, add it
+    $values = array(
+      'db_id' => $db_id,
+      'accession' => $accession,
+    );
+    $result = chado_select_record('dbxref', array('dbxref_id'), $values);
+    if (count($result) == 0) {
+      $ins_values = array(
+        'db_id'       => $db_id,
+        'accession'   => $accession,
+        'version'     => $version,
+        'description' => $description,
+      );
+      $ins_options = array('return_record' => FALSE);
+      $result = chado_insert_record('dbxref', $ins_values, $ins_options);
+      if (!$result) {
+        throw new Excpetion("Failed to insert the dbxref record $accession");
+      }
+      $result = chado_select_record('dbxref', array('dbxref_id'), $values);
+    }
+    return $result[0];
+  }
+}
+
+/**
+ * Ajax callback for the OBOImporter::form() function.
+ */
+function tripal_cv_obo_form_ajax_callback($form, $form_state) {
+  return $form['class_elements']['obo_existing'];
+}

+ 0 - 3
tripal_chado/includes/loaders/tripal_chado.obo_loader.inc

@@ -1454,6 +1454,3 @@ function tripal_cv_obo_add_dbxref($db_id, $accession, $version='', $description=
 }
 
 
-function tripal_cv_obo_form_ajax_callback($form, $form_state) {
-  return $form['obo_existing'];
-}

+ 20 - 18
tripal_chado/includes/tripal_chado.field_storage.inc

@@ -1034,6 +1034,25 @@ function tripal_chado_field_storage_bundle_mapping_form_validate($form, &$form_s
   if (array_key_exists('prop_term_name', $form_state['values'])
       and $form_state['values']['prop_term_name']) {
     $default['prop_term_name'] = $form_state['values']['prop_term_name'];
+
+    // Make sure we have only one property cvterm selected
+    $num_selected = 0;
+    foreach ($form_state['values'] as $key => $value) {
+      $matches = array();
+      if (preg_match("/^prop_term-(\d+)$/", $key, $matches) and
+          $form_state['values']['prop_term-' . $matches[1]]) {
+        $cvterm_id = $matches[1];
+        $term = chado_generate_var('cvterm', array('cvterm_id' => $cvterm_id));
+        $num_selected++;
+      }
+    }
+    if ($num_selected == 0) {
+      form_set_error('', 'Please select at least one property term.');
+    }
+    else if ($num_selected > 1) {
+      form_set_error('prop_term-' . $cvterm_id, 'Please select only one property term from the list below.');
+    }
+    $form_state['prop_term'] = $term;
   }
   if (array_key_exists('prop_term_value', $form_state['values'])
       and $form_state['values']['prop_term_value']) {
@@ -1045,24 +1064,7 @@ function tripal_chado_field_storage_bundle_mapping_form_validate($form, &$form_s
     form_set_error('base_chado_table', 'Please select a default table.');
   }
 
-  // Make sure we have only one property cvterm selected
-  $num_selected = 0;
-  foreach ($form_state['values'] as $key => $value) {
-    $matches = array();
-    if (preg_match("/^prop_term-(\d+)$/", $key, $matches) and
-        $form_state['values']['prop_term-' . $matches[1]]) {
-      $cvterm_id = $matches[1];
-      $term = chado_generate_var('cvterm', array('cvterm_id' => $cvterm_id));
-      $num_selected++;
-    }
-  }
-  if ($num_selected == 0) {
-    form_set_error('', 'Please select at least one property term.');
-  }
-  else if ($num_selected > 1) {
-    form_set_error('prop_term-' . $cvterm_id, 'Please select only one property term from the list below.');
-  }
-  $form_state['prop_term'] = $term;
+
 
 }
 /**