diff --git a/includes/TripalFields/ncit__raw_data/ncit__raw_data.inc b/includes/TripalFields/ncit__raw_data/ncit__raw_data.inc new file mode 100644 index 0000000..aa1df10 --- /dev/null +++ b/includes/TripalFields/ncit__raw_data/ncit__raw_data.inc @@ -0,0 +1,255 @@ + 'tripal_no_storage', + // It is expected that all fields set a 'value' in the load() function. + // In many cases, the value may be an associative array of key/value pairs. + // In order for Tripal to provide context for all data, the keys should + // be a controlled vocabulary term (e.g. rdfs:type). Keys in the load() + // function that are supported by the query() function should be + // listed here. + 'browseable_keys' => array(), + ); + + // Provide a list of instance specific settings. These can be access within + // the instanceSettingsForm. When the instanceSettingsForm is submitted + // then Drupal with automatically change these settings for the instance. + // It is recommended to put settings at the instance level whenever possible. + // If you override this variable in a child class be sure to replicate the + // term_name, term_vocab, term_accession and term_fixed keys as these are + // required for all TripalFields. + public static $default_instance_settings = array( + // The short name for the vocabulary (e.g. schema, SO, GO, PATO, etc.). + 'term_vocabulary' => 'NCIT', + // The name of the term. + 'term_name' => 'Raw Data', + // The unique ID (i.e. accession) of the term. + 'term_accession' => 'C142663', + // Set to TRUE if the site admin is not allowed to change the term + // type, otherwise the admin can change the term mapped to a field. + 'term_fixed' => FALSE, + // Indicates if this field should be automatically attached to display + // or web services or if this field should be loaded separately. This + // is convenient for speed. Fields that are slow should for loading + // should have auto_attach set to FALSE so tha their values can be + // attached asynchronously. + 'auto_attach' => FALSE, + // The table where the options for this specific field are stored. + // This can be one of trpfancy_browse_options or trpfancy_browse_options_per_entity + // based on admin configuration. Default: trpfancy_browse_options. + 'option_storage' => '', + // A list of browser types this field intends to provide. + 'browser_types' => '', + ); + + // A boolean specifying that users should not be allowed to create + // fields and instances of this field type through the UI. Such + // fields can only be created programmatically with field_create_field() + // and field_create_instance(). + public static $no_ui = FALSE; + // A boolean specifying that the field will not contain any data. This + // should exclude the field from web services or downloads. An example + // could be a quick browse field that appears on the page that redirects + // the user but otherwise provides no data. + public static $no_data = TRUE; + + /** + * Loads the field values from the underlying data store. + * + * @param $entity + * + * @return + * An array of the following format: + * $entity->{$field_name}['und'][0]['value'] = $value; + * where: + * - $entity is the entity object to which this field is attached. + * - $field_name is the name of this field + * - 'und' is the language code (in this case 'und' == undefined) + * - 0 is the cardinality. Increment by 1 when more than one item is + * available. + * - 'value' is the key indicating the value of this field. It should + * always be set. The value of the 'value' key will be the contents + * used for web services and for downloadable content. The value + * should be of the follow format types: 1) A single value (text, + * numeric, etc.) 2) An array of key value pair. 3) If multiple entries + * then cardinality should incremented and format types 1 and 2 should + * be used for each item. + * The array may contain as many other keys at the same level as 'value' + * but those keys are for internal field use and are not considered the + * value of the field. + */ + public function load($entity) { + global $user; + + // User permissions. + $rawpheno_permission = array('access rawpheno', 'download rawpheno'); + $count_permission = 0; + foreach($rawpheno_permission as $permission) { + if (user_access($permission, $user)) { + $count_permission++; + } + } + + // If user has no permission to begin with, skip all and report + // raw phenotypes not available. + $field_name = $this->instance['field_name']; + $entity->{$field_name}['und'][0]['value'] = array(); + + if (user_is_logged_in() && $count_permission == 2) { + // # TRAITS/EXPERIMENT/LOCATION: + $traits = array(); + // This query is identical to the rawphenotypes download page. + // Get all experiments (by plant id) where germplasm was used. + $all_experiment_locations = chado_query(" + SELECT p2.project_id, p2.name, value AS location + FROM pheno_plantprop + INNER JOIN pheno_plant_project AS p1 USING (plant_id) + INNER JOIN {project} AS p2 ON p1.project_id = p2.project_id + WHERE + type_id = (SELECT cvterm_id FROM {cvterm} cvt LEFT JOIN {cv} cv ON cv.cv_id = cvt.cv_id + WHERE cvt.name = 'Location' AND cv.name = 'phenotype_plant_property_types') AND + plant_id IN (SELECT plant_id FROM pheno_plant WHERE stock_id = :germplasm GROUP BY plant_id) + GROUP BY p2.project_id, p2.name, value + ", array(':germplasm' => $entity->chado_record->stock_id)); + $experiment_locations = $all_experiment_locations->fetchAll(); + + // Only when germplasm returned rawphenotypic data. + if (count($experiment_locations) > 0) { + // User appointed experiments. + // See includes/rawpheno.function.measurements.inc file for function definition + // Given the user id this function returns an array of chado projects keyed by project_id + // that the user has permission to see. + $user_experiment = rawpheno_function_user_project($user->uid); + $user_experiment = array_keys($user_experiment); + + // All traits in experiment and location. + $sql_cvterm = " + SELECT c_j.cvterm_json->>'id', c_j.cvterm_json->>'name' FROM ( + SELECT JSON_BUILD_OBJECT('id', cvterm_id, 'name', name) AS cvterm_json FROM {cvterm} WHERE cvterm_id = ANY (( + SELECT STRING_TO_ARRAY(list_id.all_traits, ',') FROM ( + SELECT string_agg(DISTINCT all_traits, ',') AS all_traits + FROM {rawpheno_rawdata_mview} + WHERE + location IN(:location) + AND plant_id IN (SELECT plant_id FROM pheno_plant_project WHERE project_id = :project_id) + ) AS list_id + )::int[]) + ) AS c_j + WHERE c_j.cvterm_json->>'name' NOT IN ('Rep', 'Entry', 'Location', 'Name', 'Plot', 'Planting Date (date)', '# of Seeds Planted (count)') + ORDER BY c_j.cvterm_json->>'name' ASC + "; + + $trait_experiment_location = array(); + $cache_exp = array(); + $cache_loc = array(); + + foreach($experiment_locations as $item) { + $cache_exp[] = $item->project_id; + $cache_loc[] = $item->location; + + $trait_set = chado_query($sql_cvterm, array(':location' => $item->location, ':project_id' => $item->project_id)) + ->fetchAllKeyed(0, 1); + + if ($trait_set) { + foreach($trait_set as $trait_id => $trait_name) { + $allow = (in_array($item->project_id, $user_experiment)) ? 1 : 0; + + // Save basic information about the trait (name + id key) and all experiment + location it was measured. + $entity->{$field_name}['und'][0]['value']['hydra:member'][ $trait_name . '_' . $trait_id ][] = array( + 'phenotype_customfield_terms:id' => $item->project_id, // Project id number. + 'phenotype_customfield_terms:name' => $item->name, // Project name. + 'phenotype_customfield_terms:location' => $item->location, // Location in a project trait was measured. + 'phenotype_customfield_terms:user_experiment' => $allow // Does user have permission? + ); + } + } + } + + // Save a complete summary count as a quick raw phenotypic data summary related to the germplasm. + $entity->{$field_name}['und'][0]['value']['phenotype_customfield_terms:summary'] = array( + 'phenotype_customfield_terms:experiment' => count(array_unique($cache_exp)), // Summary count of experiments. + 'phenotype_customfield_terms:location' => count(array_unique($cache_loc)), // Summary count of locations. + 'phenotype_customfield_terms:trait' => count($entity->{$field_name}['und'][0]['value']['hydra:member']), // Summary count of traits. + ); + } + } + } + + /** + * Provides a form for the 'Field Settings' of an instance of this field. + * + * This function corresponds to the hook_field_instance_settings_form() + * function of the Drupal Field API. + * + * Validation of the instance settings form is not supported by Drupal, but + * the TripalField class does provide a mechanism for supporting validation. + * To allow for validation of your setting form you must call the parent + * in your child class: + * + * @code + * $element = parent::instanceSettingsForm(); + * @endcode + * + * Please note, the form generated with this function does not easily + * support AJAX calls in the same way that other Drupal forms do. If you + * need to use AJAX you must manually alter the $form in your ajax call. + * The typical way to handle updating the form via an AJAX call is to make + * the changes in the form function itself but that doesn't work here. + */ + public function instanceSettingsForm() { + + // Retrieve the current settings. + // If this field was just created these will contain the default values. + $settings = $this->instance['settings']; + + // Allow the parent Tripal Field to set up the form element for us. + $element = parent::instanceSettingsForm(); + + return $element; + } + + /** + * @see ChadoField::elementInfo() + * + */ + public function elementInfo() { + $field_term = $this->getFieldTermID(); + return array( + $field_term => array( + 'operations' => array('eq', 'ne', 'contains', 'starts'), + 'sortable' => TRUE, + 'searchable' => TRUE, + ), + ); + } +} \ No newline at end of file diff --git a/includes/TripalFields/ncit__raw_data/ncit__raw_data_formatter.inc b/includes/TripalFields/ncit__raw_data/ncit__raw_data_formatter.inc new file mode 100644 index 0000000..a7c71bf --- /dev/null +++ b/includes/TripalFields/ncit__raw_data/ncit__raw_data_formatter.inc @@ -0,0 +1,164 @@ +chado_record->stock_id; + // Name (stock name) of the current germplasm. + $germplasm_name = $entity->chado_record->name; + + // Reference directory path. + $base_path = $GLOBALS['base_url'] . '/'; + $module_path = drupal_get_path('module', 'rawpheno') . '/'; + $theme_path = $base_path . $module_path . '/includes/TripalFields/ncit__raw_data/theme/'; + + // Append image as bullet points, header icon and export button or link. + $img = ''; + + + // CONSTRUCT SUMMARY TABLE: + // Each row will contain the trait name (trait header), location and experiment combination (LOCATION/Experiment header) + // as a select box and download link (download icon header). + + // # TABLE HEADER: + $table_header = array( + sprintf($img, '', $theme_path . 'icon-download-all.jpg', 'Download all for this trait'), + 'Trait', + 'Filter by:', + sprintf($img, '', $theme_path . 'icon-download.jpg', 'Download') + ); + + // 2 select fields are required: + // A. Select field to filter by Location + Experiment. + // B. Select field to filter by Experiment (All location included). + // Depending on which Filter by option user selects, load corresponding select. + + // # TABLE ROWS: + $table_row = array(); + foreach($germplasm_raw_phenotypes as $trait => $exp_loc) { + $tmp = explode('_', $trait); + $trait = array('id' => $tmp[1], 'name' => $tmp[0]); + + $table_row[] = array( + sprintf($img, $trait['id'] . '-all', $theme_path . 'icon-export.png', 'Download all for this trait'), + ucfirst($trait['name']), + $this->create_select($trait['id']), + sprintf($img, $trait['id'], $theme_path . 'icon-export.png', 'Download') + ); + } + + // # THEME TABLE: + $summary_table = theme('table', array( + 'header' => $table_header, + 'rows' => $table_row, + 'sticky' => FALSE, + 'attributes' => array('id' => 'rawphenotypes-germplasm-field-table')) + ); + + + // Make field elements generated by formatter avaiable to the template as template vars. + // @see template file for this field in rawphenotypes/theme directory. + $markup = theme('rawpheno_germplasm_field', array( + 'element_id' => 'rawphenotypes-germplasm-raw-phenotypes-field', + 'summary_table' => array( + 'table' => $summary_table, + 'headers' => array( + 'germplasm' => $germplasm_name, + 'experiments' => $summary_values['phenotype_customfield_terms:experiment'], + 'locations' => $summary_values['phenotype_customfield_terms:location'], + 'traits' => $summary_values['phenotype_customfield_terms:trait'], + ) + ) + )); + + // Rawphenotypes download link: + drupal_add_js(array('rawpheno' => array('exportLink' => $base_path . '/phenotypes/raw/download')), array('type' => 'setting')); + // Autocomplete UI. + drupal_add_library('system', 'ui.autocomplete'); + // All datapoints available to JS to populate select field. + drupal_add_js(array('rawpheno' => array('germRawdata' => $germplasm_raw_phenotypes, 'germ' => $germplasm_id)), array('type' => 'setting')); + + + // Construct field render array. + $element[0] = array( + '#type' => 'markup', + '#markup' => $markup, + '#attached' => array( + 'css' => array($module_path . 'theme/css/rawpheno.germplasmfield.style.css'), + 'js' => array($module_path . 'theme/js/rawpheno.germplasmfield.script.js') + ) + ); + } + + return $element; + } + + /** + * Create select field. + * + * @param $trait_id + * Trait id number (cvterm id number) used as value for id attribute. + */ + public function create_select($trait_id) { + $attributes = array( + 'id' => 'rawphenotypes-germplasm-field-filterby-' . $trait_id, + 'class' => array('rawphenotypes-germplasm-field-filterby') + ); + + $options = array( + 0 => '---' + ); + + return theme('select', array('element' => array('#attributes' => $attributes, '#options' => $options))); + } +} \ No newline at end of file diff --git a/includes/TripalFields/ncit__raw_data/ncit__raw_data_widget.inc b/includes/TripalFields/ncit__raw_data/ncit__raw_data_widget.inc new file mode 100644 index 0000000..923fd48 --- /dev/null +++ b/includes/TripalFields/ncit__raw_data/ncit__raw_data_widget.inc @@ -0,0 +1,44 @@ +data_table) AND ($bundle->data_table == 'stock')) { + // Insert auxiliary terms used by Raw Data field. + // Create cv to hold terms. + $cv = 'phenotype_customfield_terms'; + chado_insert_cv($cv, 'vocabulary term to hold terms used by Raw Data field'); + + // Insert terms to cv above. + $terms = array('experiment', 'id', 'location', 'name', 'summary', 'trait', 'user_experiment'); + foreach($terms as $term) { + tripal_insert_cvterm(array( + 'id' => 'rawpheno_tripal:' . $term, + 'name' => $term, + 'definition' => $term, + 'cv_name' => $cv + )); + } + + // Raw Data term. + tripal_insert_cvterm(array( + 'id' => 'NCIT:C142663', + 'name' => 'Raw Data', + 'cv_name' => 'NCIT', + 'definition' => 'The original information, collected from the primary source. Used in Germplasm Raw Phenotypes Field.', + )); + + $field_name = 'ncit__raw_data'; + $field_type = 'ncit__raw_data'; + $fields[$field_name] = array( + 'field_name' => $field_name, + 'type' => $field_type, + 'cardinality' => 1, + 'locked' => FALSE, + 'storage' => array( + 'type' => 'field_chado_storage', + ), + ); + } + + return $fields; +} + +/** + * Implements hook_bundle_instances_info(). + * + * This hook tells Drupal/Tripal to create a field instance of a given field type on a + * specific Tripal Content type (otherwise known as the bundle). Make sure to implement + * hook_bundle_create_fields() to create your field type before trying to create an + * instance of that field. + * + * @param $entity_type + * This should be 'TripalEntity' for all Tripal Content. + * @param $bundle + * This object describes the Type of Tripal Entity (e.g. Organism or Gene) the field + * instances are being created for. Thus this hook is called once per Tripal Content Type on your + * site. The name of the bundle is the machine name of the type (e.g. bio_data_1) and + * the label of the bundle (e.g. Organism) is what you see in the interface. Since the + * label can be changed by site admin, we suggest checking the data_table to determine + * if this is the entity you want to add field instances to. + * @return + * An array of field instance definitions. This is where you can define the defaults + * for any settings you use in your field. Each entry in this array will be used to + * create an instance of an already existing field. + */ +function rawpheno_bundle_instances_info($entity_type, $bundle) { + $instances = array(); + + // IN GERMPLASM PAGE ONLY: + if (isset($bundle->data_table) AND ($bundle->data_table == 'stock')) { + // Number of Values Recorded. + $field_name = 'ncit__raw_data'; + $field_type = 'ncit__raw_data'; + $instances[$field_name] = array( + 'field_name' => $field_name, + 'entity_type' => $entity_type, + 'bundle' => $bundle->name, + 'label' => 'Germplasm Raw Phenotypes', + 'description' => 'Field to add interface to raw phenotypes available to a germplasm.', + 'required' => FALSE, + 'settings' => array( + 'term_vocabulary' => 'NCIT', + 'term_name' => 'Raw Data', + 'term_accession' => 'C142663', + 'auto_attach' => FALSE, + 'chado_table' => $bundle->data_table, + 'chado_column' => $bundle->data_table . '_id', + 'base_table' => $bundle->data_table, + ), + 'widget' => array( + 'type' => 'ncit__raw_data_widget', + 'settings' => array(), + ), + 'display' => array( + 'default' => array( + 'label' => 'hidden', + 'type' => 'ncit__raw_data_formatter', + 'settings' => array(), + ), + ), + ); + } + + return $instances; +} \ No newline at end of file diff --git a/include/rawpheno.admin.form.inc b/includes/rawpheno.admin.form.inc similarity index 99% rename from include/rawpheno.admin.form.inc rename to includes/rawpheno.admin.form.inc index ce5dbe3..206c3f3 100644 --- a/include/rawpheno.admin.form.inc +++ b/includes/rawpheno.admin.form.inc @@ -129,7 +129,7 @@ function rawpheno_admin_page($form, &$form_state) { // Include function to manage column headers, cv terms and variable names. -module_load_include('inc', 'rawpheno', 'include/rawpheno.function.measurements'); +module_load_include('inc', 'rawpheno', 'includes/rawpheno.function.measurements'); /** diff --git a/include/rawpheno.backup.form.inc b/includes/rawpheno.backup.form.inc similarity index 100% rename from include/rawpheno.backup.form.inc rename to includes/rawpheno.backup.form.inc diff --git a/include/rawpheno.download.form.inc b/includes/rawpheno.download.form.inc similarity index 87% rename from include/rawpheno.download.form.inc rename to includes/rawpheno.download.form.inc index c48bce2..8ef6748 100644 --- a/include/rawpheno.download.form.inc +++ b/includes/rawpheno.download.form.inc @@ -1,366 +1,414 @@ - 'markup', - '#markup' => t('View Summary ❯'), - ); - - // PROJECT SELECT BOX. - if (isset($form_state['values']['sel_project'])) { - // Project selected. - $project_selected = $form_state['values']['sel_project']; - } - - // Sort the project names according to Planting Date. - // Put project with recently uploaded data/ based on planting year - // first in the list. - $sql = "SELECT project_id, name - FROM {project} AS t1 - RIGHT JOIN pheno_plant_project AS t2 USING (project_id) - LEFT JOIN pheno_measurements AS t3 USING (plant_id) - WHERE t3.type_id = (SELECT cvterm_id FROM {cvterm} WHERE name = 'Planting Date (date)' LIMIT 1) - GROUP BY project_id, name, t3.value - ORDER BY t3.value DESC"; - - $opt_project = chado_query($sql) - ->fetchAllKeyed(); - - // Project options: - if (count($opt_project) <= 0) { - // Module has no projects w/ data yet. - return $form; - } - else { - // Remove any duplicates from the sorted list. - $opt_project = array_unique($opt_project, SORT_REGULAR); - } - - // AJAX wrapper. - // Main wrapper - $form['ajax_container'] = array( - '#type' => 'markup', - '#prefix' => '
', - '#suffix' => '
', - ); - - // This a hidden field containing all project id. - // This field will allow callback functions to get all project ids which is - // the equivalent of the option select all project from the project select box. - $form['ajax_container']['txt_project'] = array( - '#type' => 'hidden', - '#value' => implode(',', array_keys($opt_project)), - ); - - $form['ajax_container']['sel_project'] = array( - '#type' => 'select', - '#title' => t('Experiment'), - '#options' => $opt_project, - '#multiple' => FALSE, - '#id' => 'download-sel-project', - '#ajax' => array( - 'event' => 'change', - 'callback' => 'rawpheno_download_get_locations_traits', - 'wrapper' => 'download-ajax-wrapper', - 'progress' => array('type' => '', 'message' => '') - ), - ); - - // This will reset the project select box on load and page refresh. - drupal_add_js('jQuery(document).ready(function() { - jQuery("#download-sel-project").val(0); - })', 'inline'); - - // Define the project ids required by the next field. - if (isset($project_selected)) { - // When a project is selected. Default to the project selected. - $project_id = $project_selected; - } - else { - // No project select. This is the default to the first project. - $p = array_keys($opt_project); - $project_id = reset($p); - } - - // All Locations for the default project above. Default project is the - // first project in the list. - $sql = "SELECT DISTINCT value, value AS prj_location - FROM pheno_plantprop - WHERE - type_id = (SELECT cvterm_id FROM {cvterm} cvt LEFT JOIN {cv} cv ON cv.cv_id = cvt.cv_id - WHERE cvt.name = 'Location' AND cv.name = 'phenotype_plant_property_types') AND - plant_id IN (SELECT plant_id FROM pheno_plant_project WHERE project_id IN (:project_id)) - ORDER BY value ASC"; - - $opt_location = chado_query($sql, array(':project_id' => $project_id)) - ->fetchAllKeyed(); - - $form['ajax_container']['sel_location'] = array( - '#type' => 'select', - '#title' => t('Location'), - '#options' => $opt_location, - '#multiple' => TRUE, - '#size' => 7, - '#id' => 'download-sel-location', - '#ajax' => array( - 'event' => 'change', - 'callback' => 'rawpheno_download_get_traits', - 'wrapper' => 'download-ajax-wrapper-traits', - 'progress' => array('type' => '', 'message' => '') - ), - ); - - $form['ajax_container']['chk_select_all_locations'] = array( - '#title' => t('Select all Locations'), - '#type' => 'checkbox', - '#default_value' => 0, - '#ajax' => array( - 'event' => 'change', - 'callback' => 'rawpheno_download_get_locations_traits', - 'wrapper' => 'download-ajax-wrapper', - 'progress' => array('type' => '', 'message' => '') - ), - '#id' => 'chk-select-all-locations', - ); - - $location_id = $opt_location; - - // Manage environment data file option. - // Allow option when a project is selected and project and location combination - // returns an environment data file. - $add_option = FALSE; - - if (isset($project_selected) && $project_selected > 0) { - if (isset($form_state['values']['sel_location']) - && count($form_state['values']['sel_location']) > 0) { - - $location = $form_state['values']['sel_location']; - - $envfile = rawpheno_function_getenv($project_selected, $location); - if ($envfile) { - $add_option = TRUE; - } - } - } - - drupal_add_js(array('rawpheno' => array('envdata_option' => $add_option)), array('type' => 'setting')); - - - // TRAITS. - // Select traits wrapper. - $form['ajax_container']['ajax_container_traits'] = array( - '#type' => 'markup', - '#prefix' => '
', - '#suffix' => '
', - ); - - // Get traits given a location and project. - if (isset($project_selected) && isset($location)) { - $project_id = $project_selected; - $location_id = $location; - } - - // The summarized list of cvterm_ids from MVIEW returned by inner most query will be passed to function that converts - // comma separated values into individual values (cvterm_id numbers) and the result is the parameter of ANY clause - // that will filter cvterms to only those in the list. Final rows are in JSON object and sorted alphabetically by name - // that will be passed on to the select field of rawdata form. - $sql_cvterm = " - SELECT c_j.cvterm_json->>'id', c_j.cvterm_json->>'name' FROM ( - SELECT JSON_BUILD_OBJECT('id', cvterm_id, 'name', name) AS cvterm_json FROM {cvterm} WHERE cvterm_id = ANY (( - SELECT STRING_TO_ARRAY(list_id.all_traits, ',') FROM ( - SELECT string_agg(DISTINCT all_traits, ',') AS all_traits - FROM {rawpheno_rawdata_mview} - WHERE - location IN(:location) - AND plant_id IN (SELECT plant_id FROM pheno_plant_project WHERE project_id = :project_id) - ) AS list_id - )::int[]) - ) AS c_j - WHERE c_j.cvterm_json->>'name' NOT IN ('Rep', 'Entry', 'Location', 'Name', 'Plot', 'Planting Date (date)', '# of Seeds Planted (count)') - ORDER BY c_j.cvterm_json->>'name' ASC - "; - - $trait_set = chado_query($sql_cvterm, array(':location' => $location_id, ':project_id' => $project_id)) - ->fetchAllKeyed(); - - $opt_trait = array_unique($trait_set); - - $form['ajax_container']['ajax_container_traits']['sel_trait'] = array( - '#type' => 'select', - '#title' => t('@trait_count Traits available', array('@trait_count' => count($opt_trait))), - '#options' => $opt_trait, - '#multiple' => TRUE, - '#size' => 15, - '#id' => 'download-sel-trait', - ); - - $form['ajax_container']['chk_select_all_traits'] = array( - '#title' => t('Select all Traits'), - '#type' => 'checkbox', - '#default_value' => 0, - '#id' => 'chk-select-all-traits', - ); - - $form['div_buttons'] = array( - '#prefix' => '
', - '#suffix' => '
', - ); - - $form['div_buttons']['chk_envdata'] = array( - '#title' => t('Include Environment Data (Include Environment Data)', array('@img' => '../../' . $path . 'img/env.gif')), - '#type' => 'checkbox', - '#default_value' => 0, - '#id' => 'chk-envdata', - ); - - $form['div_buttons']['chk_rfriendly'] = array( - '#title' => t('Make R Friendly (Make R Friendly)', array('@img' => '../../' . $path . 'img/r.gif')), - '#type' => 'checkbox', - '#default_value' => 0, - ); - - - $form['div_buttons']['download_submit_download'] = array( - '#type' => 'submit', - '#value' => 'Download', - ); - - $form['#attached']['js'] = array($path . 'js/rawpheno.download.script.js'); - - - return $form; -} - - -/** - * Function callback: AJAX update location and traits select boxes when project is selected. - */ -function rawpheno_download_get_locations_traits($form, $form_state) { - return $form['ajax_container']; -} - - -/** - * Function callback: AJAX update trait select box. - */ -function rawpheno_download_get_traits($form, $form_state) { - /* - $location = $form_state['values']['sel_location']; - $project = $form_state['values']['sel_project']; - - // Determine if the selected project is all project. - if ($project == 0) { - // Yes, then read the value of the hidden field containing project ids. - $t = $form_state['values']['txt_project']; - $project = explode(',', $t); - } - - // Get all traits given a location and project. - $opt_trait = rawpheno_download_load_traits($location, $project); - - // Update the #options value of select a trait select box. - $form['ajax_container']['ajax_container_traits']['sel_trait']['#options'] = $opt_trait; - // Update the title. - $form['ajax_container']['ajax_container_traits']['sel_trait']['#title'] = t('@count_trait Traits available', array('@count_trait' => count($opt_trait))); -*/ - - return $form['ajax_container']['ajax_container_traits']; -} - - -/** - * Implements hook_form_submit(). - * - * Generate a comma separated values (csv) file based on the location and trait set selected. - */ -function rawpheno_download_submit($form, &$form_state) { - // Project select field. - // Project by default is 0 - all projects then we want all project id field. - // This is field is never an array. - $prj = $form_state['values']['sel_project']; - $all_prj = $form_state['values']['txt_project']; - $prj = ($prj == 0) ? $all_prj : $prj; - - // Location select field. - // Location select field is an empty array - all locations. - // Otherwise, it will be an associative array where location is both key and value. - // Convert this to comma separated string when there's anything else set to 0 - for all locations. - $loc = $form_state['values']['sel_location']; - // Location 1 + (and) Location 2 + ..... - $loc = (count($loc) > 0) ? implode('+', $loc) : 0; - - // Trait select field. - // Trait select field is an empty array - all traits. - // Otherwise, it will be an associative array where trait is both key and value. - // Convert this to comma separated string when there's anything else set to 0 - for all traits. - $trt = $form_state['values']['sel_trait']; - $trt = (count($trt) > 0) ? implode(',', $trt) : 0; - - // Lastly, if user wants Environment Data and R version. - $env = $form_state['values']['chk_envdata']; - $rvr = $form_state['values']['chk_rfriendly']; - - // Construct environment data files archive. - $env_filename = 0; - - if (isset($env) && $env == 1) { - // Ensure that project and location combination return an environment data file. - $project = explode(',', $prj); - $location = explode('+', $loc); - - $files = rawpheno_function_getenv($project, $location); - - if (count($files) > 0) { - // Env file available. - $envs = array(); - - foreach($files as $file) { - $envs[] = $file->filename; - } - - if (count($envs) == 1) { - // Single env file found. Fetch the file (xlsx usually) and submit to tripal download. - $env_filename = reset($envs); - } - else { - // Multiple env files found. Fetch all files, tar (archive) and submit to tripal download. - $public = drupal_realpath('public://'); - $tar_filename = 'environment_data_' . date('ymdis') . '.tar'; - $tar_file = $public . '/' . $tar_filename; - - $tar_cmd = 'tar -cf ' . escapeshellarg($tar_file) . ' -C ' . escapeshellarg($public) . ' '; - $tar_cmd .= implode(' ', $envs) . ' 2>&1'; - - // Package everything... - shell_exec($tar_cmd); - $env_filename = $tar_filename; - } - } - } - - // Contain all query parameters/string into one string. - // Decode first when reading this string using base64_decode() function. - $url = 'p=' . $prj . '&l=' . $loc . '&t=' . $trt . '&r=' . $rvr . '&e=' . $env . '&file=' . $env_filename; - - // Format url for redirect. - $form_state['redirect'] = array( - '/phenotypes/raw/csv', - array( - 'query' => array( - 'code' => base64_encode($url), - ), - ), - ); -} +uid); + $user_experiment = array_keys($user_experiment); + $allowed_experiment = array(); + + $experiments = explode('+', $param_experiment); + foreach($experiments as $exp) { + if (in_array($exp, $user_experiment)) { + $allowed_experiment[] = $exp; + } + } + + if (count($allowed_experiment) > 0) { + $param_location = $query_vars['l']; + // Expected single value only for trait and germplasm. + $param_trait = (int) $query_vars['t']; + $param_stock = (int) $query_vars['g']; + + if ($param_experiment > 0 && $param_location && $param_trait > 0) { + // Create query string. + $query_string = 'p=' . implode('+', $allowed_experiment) . '&l=' . $param_location . '&t=' . $param_trait . '&r=0&e=0&file=0&g=' . $param_stock; + drupal_goto('/phenotypes/raw/csv', array('query' => array('code' => base64_encode($query_string)))); + } + } + } + } + + // Attach CSS and JavaScript + $path = drupal_get_path('module', 'rawpheno') . '/theme/'; + $form['#attached']['css'] = array($path . 'css/rawpheno.download.style.css'); + + // Navigation button. Related page of download page is rawdata/summary page. + $form['page_button'] = array( + '#type' => 'markup', + '#markup' => t('View Summary ❯'), + ); + + // PROJECT SELECT BOX. + if (isset($form_state['values']['sel_project'])) { + // Project selected. + $project_selected = $form_state['values']['sel_project']; + } + + // Sort the project names according to Planting Date. + // Put project with recently uploaded data/ based on planting year + // first in the list. + $sql = "SELECT project_id, name + FROM {project} AS t1 + RIGHT JOIN pheno_plant_project AS t2 USING (project_id) + LEFT JOIN pheno_measurements AS t3 USING (plant_id) + WHERE t3.type_id = (SELECT cvterm_id FROM {cvterm} WHERE name = 'Planting Date (date)' LIMIT 1) + GROUP BY project_id, name, t3.value + ORDER BY t3.value DESC"; + + $opt_project = chado_query($sql) + ->fetchAllKeyed(); + + // Project options: + if (count($opt_project) <= 0) { + // Module has no projects w/ data yet. + return $form; + } + else { + // Remove any duplicates from the sorted list. + $opt_project = array_unique($opt_project, SORT_REGULAR); + } + + // AJAX wrapper. + // Main wrapper + $form['ajax_container'] = array( + '#type' => 'markup', + '#prefix' => '
', + '#suffix' => '
', + ); + + // This a hidden field containing all project id. + // This field will allow callback functions to get all project ids which is + // the equivalent of the option select all project from the project select box. + $form['ajax_container']['txt_project'] = array( + '#type' => 'hidden', + '#value' => implode(',', array_keys($opt_project)), + ); + + $form['ajax_container']['sel_project'] = array( + '#type' => 'select', + '#title' => t('Experiment'), + '#options' => $opt_project, + '#multiple' => FALSE, + '#id' => 'download-sel-project', + '#ajax' => array( + 'event' => 'change', + 'callback' => 'rawpheno_download_get_locations_traits', + 'wrapper' => 'download-ajax-wrapper', + 'progress' => array('type' => '', 'message' => '') + ), + ); + + // This will reset the project select box on load and page refresh. + drupal_add_js('jQuery(document).ready(function() { + jQuery("#download-sel-project").val(0); + })', 'inline'); + + // Define the project ids required by the next field. + if (isset($project_selected)) { + // When a project is selected. Default to the project selected. + $project_id = $project_selected; + } + else { + // No project select. This is the default to the first project. + $p = array_keys($opt_project); + $project_id = reset($p); + } + + // All Locations for the default project above. Default project is the + // first project in the list. + $sql = "SELECT DISTINCT value, value AS prj_location + FROM pheno_plantprop + WHERE + type_id = (SELECT cvterm_id FROM {cvterm} cvt LEFT JOIN {cv} cv ON cv.cv_id = cvt.cv_id + WHERE cvt.name = 'Location' AND cv.name = 'phenotype_plant_property_types') AND + plant_id IN (SELECT plant_id FROM pheno_plant_project WHERE project_id IN (:project_id)) + ORDER BY value ASC"; + + $opt_location = chado_query($sql, array(':project_id' => $project_id)) + ->fetchAllKeyed(); + + $form['ajax_container']['sel_location'] = array( + '#type' => 'select', + '#title' => t('Location'), + '#options' => $opt_location, + '#multiple' => TRUE, + '#size' => 7, + '#id' => 'download-sel-location', + '#ajax' => array( + 'event' => 'change', + 'callback' => 'rawpheno_download_get_traits', + 'wrapper' => 'download-ajax-wrapper-traits', + 'progress' => array('type' => '', 'message' => '') + ), + ); + + $form['ajax_container']['chk_select_all_locations'] = array( + '#title' => t('Select all Locations'), + '#type' => 'checkbox', + '#default_value' => 0, + '#ajax' => array( + 'event' => 'change', + 'callback' => 'rawpheno_download_get_locations_traits', + 'wrapper' => 'download-ajax-wrapper', + 'progress' => array('type' => '', 'message' => '') + ), + '#id' => 'chk-select-all-locations', + ); + + $location_id = $opt_location; + + // Manage environment data file option. + // Allow option when a project is selected and project and location combination + // returns an environment data file. + $add_option = FALSE; + + if (isset($project_selected) && $project_selected > 0) { + if (isset($form_state['values']['sel_location']) + && count($form_state['values']['sel_location']) > 0) { + + $location = $form_state['values']['sel_location']; + + $envfile = rawpheno_function_getenv($project_selected, $location); + if ($envfile) { + $add_option = TRUE; + } + } + } + + drupal_add_js(array('rawpheno' => array('envdata_option' => $add_option)), array('type' => 'setting')); + + + // TRAITS. + // Select traits wrapper. + $form['ajax_container']['ajax_container_traits'] = array( + '#type' => 'markup', + '#prefix' => '
', + '#suffix' => '
', + ); + + // Get traits given a location and project. + if (isset($project_selected) && isset($location)) { + $project_id = $project_selected; + $location_id = $location; + } + + // The summarized list of cvterm_ids from MVIEW returned by inner most query will be passed to function that converts + // comma separated values into individual values (cvterm_id numbers) and the result is the parameter of ANY clause + // that will filter cvterms to only those in the list. Final rows are in JSON object and sorted alphabetically by name + // that will be passed on to the select field of rawdata form. + $sql_cvterm = " + SELECT c_j.cvterm_json->>'id', c_j.cvterm_json->>'name' FROM ( + SELECT JSON_BUILD_OBJECT('id', cvterm_id, 'name', name) AS cvterm_json FROM {cvterm} WHERE cvterm_id = ANY (( + SELECT STRING_TO_ARRAY(list_id.all_traits, ',') FROM ( + SELECT string_agg(DISTINCT all_traits, ',') AS all_traits + FROM {rawpheno_rawdata_mview} + WHERE + location IN(:location) + AND plant_id IN (SELECT plant_id FROM pheno_plant_project WHERE project_id = :project_id) + ) AS list_id + )::int[]) + ) AS c_j + WHERE c_j.cvterm_json->>'name' NOT IN ('Rep', 'Entry', 'Location', 'Name', 'Plot', 'Planting Date (date)', '# of Seeds Planted (count)') + ORDER BY c_j.cvterm_json->>'name' ASC + "; + + $trait_set = chado_query($sql_cvterm, array(':location' => $location_id, ':project_id' => $project_id)) + ->fetchAllKeyed(); + + $opt_trait = array_unique($trait_set); + + $form['ajax_container']['ajax_container_traits']['sel_trait'] = array( + '#type' => 'select', + '#title' => t('@trait_count Traits available', array('@trait_count' => count($opt_trait))), + '#options' => $opt_trait, + '#multiple' => TRUE, + '#size' => 15, + '#id' => 'download-sel-trait', + ); + + $form['ajax_container']['chk_select_all_traits'] = array( + '#title' => t('Select all Traits'), + '#type' => 'checkbox', + '#default_value' => 0, + '#id' => 'chk-select-all-traits', + ); + + $form['div_buttons'] = array( + '#prefix' => '
', + '#suffix' => '
', + ); + + $form['div_buttons']['chk_envdata'] = array( + '#title' => t('Include Environment Data (Include Environment Data)', array('@img' => '../../' . $path . 'img/env.gif')), + '#type' => 'checkbox', + '#default_value' => 0, + '#id' => 'chk-envdata', + ); + + $form['div_buttons']['chk_rfriendly'] = array( + '#title' => t('Make R Friendly (Make R Friendly)', array('@img' => '../../' . $path . 'img/r.gif')), + '#type' => 'checkbox', + '#default_value' => 0, + ); + + + $form['div_buttons']['download_submit_download'] = array( + '#type' => 'submit', + '#value' => 'Download', + ); + + $form['#attached']['js'] = array($path . 'js/rawpheno.download.script.js'); + + return $form; +} + + +/** + * Function callback: AJAX update location and traits select boxes when project is selected. + */ +function rawpheno_download_get_locations_traits($form, $form_state) { + return $form['ajax_container']; +} + + +/** + * Function callback: AJAX update trait select box. + */ +function rawpheno_download_get_traits($form, $form_state) { + /* + $location = $form_state['values']['sel_location']; + $project = $form_state['values']['sel_project']; + + // Determine if the selected project is all project. + if ($project == 0) { + // Yes, then read the value of the hidden field containing project ids. + $t = $form_state['values']['txt_project']; + $project = explode(',', $t); + } + + // Get all traits given a location and project. + $opt_trait = rawpheno_download_load_traits($location, $project); + + // Update the #options value of select a trait select box. + $form['ajax_container']['ajax_container_traits']['sel_trait']['#options'] = $opt_trait; + // Update the title. + $form['ajax_container']['ajax_container_traits']['sel_trait']['#title'] = t('@count_trait Traits available', array('@count_trait' => count($opt_trait))); +*/ + + return $form['ajax_container']['ajax_container_traits']; +} + + +/** + * Implements hook_form_submit(). + * + * Generate a comma separated values (csv) file based on the location and trait set selected. + */ +function rawpheno_download_submit($form, &$form_state) { + // Project select field. + // Project by default is 0 - all projects then we want all project id field. + // This is field is never an array. + $prj = $form_state['values']['sel_project']; + $all_prj = $form_state['values']['txt_project']; + $prj = ($prj == 0) ? $all_prj : $prj; + + // Location select field. + // Location select field is an empty array - all locations. + // Otherwise, it will be an associative array where location is both key and value. + // Convert this to comma separated string when there's anything else set to 0 - for all locations. + $loc = $form_state['values']['sel_location']; + // Location 1 + (and) Location 2 + ..... + $loc = (count($loc) > 0) ? implode('+', $loc) : 0; + + // Trait select field. + // Trait select field is an empty array - all traits. + // Otherwise, it will be an associative array where trait is both key and value. + // Convert this to comma separated string when there's anything else set to 0 - for all traits. + $trt = $form_state['values']['sel_trait']; + $trt = (count($trt) > 0) ? implode(',', $trt) : 0; + + // Lastly, if user wants Environment Data and R version. + $env = $form_state['values']['chk_envdata']; + $rvr = $form_state['values']['chk_rfriendly']; + + // Construct environment data files archive. + $env_filename = 0; + + if (isset($env) && $env == 1) { + // Ensure that project and location combination return an environment data file. + $project = explode(',', $prj); + $location = explode('+', $loc); + + $files = rawpheno_function_getenv($project, $location); + + if (count($files) > 0) { + // Env file available. + $envs = array(); + + foreach($files as $file) { + $envs[] = $file->filename; + } + + if (count($envs) == 1) { + // Single env file found. Fetch the file (xlsx usually) and submit to tripal download. + $env_filename = reset($envs); + } + else { + // Multiple env files found. Fetch all files, tar (archive) and submit to tripal download. + $public = drupal_realpath('public://'); + $tar_filename = 'environment_data_' . date('ymdis') . '.tar'; + $tar_file = $public . '/' . $tar_filename; + + $tar_cmd = 'tar -cf ' . escapeshellarg($tar_file) . ' -C ' . escapeshellarg($public) . ' '; + $tar_cmd .= implode(' ', $envs) . ' 2>&1'; + + // Package everything... + shell_exec($tar_cmd); + $env_filename = $tar_filename; + } + } + } + + // Contain all query parameters/string into one string. + // Decode first when reading this string using base64_decode() function. + $url = 'p=' . $prj . '&l=' . $loc . '&t=' . $trt . '&r=' . $rvr . '&e=' . $env . '&file=' . $env_filename . '&g=0'; + + // Format url for redirect. + $form_state['redirect'] = array( + '/phenotypes/raw/csv', + array( + 'query' => array( + 'code' => base64_encode($url), + ), + ), + ); +} diff --git a/include/rawpheno.function.measurements.inc b/includes/rawpheno.function.measurements.inc similarity index 100% rename from include/rawpheno.function.measurements.inc rename to includes/rawpheno.function.measurements.inc diff --git a/include/rawpheno.instructions.form.inc b/includes/rawpheno.instructions.form.inc old mode 100755 new mode 100644 similarity index 97% rename from include/rawpheno.instructions.form.inc rename to includes/rawpheno.instructions.form.inc index 513483c..a095565 --- a/include/rawpheno.instructions.form.inc +++ b/includes/rawpheno.instructions.form.inc @@ -1,506 +1,506 @@ - 'markup', - '#markup' => t('Upload Data ❯'), - ); - - // Attach CSS. - $path = drupal_get_path('module', 'rawpheno') . '/theme/'; - $form['#attached']['css'] = array($path . 'css/rawpheno.instructions.style.css'); - - // Create a select box containing projects available - these are projects - // that have associated column header set and must have at least 1 essential column header. - // The projects are filtered to show only projects assigned to user. - $all_project = rawpheno_function_user_project($GLOBALS['user']->uid); - - // No projects assined to the user. - if (count($all_project) < 1) { - return $form; - } - - // Ensure that project is valid. - if ($project_id) { - if (!in_array($project_id, array_keys($all_project))) { - // Project does not exist. - $form['message_invalid_project'] = array( - '#markup' => '
Experiment does not exist.
' - ); - } - else { - // Project is valid. Null this. - $form['message_invalid_project'] = array(); - } - } - else { - // When no project is supplied, then default this page to the most recent project - // available from the projects defined by admin. - $project_id = array_keys($all_project)[0]; - } - - // Project Name. - $project_name = $all_project[$project_id]; - - // Given a project id, construct the table required for each trait type set. - // All trait types, need to remove type plantproperty. - $trait_set = rawpheno_function_trait_types(); - - $sql = "SELECT project_cvterm_id AS id FROM {pheno_project_cvterm} - WHERE project_id = :project_id AND type <> :plantprop - ORDER BY type, cvterm_id ASC"; - - $args = array(':project_id' => $project_id, ':plantprop' => $trait_set['type4']); - $cvterm_id = db_query($sql, $args); - - // Array to hold trait row. - $arr_cvterm = array(); - - foreach($cvterm_id as $id) { - $cvterm = rawpheno_function_header_properties($id->id); - // Create an array that will translate to a row in table. - $arr_cvterm[ $cvterm['type'] ][] = array( - '
' . $cvterm['name'] . '
', - $cvterm['method'], - $cvterm['definition'] - ); - } - - // Construct table. - $arr_tbl_args['empty'] = '0 Column Header'; - $arr_tbl_args['header'] = array(t('Column Header/Trait'), t('Collection Method'), t('Definition')); - - unset($trait_set['type4']); - - foreach($trait_set as $type) { - $arr_tbl_args['rows'] = isset($arr_cvterm[$type]) ? $arr_cvterm[$type] : array(); - - $form['tbl_project_headers_' . $type] = array( - '#markup' => (count($arr_tbl_args['rows']) <= 0) ? 'no-trait' : theme('table', $arr_tbl_args), - ); - } - - // Project and project select box. - $form['project_panel'] = array( - '#markup' => $project_name - ); - - $form['sel_project'] = array( - '#type' => 'select', - '#options' => array(0 => 'Please select an experiment') + $all_project, - '#id' => 'rawpheno-ins-sel-project' - ); - - $ins_path = base_path() . 'phenotypes/raw/instructions/'; - // Make the project select box into a jump menu and add the project id number to the url. - drupal_add_js('jQuery(document).ready(function() { - jQuery("#rawpheno-ins-sel-project").change(function(){ - if (jQuery(this).val() > 0) { - window.location.href = "'. $ins_path .'" + jQuery(this).val(); - } - }); - })', 'inline'); - - - // NOTE: When project is AGILE, download data collection spreadsheet link points to - // pre-made AGILE spreadsheet, otherwise, instructions page will generate the spreadsheet file. To ensure that - // the column headers matched for AGILE project, a check (when no new column header added tru admin then download - // pre-made, else generate) is required before downloading the file. - - // Provide user with link to download project specific data collection spreadsheet file. - // When the project is the default project (AGILE) then supply a ready made spreadsheet. - - $file_path = ($tmp_project == null) ? 'instructions/spreadsheet/' : 'spreadsheet/'; - $link_to_xls = $file_path . $project_id; - - if ($project_name == 'AGILE: Application of Genomic Innovation in the Lentil Economy') { - // Compare the AGILE-specific column header to check if they match - $AGILE_column_headers = rawpheno_function_headers('expected'); - $AGILE_essential_headers = rawpheno_function_headers('essential'); - $AGILE_required_headers = rawpheno_function_headers('required'); - $AGILE_essential_headers = array_diff($AGILE_essential_headers, $AGILE_required_headers); - - // Query the AGILE project column headers. - $sql = "SELECT name, type FROM {cvterm} RIGHT JOIN pheno_project_cvterm USING(cvterm_id) WHERE project_id = :project_id"; - $args = array(':project_id' => $project_id); - $h = chado_query($sql, $args) - ->fetchAllKeyed(); - - // Manually add Name, since Name is not added to project. - $h['Name'] = 'plantproperty'; - - $header_found_count = 0; - $essential_found_count = 0; - $essential_count = 0; - $header_count = 0; - - foreach($h as $header => $type) { - // Ignore trait contributed. - if ($type == $trait_set['type5']) { - continue; - } - - $header_count++; - - // Use ready made AGILE data collection spreadsheet. - // Total number of headers, excluding contributed matches AGILE traits. - if (in_array($header, $AGILE_column_headers)) { - $header_found_count++; - } - - // Same essential traits as in AGILE traits. - if ($type == $trait_set['type1'] AND in_array($header, $AGILE_essential_headers)) { - $essential_found_count++; - } - - // Same number of essential traits as in AGILE traits. - if ($type == $trait_set['type1']) { - $essential_count++; - } - - // Finally, name must be AGILE: Application of Genomic Innovation in the Lentil Economy as condition for this block. - } - - // Check if AGILE trait set was not altered. - if ($header_count == count($AGILE_column_headers) - && $header_found_count == count($AGILE_column_headers) - && $essential_found_count == count($AGILE_essential_headers) - && $essential_count == count($AGILE_essential_headers)) { - // AGILE Project match the original trait set. Download ready-made data collection spreadsheet file. - $link_to_xls = file_create_url('public://AGILE-PhenotypeDataCollection-v5.xlsx.zip'); - } - } - - $form['download_data_collection'] = array( - '#markup' => t('Download Data Collection Spreadsheet', array('@link' => $link_to_xls)) - ); - - // Search field with autocomplete feature. - $form['txt_search'] = array( - '#title' => '', - '#type' => 'textfield', - '#maxlength' => 65, - '#size' => 65, - '#default_value' => t('Search Trait'), - '#autocomplete_path' => 'phenotypes/raw/instructions/autocomplete/' . $project_id, - ); - - $form['btn_search'] = array( - '#type' => 'markup', - '#markup' => '', - ); - - // Hidden field containing url to json. - $form['json_url'] = array( - '#type' => 'hidden', - '#value' => $GLOBALS['base_url'] . '/phenotypes/raw/instructions/autocomplete/' . $project_id, - '#attributes' => array('id' => array('traits-json')) - ); - - // Attach JQuery UI library and JavaScript. - $form['#attached']['library'][] = array('system', 'ui.tabs'); - $form['#attached']['js'] = array($path . 'js/rawpheno.instructions.script.js'); - - return $form; -} - - -/** - * Function create a spreadsheet file. - * - */ -function rawpheno_instructions_create_spreadsheet($project_id) { - // Query column headers specific to a project, given a project id. - if (isset($project_id) AND $project_id > 0) { - // Array to hold all trait types. - $trait_type = rawpheno_function_trait_types(); - - // Exclude the plant property from the set of headers. They are pre-inserted to the array - // of column headers passed to the spreadsheet writer. - $sql = "SELECT project_cvterm_id, name, type - FROM {cvterm} RIGHT JOIN pheno_project_cvterm USING(cvterm_id) - WHERE project_id = :project_id AND type NOT IN ( :exclude_property ) - ORDER BY type ASC"; - - $args = array(':project_id' => $project_id, ':exclude_property' => array($trait_type['type4'], $trait_type['type5'])); - $cvterm = chado_query($sql, $args); - - // Only when project has headers. - if ($cvterm->rowCount() > 0) { - // Array to hold the column headers passed to the excel writer. - $col_headers = array(); - // Array to hold standard procedure, which basically is - // the traits definition and collection method. - $instructions_data = array(); - - // Get the data type per unit. This type will be the cell type in the spreadsheet. - $data_type = rawpheno_function_default_unit('type'); - - // Prepend the array with plant property column headers. - $col_headers = array( - 'Plot' => 'integer', - 'Entry' => 'integer', - 'Name' => 'string', - 'Rep' => 'integer', - 'Location' => 'string' - ); - - // Start at F column taking into account plant properties. - // A for Plot, B for Entry and so on (A-E is 5 cols). - $l = 'F'; - $cell_i = array(); - - // Assign the data type for each header based on the unit it contains. - $h = array('name' => 'Trait', 'definition' => 'Definition', 'method' => 'Collection Method'); - - foreach($cvterm as $trait) { - // Get the unit. - $u = rawpheno_function_header_unit($trait->name); - $unit = isset($data_type[$u]) ? $data_type[$u] : 'string'; - - $col_headers[$trait->name] = $unit; - - // Highlight the cells when it is essential trait. - if ($trait->type == $trait_type['type1']) { - array_push($cell_i, $l . '1'); - // Increment F column. - $l++; - } - - // Get header method and definition information. - $t = rawpheno_function_header_properties($trait->project_cvterm_id); - - foreach($h as $m_i => $m) { - $star = ($m_i == 'name') ? '*' : ''; - - if (strlen($t[$m_i]) < 80) { - // Short text, save it. - array_push($instructions_data, array($star . $m . ':', $t[$m_i])); - } - else { - // Hard-wrap long lines into shorter line and put each - // line into a cell/row. - $wrapped_string = wordwrap($t[$m_i], 100, "\n"); - $chunks = explode("\n", $wrapped_string); - - foreach($chunks as $i => $chunk) { - $ins_text = ($i == 0) ? array($star . $m . ':', $chunk) : array('', $chunk); - array_push($instructions_data, $ins_text); - } - } - } - - // Add extra new line. - array_push($instructions_data, array('' , '')); - } - - // Load spreadsheet writer library. - $xlsx_writer = libraries_load('spreadsheet_writer'); - include_once $xlsx_writer['library path'] . '/'. $xlsx_writer['files'][0]; - - $writer = new XLSXWriter(); - // Measurement tab. - @$writer->writeSheet(array(), 'Measurements', $col_headers, - array( - // The entire header row apply these styles. - array( - 'font' => - array( - 'name' => 'Arial', - 'size' => '11', - 'color' => '000000', - 'bold' => false, - 'italic' => false, - 'underline' => false - ), - 'wrapText' => true, - 'verticalAlign' => 'top', - 'horizontalAlign' => 'center', - 'fill' => array('color' => 'F7F7F7'), - 'border' => array('style' => 'thin', 'color' => 'A0A0A0'), - 'rows' => array('0') - ), - // Once the styles above have been applied, style the plant property headers. - array( - 'font' => - array( - 'name' => 'Arial', - 'size' => '11', - 'color' => '000000', - 'bold' => true, - 'italic' => false, - 'underline' => false - ), - 'verticalAlign' => 'bottom', - 'horizontalAlign' => 'center', - 'fill' => array('color' => 'EAEAEA'), - 'border' => array('style' => 'thin', 'color' => 'A0A0A0'), - 'cells' => array('A1', 'B1', 'C1', 'D1', 'E1') - ), - // Make sure to style the essential trait/header. - array( - 'font' => - array( - 'name' => 'Arial', - 'size' => '11', - 'color' => '008000', - 'bold' => true, - 'italic' => false, - 'underline' => false - ), - 'wrapText' => true, - 'verticalAlign' => 'top', - 'horizontalAlign' => 'center', - 'fill' => array('color' => 'F5FFDF'), - 'border' => array('style' => 'thin', 'color' => 'A0A0A0'), - 'cells' => $cell_i - ) - ) - ); - - // Standard procedure tab. - // Load trait definition and data collection method to this sheet. - $instructions_header = array(); - @$writer->writeSheet($instructions_data, 'Instructions', $instructions_header, - array( - array( - 'font' => - array( - 'name' => 'Arial', - 'size' => '11', - 'color' => '000000', - 'bold' => true, - 'italic' => false, - 'underline' => false - ), - 'wrapText' => true, - 'columns' => '0', - ), - array( - 'font' => - array( - 'size' => '12', - ), - 'wrapText' => false, - 'columns' => '1', - ), - ) - ); - - // Calculator tab. - $calc_header = array('CALCULATE DAYS TO' => 'string'); - $calc_data = - array( - array('Planting Date', '2015-10-06'), - array('Current Date', date('Y-m-d')), - array('Current "Days till"', '=B3 - B2'), - array('',''), - array('Instructions', ''), - array('', ''), - array('Fill out the planting date indicated in the measurements tab, as well as, the current date.', ''), - array('The "Days till" date will then be calculated for you.', '') - ); - - @$writer->writeSheet($calc_data, 'Calculate Days to', $calc_header, - array( - array( - 'font' => - array( - 'name' => 'Arial', - 'size' => '20', - 'color' => '000000', - 'bold' => true, - 'italic' => false, - 'underline' => false - ), - 'wrapText' => false, - 'rows' => array('0'), - ), - array( - 'font' => - array( - 'name' => 'Arial', - 'size' => '11', - 'color' => 'FFFFFF', - 'bold' => true, - 'italic' => false, - 'underline' => false - ), - 'wrapText' => true, - 'fill' => array('color' => '305673'), - 'border' => array('style' => 'thin', 'color' => 'A0A0A0'), - 'rows' => array('1'), - ), - array( - 'font' => - array( - 'name' => 'Arial', - 'size' => '11', - 'color' => '000000', - 'bold' => true, - 'italic' => false, - 'underline' => false - ), - 'wrapText' => true, - 'fill' => array('color' => 'F7F7F7'), - 'border' => array('style' => 'thin', 'color' => 'A0A0A0'), - 'rows' => array('2'), - ), - array( - 'font' => - array( - 'name' => 'Arial', - 'size' => '11', - 'color' => '000000', - 'bold' => true, - 'italic' => false, - 'underline' => false - ), - 'wrapText' => true, - 'fill' => array('color' => '79a183'), - 'border' => array('style' => 'thin', 'color' => 'A0A0A0'), - 'rows' => array('3'), - ) - ) - ); - - // Data collection spreadsheet name contains the following: - // Project id, name of the user, date and time. - $filename = 'datacollection_' . $project_id . '_' . str_replace(' ', '_', $GLOBALS['user']->name) .'_'. date('YMd') .'_'. time() . '.xlsx'; - $file = file_save_data($writer->writeToString(), 'public://' . $filename); - - // Launch save file window and ask user to save file. - $http_headers = array( - 'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - 'Content-Disposition' => 'attachment; filename="' . $filename . '"', - ); - - if (strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE')) { - $http_headers['Cache-Control'] = 'must-revalidate, post-check=0, pre-check=0'; - $http_headers['Pragma'] = 'public'; - } - else { - $http_headers['Pragma'] = 'no-cache'; - } - - file_transfer($file->uri, $http_headers); - - // Just in case the auto download fails, provide a link to manually download the file. - print 'Download Data Collection Spreadsheet'; - } - else { - // Here project not found. - print 'Experiment not found.'; - } - } - else { - // Project id is not valid. - print 'Experiment ID number is invalid.'; - } -} + 'markup', + '#markup' => t('Upload Data ❯'), + ); + + // Attach CSS. + $path = drupal_get_path('module', 'rawpheno') . '/theme/'; + $form['#attached']['css'] = array($path . 'css/rawpheno.instructions.style.css'); + + // Create a select box containing projects available - these are projects + // that have associated column header set and must have at least 1 essential column header. + // The projects are filtered to show only projects assigned to user. + $all_project = rawpheno_function_user_project($GLOBALS['user']->uid); + + // No projects assined to the user. + if (count($all_project) < 1) { + return $form; + } + + // Ensure that project is valid. + if ($project_id) { + if (!in_array($project_id, array_keys($all_project))) { + // Project does not exist. + $form['message_invalid_project'] = array( + '#markup' => '
Experiment does not exist.
' + ); + } + else { + // Project is valid. Null this. + $form['message_invalid_project'] = array(); + } + } + else { + // When no project is supplied, then default this page to the most recent project + // available from the projects defined by admin. + $project_id = array_keys($all_project)[0]; + } + + // Project Name. + $project_name = $all_project[$project_id]; + + // Given a project id, construct the table required for each trait type set. + // All trait types, need to remove type plantproperty. + $trait_set = rawpheno_function_trait_types(); + + $sql = "SELECT project_cvterm_id AS id FROM {pheno_project_cvterm} + WHERE project_id = :project_id AND type <> :plantprop + ORDER BY type, cvterm_id ASC"; + + $args = array(':project_id' => $project_id, ':plantprop' => $trait_set['type4']); + $cvterm_id = db_query($sql, $args); + + // Array to hold trait row. + $arr_cvterm = array(); + + foreach($cvterm_id as $id) { + $cvterm = rawpheno_function_header_properties($id->id); + // Create an array that will translate to a row in table. + $arr_cvterm[ $cvterm['type'] ][] = array( + '
' . $cvterm['name'] . '
', + $cvterm['method'], + $cvterm['definition'] + ); + } + + // Construct table. + $arr_tbl_args['empty'] = '0 Column Header'; + $arr_tbl_args['header'] = array(t('Column Header/Trait'), t('Collection Method'), t('Definition')); + + unset($trait_set['type4']); + + foreach($trait_set as $type) { + $arr_tbl_args['rows'] = isset($arr_cvterm[$type]) ? $arr_cvterm[$type] : array(); + + $form['tbl_project_headers_' . $type] = array( + '#markup' => (count($arr_tbl_args['rows']) <= 0) ? 'no-trait' : theme('table', $arr_tbl_args), + ); + } + + // Project and project select box. + $form['project_panel'] = array( + '#markup' => $project_name + ); + + $form['sel_project'] = array( + '#type' => 'select', + '#options' => array(0 => 'Please select an experiment') + $all_project, + '#id' => 'rawpheno-ins-sel-project' + ); + + $ins_path = base_path() . 'phenotypes/raw/instructions/'; + // Make the project select box into a jump menu and add the project id number to the url. + drupal_add_js('jQuery(document).ready(function() { + jQuery("#rawpheno-ins-sel-project").change(function(){ + if (jQuery(this).val() > 0) { + window.location.href = "'. $ins_path .'" + jQuery(this).val(); + } + }); + })', 'inline'); + + + // NOTE: When project is AGILE, download data collection spreadsheet link points to + // pre-made AGILE spreadsheet, otherwise, instructions page will generate the spreadsheet file. To ensure that + // the column headers matched for AGILE project, a check (when no new column header added tru admin then download + // pre-made, else generate) is required before downloading the file. + + // Provide user with link to download project specific data collection spreadsheet file. + // When the project is the default project (AGILE) then supply a ready made spreadsheet. + + $file_path = ($tmp_project == null) ? 'instructions/spreadsheet/' : 'spreadsheet/'; + $link_to_xls = $file_path . $project_id; + + if ($project_name == 'AGILE: Application of Genomic Innovation in the Lentil Economy') { + // Compare the AGILE-specific column header to check if they match + $AGILE_column_headers = rawpheno_function_headers('expected'); + $AGILE_essential_headers = rawpheno_function_headers('essential'); + $AGILE_required_headers = rawpheno_function_headers('required'); + $AGILE_essential_headers = array_diff($AGILE_essential_headers, $AGILE_required_headers); + + // Query the AGILE project column headers. + $sql = "SELECT name, type FROM {cvterm} RIGHT JOIN pheno_project_cvterm USING(cvterm_id) WHERE project_id = :project_id"; + $args = array(':project_id' => $project_id); + $h = chado_query($sql, $args) + ->fetchAllKeyed(); + + // Manually add Name, since Name is not added to project. + $h['Name'] = 'plantproperty'; + + $header_found_count = 0; + $essential_found_count = 0; + $essential_count = 0; + $header_count = 0; + + foreach($h as $header => $type) { + // Ignore trait contributed. + if ($type == $trait_set['type5']) { + continue; + } + + $header_count++; + + // Use ready made AGILE data collection spreadsheet. + // Total number of headers, excluding contributed matches AGILE traits. + if (in_array($header, $AGILE_column_headers)) { + $header_found_count++; + } + + // Same essential traits as in AGILE traits. + if ($type == $trait_set['type1'] AND in_array($header, $AGILE_essential_headers)) { + $essential_found_count++; + } + + // Same number of essential traits as in AGILE traits. + if ($type == $trait_set['type1']) { + $essential_count++; + } + + // Finally, name must be AGILE: Application of Genomic Innovation in the Lentil Economy as condition for this block. + } + + // Check if AGILE trait set was not altered. + if ($header_count == count($AGILE_column_headers) + && $header_found_count == count($AGILE_column_headers) + && $essential_found_count == count($AGILE_essential_headers) + && $essential_count == count($AGILE_essential_headers)) { + // AGILE Project match the original trait set. Download ready-made data collection spreadsheet file. + $link_to_xls = file_create_url('public://AGILE-PhenotypeDataCollection-v5.xlsx.zip'); + } + } + + $form['download_data_collection'] = array( + '#markup' => t('Download Data Collection Spreadsheet', array('@link' => $link_to_xls)) + ); + + // Search field with autocomplete feature. + $form['txt_search'] = array( + '#title' => '', + '#type' => 'textfield', + '#maxlength' => 65, + '#size' => 65, + '#default_value' => t('Search Trait'), + '#autocomplete_path' => 'phenotypes/raw/instructions/autocomplete/' . $project_id, + ); + + $form['btn_search'] = array( + '#type' => 'markup', + '#markup' => '', + ); + + // Hidden field containing url to json. + $form['json_url'] = array( + '#type' => 'hidden', + '#value' => $GLOBALS['base_url'] . '/phenotypes/raw/instructions/autocomplete/' . $project_id, + '#attributes' => array('id' => array('traits-json')) + ); + + // Attach JQuery UI library and JavaScript. + $form['#attached']['library'][] = array('system', 'ui.tabs'); + $form['#attached']['js'] = array($path . 'js/rawpheno.instructions.script.js'); + + return $form; +} + + +/** + * Function create a spreadsheet file. + * + */ +function rawpheno_instructions_create_spreadsheet($project_id) { + // Query column headers specific to a project, given a project id. + if (isset($project_id) AND $project_id > 0) { + // Array to hold all trait types. + $trait_type = rawpheno_function_trait_types(); + + // Exclude the plant property from the set of headers. They are pre-inserted to the array + // of column headers passed to the spreadsheet writer. + $sql = "SELECT project_cvterm_id, name, type + FROM {cvterm} RIGHT JOIN pheno_project_cvterm USING(cvterm_id) + WHERE project_id = :project_id AND type NOT IN ( :exclude_property ) + ORDER BY type ASC"; + + $args = array(':project_id' => $project_id, ':exclude_property' => array($trait_type['type4'], $trait_type['type5'])); + $cvterm = chado_query($sql, $args); + + // Only when project has headers. + if ($cvterm->rowCount() > 0) { + // Array to hold the column headers passed to the excel writer. + $col_headers = array(); + // Array to hold standard procedure, which basically is + // the traits definition and collection method. + $instructions_data = array(); + + // Get the data type per unit. This type will be the cell type in the spreadsheet. + $data_type = rawpheno_function_default_unit('type'); + + // Prepend the array with plant property column headers. + $col_headers = array( + 'Plot' => 'integer', + 'Entry' => 'integer', + 'Name' => 'string', + 'Rep' => 'integer', + 'Location' => 'string' + ); + + // Start at F column taking into account plant properties. + // A for Plot, B for Entry and so on (A-E is 5 cols). + $l = 'F'; + $cell_i = array(); + + // Assign the data type for each header based on the unit it contains. + $h = array('name' => 'Trait', 'definition' => 'Definition', 'method' => 'Collection Method'); + + foreach($cvterm as $trait) { + // Get the unit. + $u = rawpheno_function_header_unit($trait->name); + $unit = isset($data_type[$u]) ? $data_type[$u] : 'string'; + + $col_headers[$trait->name] = $unit; + + // Highlight the cells when it is essential trait. + if ($trait->type == $trait_type['type1']) { + array_push($cell_i, $l . '1'); + // Increment F column. + $l++; + } + + // Get header method and definition information. + $t = rawpheno_function_header_properties($trait->project_cvterm_id); + + foreach($h as $m_i => $m) { + $star = ($m_i == 'name') ? '*' : ''; + + if (strlen($t[$m_i]) < 80) { + // Short text, save it. + array_push($instructions_data, array($star . $m . ':', $t[$m_i])); + } + else { + // Hard-wrap long lines into shorter line and put each + // line into a cell/row. + $wrapped_string = wordwrap($t[$m_i], 100, "\n"); + $chunks = explode("\n", $wrapped_string); + + foreach($chunks as $i => $chunk) { + $ins_text = ($i == 0) ? array($star . $m . ':', $chunk) : array('', $chunk); + array_push($instructions_data, $ins_text); + } + } + } + + // Add extra new line. + array_push($instructions_data, array('' , '')); + } + + // Load spreadsheet writer library. + $xlsx_writer = libraries_load('spreadsheet_writer'); + include_once $xlsx_writer['library path'] . '/'. $xlsx_writer['files'][0]; + + $writer = new XLSXWriter(); + // Measurement tab. + @$writer->writeSheet(array(), 'Measurements', $col_headers, + array( + // The entire header row apply these styles. + array( + 'font' => + array( + 'name' => 'Arial', + 'size' => '11', + 'color' => '000000', + 'bold' => false, + 'italic' => false, + 'underline' => false + ), + 'wrapText' => true, + 'verticalAlign' => 'top', + 'horizontalAlign' => 'center', + 'fill' => array('color' => 'F7F7F7'), + 'border' => array('style' => 'thin', 'color' => 'A0A0A0'), + 'rows' => array('0') + ), + // Once the styles above have been applied, style the plant property headers. + array( + 'font' => + array( + 'name' => 'Arial', + 'size' => '11', + 'color' => '000000', + 'bold' => true, + 'italic' => false, + 'underline' => false + ), + 'verticalAlign' => 'bottom', + 'horizontalAlign' => 'center', + 'fill' => array('color' => 'EAEAEA'), + 'border' => array('style' => 'thin', 'color' => 'A0A0A0'), + 'cells' => array('A1', 'B1', 'C1', 'D1', 'E1') + ), + // Make sure to style the essential trait/header. + array( + 'font' => + array( + 'name' => 'Arial', + 'size' => '11', + 'color' => '008000', + 'bold' => true, + 'italic' => false, + 'underline' => false + ), + 'wrapText' => true, + 'verticalAlign' => 'top', + 'horizontalAlign' => 'center', + 'fill' => array('color' => 'F5FFDF'), + 'border' => array('style' => 'thin', 'color' => 'A0A0A0'), + 'cells' => $cell_i + ) + ) + ); + + // Standard procedure tab. + // Load trait definition and data collection method to this sheet. + $instructions_header = array(); + @$writer->writeSheet($instructions_data, 'Instructions', $instructions_header, + array( + array( + 'font' => + array( + 'name' => 'Arial', + 'size' => '11', + 'color' => '000000', + 'bold' => true, + 'italic' => false, + 'underline' => false + ), + 'wrapText' => true, + 'columns' => '0', + ), + array( + 'font' => + array( + 'size' => '12', + ), + 'wrapText' => false, + 'columns' => '1', + ), + ) + ); + + // Calculator tab. + $calc_header = array('CALCULATE DAYS TO' => 'string'); + $calc_data = + array( + array('Planting Date', '2015-10-06'), + array('Current Date', date('Y-m-d')), + array('Current "Days till"', '=B3 - B2'), + array('',''), + array('Instructions', ''), + array('', ''), + array('Fill out the planting date indicated in the measurements tab, as well as, the current date.', ''), + array('The "Days till" date will then be calculated for you.', '') + ); + + @$writer->writeSheet($calc_data, 'Calculate Days to', $calc_header, + array( + array( + 'font' => + array( + 'name' => 'Arial', + 'size' => '20', + 'color' => '000000', + 'bold' => true, + 'italic' => false, + 'underline' => false + ), + 'wrapText' => false, + 'rows' => array('0'), + ), + array( + 'font' => + array( + 'name' => 'Arial', + 'size' => '11', + 'color' => 'FFFFFF', + 'bold' => true, + 'italic' => false, + 'underline' => false + ), + 'wrapText' => true, + 'fill' => array('color' => '305673'), + 'border' => array('style' => 'thin', 'color' => 'A0A0A0'), + 'rows' => array('1'), + ), + array( + 'font' => + array( + 'name' => 'Arial', + 'size' => '11', + 'color' => '000000', + 'bold' => true, + 'italic' => false, + 'underline' => false + ), + 'wrapText' => true, + 'fill' => array('color' => 'F7F7F7'), + 'border' => array('style' => 'thin', 'color' => 'A0A0A0'), + 'rows' => array('2'), + ), + array( + 'font' => + array( + 'name' => 'Arial', + 'size' => '11', + 'color' => '000000', + 'bold' => true, + 'italic' => false, + 'underline' => false + ), + 'wrapText' => true, + 'fill' => array('color' => '79a183'), + 'border' => array('style' => 'thin', 'color' => 'A0A0A0'), + 'rows' => array('3'), + ) + ) + ); + + // Data collection spreadsheet name contains the following: + // Project id, name of the user, date and time. + $filename = 'datacollection_' . $project_id . '_' . str_replace(' ', '_', $GLOBALS['user']->name) .'_'. date('YMd') .'_'. time() . '.xlsx'; + $file = file_save_data($writer->writeToString(), 'public://' . $filename); + + // Launch save file window and ask user to save file. + $http_headers = array( + 'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'Content-Disposition' => 'attachment; filename="' . $filename . '"', + ); + + if (strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE')) { + $http_headers['Cache-Control'] = 'must-revalidate, post-check=0, pre-check=0'; + $http_headers['Pragma'] = 'public'; + } + else { + $http_headers['Pragma'] = 'no-cache'; + } + + file_transfer($file->uri, $http_headers); + + // Just in case the auto download fails, provide a link to manually download the file. + print 'Download Data Collection Spreadsheet'; + } + else { + // Here project not found. + print 'Experiment not found.'; + } + } + else { + // Project id is not valid. + print 'Experiment ID number is invalid.'; + } +} diff --git a/include/rawpheno.rawdata.form.inc b/includes/rawpheno.rawdata.form.inc old mode 100755 new mode 100644 similarity index 97% rename from include/rawpheno.rawdata.form.inc rename to includes/rawpheno.rawdata.form.inc index 8d92588..e4e0c56 --- a/include/rawpheno.rawdata.form.inc +++ b/includes/rawpheno.rawdata.form.inc @@ -1,128 +1,128 @@ - 'markup', - '#markup' => t('Download Data ❯'), - ); - - // Query project that has data saved to it. - $sql = "SELECT DISTINCT t1.project_id, t1.name - FROM {project} AS t1 RIGHT JOIN pheno_plant_project AS t2 USING(project_id) - WHERE plant_id IS NOT NULL - ORDER BY t1.project_id ASC"; - - $project = chado_query($sql) - ->fetchAllKeyed(); - - $form['rawdata_txt_project'] = array( - '#type' => 'hidden', - '#value' => implode(',', array_keys($project)) - ); - - // Select project select box. - $form['rawdata_sel_project'] = array( - '#type' => 'select', - '#title' => t('Select experiment and trait:'), - '#options' => $project, - '#id' => 'rawdata-sel-project', - ); - - // The summarized list of cvterm_ids from MVIEW returned by inner most query will be passed to function that converts - // comma separated values into individual values (cvterm_id numbers) and the result is the parameter of ANY clause - // that will filter cvterms to only those in the list. Final rows are in JSON object and sorted alphabetically by name - // that will be passed on to the select field of rawdata form. - $sql_cvterm = " - SELECT c_j.cvterm_json->>'id', c_j.cvterm_json->>'name' FROM ( - SELECT JSON_BUILD_OBJECT('id', cvterm_id, 'name', name) AS cvterm_json FROM {cvterm} WHERE cvterm_id = ANY (( - SELECT STRING_TO_ARRAY(list_id.all_traits, ',') FROM ( - SELECT string_agg(DISTINCT all_traits, ',') AS all_traits - FROM {rawpheno_rawdata_mview} - WHERE plant_id IN (SELECT plant_id FROM pheno_plant_project WHERE project_id = :project_id) - ) AS list_id - )::int[]) - ) AS c_j - WHERE c_j.cvterm_json->>'name' NOT IN ('Rep', 'Entry', 'Location', 'Name', 'Plot', 'Planting Date (date)', '# of Seeds Planted (count)') - ORDER BY c_j.cvterm_json->>'name' ASC - "; - - // Add first option as instruction to this field. - $default_option = array(0 => 'Select a trait to hightlight in the chart'); - - // Create the select field and populate it with traits specific to a project. - foreach(array_keys($project) as $p_id) { - // Trait id numbers. - $trait_ids = chado_query($sql_cvterm, array(':project_id' => $p_id)) - ->fetchAllKeyed(); - - $traits_array = array_unique($trait_ids); - $traits = $default_option + $traits_array; - - $form['sel_' . $p_id] = array( - '#type' => 'select', - '#title' => ' ', - '#options' => $traits, - '#default_value' => reset($traits), - '#attributes' => array( - 'name' => 'rawdata-sel-trait' - ), - '#states' => array( - 'visible' => array(':input[name="rawdata_sel_project"]' => array('value' => $p_id)), - ), - ); - } - - // SVG elements. - // SVG canvas. - $form['page_content'] = array( - '#type' => 'markup', - '#markup' => ' - - - - - - - - - - ', - ); - - // Hidden field containing url to JSON (summary data). - $form['json_url'] = array( - '#type' => 'hidden', - // Update this line if this module is in a different directory structure. - '#value' => url('/'), - '#attributes' => array('id' => array('rawdata-json')) - ); - - // Attach D3 JS library. - $d3_lib = libraries_load('d3js'); - - if (isset($d3_lib) && !empty($d3_lib['loaded'])) { - $form['d3lib']['#attached']['libraries_load'][] = array('d3js'); - } - - // Attach CSS and JavaScript. - $path = drupal_get_path('module', 'rawpheno') . '/theme/'; - $form['#attached']['css'] = array($path . 'css/rawpheno.rawdata.style.css'); - $form['#attached']['js'] = array($path . 'js/rawpheno.rawdata.script.js'); - - return $form; -} + 'markup', + '#markup' => t('Download Data ❯'), + ); + + // Query project that has data saved to it. + $sql = "SELECT DISTINCT t1.project_id, t1.name + FROM {project} AS t1 RIGHT JOIN pheno_plant_project AS t2 USING(project_id) + WHERE plant_id IS NOT NULL + ORDER BY t1.project_id ASC"; + + $project = chado_query($sql) + ->fetchAllKeyed(); + + $form['rawdata_txt_project'] = array( + '#type' => 'hidden', + '#value' => implode(',', array_keys($project)) + ); + + // Select project select box. + $form['rawdata_sel_project'] = array( + '#type' => 'select', + '#title' => t('Select experiment and trait:'), + '#options' => $project, + '#id' => 'rawdata-sel-project', + ); + + // The summarized list of cvterm_ids from MVIEW returned by inner most query will be passed to function that converts + // comma separated values into individual values (cvterm_id numbers) and the result is the parameter of ANY clause + // that will filter cvterms to only those in the list. Final rows are in JSON object and sorted alphabetically by name + // that will be passed on to the select field of rawdata form. + $sql_cvterm = " + SELECT c_j.cvterm_json->>'id', c_j.cvterm_json->>'name' FROM ( + SELECT JSON_BUILD_OBJECT('id', cvterm_id, 'name', name) AS cvterm_json FROM {cvterm} WHERE cvterm_id = ANY (( + SELECT STRING_TO_ARRAY(list_id.all_traits, ',') FROM ( + SELECT string_agg(DISTINCT all_traits, ',') AS all_traits + FROM {rawpheno_rawdata_mview} + WHERE plant_id IN (SELECT plant_id FROM pheno_plant_project WHERE project_id = :project_id) + ) AS list_id + )::int[]) + ) AS c_j + WHERE c_j.cvterm_json->>'name' NOT IN ('Rep', 'Entry', 'Location', 'Name', 'Plot', 'Planting Date (date)', '# of Seeds Planted (count)') + ORDER BY c_j.cvterm_json->>'name' ASC + "; + + // Add first option as instruction to this field. + $default_option = array(0 => 'Select a trait to hightlight in the chart'); + + // Create the select field and populate it with traits specific to a project. + foreach(array_keys($project) as $p_id) { + // Trait id numbers. + $trait_ids = chado_query($sql_cvterm, array(':project_id' => $p_id)) + ->fetchAllKeyed(); + + $traits_array = array_unique($trait_ids); + $traits = $default_option + $traits_array; + + $form['sel_' . $p_id] = array( + '#type' => 'select', + '#title' => ' ', + '#options' => $traits, + '#default_value' => reset($traits), + '#attributes' => array( + 'name' => 'rawdata-sel-trait' + ), + '#states' => array( + 'visible' => array(':input[name="rawdata_sel_project"]' => array('value' => $p_id)), + ), + ); + } + + // SVG elements. + // SVG canvas. + $form['page_content'] = array( + '#type' => 'markup', + '#markup' => ' + + + + + + + + + + ', + ); + + // Hidden field containing url to JSON (summary data). + $form['json_url'] = array( + '#type' => 'hidden', + // Update this line if this module is in a different directory structure. + '#value' => url('/'), + '#attributes' => array('id' => array('rawdata-json')) + ); + + // Attach D3 JS library. + $d3_lib = libraries_load('d3js'); + + if (isset($d3_lib) && !empty($d3_lib['loaded'])) { + $form['d3lib']['#attached']['libraries_load'][] = array('d3js'); + } + + // Attach CSS and JavaScript. + $path = drupal_get_path('module', 'rawpheno') . '/theme/'; + $form['#attached']['css'] = array($path . 'css/rawpheno.rawdata.style.css'); + $form['#attached']['js'] = array($path . 'js/rawpheno.rawdata.script.js'); + + return $form; +} diff --git a/include/rawpheno.tripaldownload.inc b/includes/rawpheno.tripaldownload.inc similarity index 85% rename from include/rawpheno.tripaldownload.inc rename to includes/rawpheno.tripaldownload.inc index d55777c..aa6fa1c 100644 --- a/include/rawpheno.tripaldownload.inc +++ b/includes/rawpheno.tripaldownload.inc @@ -30,7 +30,7 @@ function rawpheno_register_trpdownload_type() { 'get_format' => 'rawpheno_trpdownload_get_readable_format', ), ); - + return $types; } @@ -54,7 +54,7 @@ function rawpheno_trpdownload_summarize_download($vars) { list($project, $location, $traits, $r_version, $envdata, $envfile) = explode('&', $q); $tmp = trim(str_replace('p=', '', $project)); - $project = explode(',', $tmp); + $project = explode('+', $tmp); $sql = "SELECT name FROM {project} WHERE project_id IN (:project)"; $args = array(':project' => $project); @@ -191,7 +191,7 @@ function rawpheno_trpdownload_get_readable_format($vars) { /** * Function callback: generate csv file. */ -function rawpheno_trpdownload_generate_file($variables, $job_id = NULL) { +function rawpheno_trpdownload_generate_file($variables, $job_id = NULL) { // Get query string and filename. $code = ''; foreach($variables as $l => $v) { @@ -209,12 +209,11 @@ function rawpheno_trpdownload_generate_file($variables, $job_id = NULL) { } $q = base64_decode($code); - list($project, $location, $traits, $r_version,,) = explode('&', $q); - + list($project, $location, $traits, $r_version,,,$germplasm) = explode('&', $q); // Projects: $tmp = trim(str_replace('p=', '', $project)); - $project = explode(',', $tmp); - + $project = explode('+', $tmp); + // Locations: $location = trim(str_replace('l=', '', $location)); @@ -241,6 +240,9 @@ function rawpheno_trpdownload_generate_file($variables, $job_id = NULL) { } else { $traits = explode(',', $traits); + // Backup trait selection to account for trait w/o plant id + // but is a trait in experiment trait list. + $rd_field_trait = $traits; } // Rversion @@ -261,15 +263,40 @@ function rawpheno_trpdownload_generate_file($variables, $job_id = NULL) { } } + // Germplasm/Stock id - request from germplasm field. + if ($germplasm) { + $stock_id = trim(str_replace('g=', '', $germplasm)); + $limit_stock = ''; + } + // Sub-query to select plant_id given a location and project. // NOTE: leading and trailing spaces are required. $sub_sql = " (SELECT plant_id FROM {pheno_plantprop} INNER JOIN {pheno_plant_project} USING(plant_id) - WHERE value IN (:location) AND project_id IN (:project)) "; + WHERE value IN (:location) AND project_id IN (:project) %s) "; // Query values required by sub query. $arr_q_string = array(':project' => $project, ':location' => $location, ':traits' => $traits); + // Array to hold column headers. + // Add Name/Stock name column headers array. + $header = array('A0' => 'Name'); + + // Filter by germplasm. + if ($stock_id != '' && (int) $stock_id > 0) { + // Additional filter to limit datapoints to only those that match stock/germplasm. + $limit_stock = "AND plant_id IN (SELECT plant_id FROM pheno_plant WHERE stock_id = :stock_id)"; + $arr_q_string[':stock_id'] = $stock_id; + + // Experiment name is added to the result as export from multiple + // experiments is supported by the field. + + // Query below is also updated to correspond to this header. + $header['E0'] = 'Experiment'; + } + + $sub_sql = sprintf($sub_sql, $limit_stock); + // First we need to get the header. This will allow us to ensure that the data // downloaded all matches up with the trait it is associated with. Furthermore, // it will allow us to handle missing data. @@ -286,10 +313,6 @@ function rawpheno_trpdownload_generate_file($variables, $job_id = NULL) { $result = db_query($sql, $arr_q_string); - // Array to hold column headers. - //Add Name/Stock name column headers array. - $header = array('A0' => 'Name'); - foreach ($result as $r) { $def = $r->name; @@ -302,22 +325,51 @@ function rawpheno_trpdownload_generate_file($variables, $job_id = NULL) { // When no equivalent R version is available, load the definition instead. $def = ($rfreindly == null) ? $r->name : $rfreindly; } - + // Column headers array. $header[ $r->id ] = $def; } + // When request is from field, it has additional filter to phenotypes + // by comparing stock id + plant id. In some case when a trait has no values, + // the final result will exclude this trait inspite this trait being part + // of trait set of the experiment, since it has no plant_id (stock_id) to compare. + + // Include the trait and the subsequent code will assign the value NA. This to show + // that trait is part of the experiment. + // NOTE: rversion is always off when request comes from field. + foreach($rd_field_trait as $exp_trait) { + if (!in_array($exp_trait, array_keys($header))) { + $t = chado_get_cvterm(array('cvterm_id' => $exp_trait)); + $header[ 'C' . $t->cvterm_id ] = $t->name; + } + } + // Sort array by key. ksort($header); + // Filter by germplasm. + $sql_experiment = ''; + if ($stock_id != '' && (int) $stock_id > 0) { + // Include a query to mark datapoints at to which experiment to correspond + // to experiment header. + $sql_experiment = " + SELECT t1.plant_id AS id, 0 AS tid, 'e' AS def, t2.name AS value, 'E' AS grp + FROM {pheno_plant_project} AS t1 + INNER JOIN {chado.project} AS t2 ON t1.project_id = t2.project_id + WHERE plant_id IN" . $sub_sql . "UNION "; + } + // Query to join data from different tables. // Result: plant_id, trait_id, definition, data, and a grouping string // The result is sorted by plant_id and the grouping string ensuring that the first // row is Name - containing the stock name. // The result will be sorted into standard order: plot,entry,name,rep,location,traits..... - // Thus first we select the name. Note that the tid is 0 because this doesn't have a cvterm (ie: not a trait). - $sql = "SELECT t2.plant_id AS id, '0' AS tid, 'Name' AS def, t1.name AS value, 'A' AS grp + // Thus first we select experiment and name. Note that the tid is 0 because this doesn't have a cvterm (ie: not a trait). + $sql = "%s + + SELECT t2.plant_id AS id, '0' AS tid, 'Name' AS def, t1.name AS value, 'A' AS grp FROM {chado.stock} AS t1 INNER JOIN {pheno_plant} AS t2 USING(stock_id) WHERE t2.plant_id IN" . $sub_sql @@ -340,7 +392,8 @@ function rawpheno_trpdownload_generate_file($variables, $job_id = NULL) { // Lastly we order the results by plant_id and grouping string, and tid. . "GROUP BY t1.plant_id, t1.type_id, t2.name ORDER BY id, grp, tid ASC"; - + + $sql = sprintf($sql, $sql_experiment); $results = db_query($sql, $arr_q_string); if ($results) { diff --git a/include/rawpheno.upload.excel.inc b/includes/rawpheno.upload.excel.inc old mode 100755 new mode 100644 similarity index 97% rename from include/rawpheno.upload.excel.inc rename to includes/rawpheno.upload.excel.inc index ef4fbd8..9c6fdd9 --- a/include/rawpheno.upload.excel.inc +++ b/includes/rawpheno.upload.excel.inc @@ -1,1271 +1,1271 @@ - $fid), - array('print' => TRUE) - ); - tripal_report_error('rawpheno', TRIPAL_CRITICAL, '[CODE 100] Failed to load phenoypic data (job !id)', array('!id' => $job_id), array('print' => TRUE)); - exit(1); - } - - $xls_file = drupal_realpath($file->uri); - - // INFO: - // Keep a record of which file we are loading in the logs. - // print "\nXLSX File: " . $xls_file . "\n"; - - // Add the libraries needed to parse excel files. - rawpheno_add_parsing_libraries(); - - // Open the file for reading - $xls_obj = rawpheno_open_file($file); - if (!$xls_obj) { - tripal_report_error( - 'rawpheno', - TRIPAL_CRITICAL, - 'Uploading Phenoypic Data: Unable to open file. File=@file', - array('@file' => print_r($file, TRUE)), - array('print' => TRUE) - ); - tripal_report_error('rawpheno', TRIPAL_CRITICAL, '[CODE 101] Failed to load phenoypic data (job !id)', array('!id' => $job_id), array('print' => TRUE)); - exit(2); - } - - // Change to the correct spreadsheet. - rawpheno_change_sheet($xls_obj, 'measurements'); - - // Compute the total number of rows parsed. - $row_count = rawpheno_count_rows($xls_obj); - - // File to write progress status. - $tmp = file_directory_temp(); - $filename = $tmp . '/' . 'job-progress' . $job_id . '.txt'; - - // Variations of Not Applicable. - $not_applicable = array('na', 'n/a', 'n.a.'); - - // Start Transaction. - $TRANSACTION = db_transaction(); - try { - - // Read each row. - // INFO: - // print "\nNow parsing each row and saving it to the database...\nNumber of rows saved: \n"; - $i = 0; - - // Skip columns. - $skip = array(); - // Project name. - $project_name = rawpheno_function_getproject($project_id); - // Calling all modules implementing hook_rawpheno_ignorecols_valsave_alter(): - drupal_alter('rawpheno_ignorecols_valsave', $skip, $project_name); - - // Each row in the spreadsheet. - foreach ($xls_obj as $row) { - // Create progress update by computing the number of rows saved in percent. - // Write to file and progress bar API reads the content and pass it JSON generator - // in file rawpheno.module function: rawpheno_upload_job_progress_json(). - // Echo to terminal. - - $percent = round(($i / $row_count) * 100); - if ($percent % 10 == 0) { - print $percent . '% complete...' . "\n"; - } - - // To file. - file_unmanaged_save_data($percent, $filename, FILE_EXISTS_REPLACE); - - // HEADER! - // This is the header. - if ($i == 0) { - $header = $row; - - // Find the index number of name header in the spreadsheet. - $name_index = array_search('name', array_map('rawpheno_function_delformat', $header)); - if ($name_index === FALSE) { - tripal_report_error( - 'rawpheno', - TRIPAL_CRITICAL, - 'Uploading Phenoypic Data: Unable to determine the name column.', - array(), - array('print' => TRUE) - ); - $TRANSACTION->rollback(); - tripal_report_error('rawpheno', TRIPAL_CRITICAL, '[CODE 102] Failed to load phenoypic data (job !id)', array('!id' => $job_id), array('print' => TRUE)); - exit(31); - } - - $i++; - continue; - } - - // NEXT ROW IF EMPTY! - // Don't continue processing if this row is empty. - if (strlen(trim(implode('', $row))) <= 5) { - break; - } - - // VALID EACH ROW! - // Process the name column first since we need a plant_id before we can insert any more data. - // Name column header goes into pheno_plant. - // Check if stock name exists. - unset($stock_id); - - // Prior to saving, Remove non-breaking whitespace by converting it to a blank space instead of removing it, - // in case user intends a space between words/values. - // trim() implementation below should drop unecessary leading and trailing spaces. - if (preg_match('/\xc2\xa0/', $row[$name_index])) { - $row[$name_index] = preg_replace('/\xc2\xa0/', ' ', $row[$name_index]); - } - - $stock_id = rawpheno_function_getstockid(trim($row[$name_index]), $project_name); - // print $stock_id . ' -- ' . $row[$name_index] . "\n"; - - // Determine if name has a stock id number. - if (isset($stock_id) && $stock_id > 0) { - $p_id = 0; - - // Test if stock was measured in the active project, if not, insert as a new record. - // Otherwise, do more check (plot) to see if plant_id should be re-used. - $sql = " - SELECT plant_id - FROM pheno_plant AS t1 INNER JOIN pheno_plant_project AS t2 USING(plant_id) - WHERE t1.stock_id = :stock_id AND t2.project_id = :project_id LIMIT 1"; - - $args = array(':stock_id' => $stock_id, ':project_id' => $project_id); - $p = chado_query($sql, $args); - - if ($p->rowCount()) { - // Found a stock record in the project. Do more test. - // Array to hold plot headers. - // Plot, Rep, Location, Planting Date (date) - $arr_plot_cols = rawpheno_function_headers('plot'); - - // Construct query string. - // String : stock_id - plot - rep - location - year - // eg. 147-5-2-Saskatoon-2015 - $plot = $stock_id; - - // Given a row, construct the search string (format) above and use it to search if - // the such combination matched any record in the database. - foreach($arr_plot_cols as $plot_col) { - $plot_col = rawpheno_function_delformat($plot_col); - - // Cell value of plot property header. - $col_index = array_search(strtolower($plot_col), array_map('rawpheno_function_delformat', $header)); - $cell_val = trim($row[$col_index]); - - // If planting date - extract the year value. - // Support NA and YYYY in planting date. Use this value when - // value cannot be split by -. - if ($plot_col == 'plantingdate(date)') { - $y = explode('-', $cell_val); - - if (is_array($y)) { - // Extract the year only from planting date. - $cell_val = $y[0]; - } - - // Else use the NA or YYYY. - } - - $plot .= '-' . $cell_val; - } - - // Search the query string. - $p_id = rawpheno_function_plot_exists($plot, $project_id); - } - - - if ($p_id) { - // Plot found - re-use the plant_id. - $pheno_plantid = $p_id; - // INFO: - // print 'FOUND PLOT: ' . $plot . ' [re-using plot id #' . $pheno_plantid . '] ~ '; - } - else { - // Plot not found - insert as new row. - $pheno_plantid = db_insert('pheno_plant') - ->fields(array('stock_id' => $stock_id)) - ->execute(); - - // Map this record/stock to a project. - db_insert('pheno_plant_project') - ->fields(array('project_id' => $project_id, - 'plant_id' => $pheno_plantid)) - ->execute(); - - // INFO: - // print 'NEW STOCK: #' . $stock_id . ' [adding plot id #' . $pheno_plantid . '] ~ '; - } - } - else { - // Warn the admin that germplasm is not available... - // We want to stop loading if this is the case. - tripal_report_error( - 'rawpheno', - TRIPAL_CRITICAL, - 'Uploading Phenoypic Data: Germplasm doesn\'t exist (name=!name; row=!row)', - array('!name' => $row[$name_index], '!row' => $i), - array('print' => TRUE) - ); - $TRANSACTION->rollback(); - tripal_report_error('rawpheno', TRIPAL_CRITICAL, '[CODE 103] Failed to load phenoypic data (job !id)', array('!id' => $job_id), array('print' => TRUE)); - exit(32); - } - - // Read each row and each cell. - // Each row will be an array where name is always the first element. - foreach($row as $cell_index => $cell_entry) { - // Skip this cell when col from durpal_alter hook matches the col. - $h = rawpheno_function_delformat($header[$cell_index]); - - if (count($skip) > 0 && in_array($h, $skip)) { - // print 'skipping value : ' . $h . '=' . $cell_entry . "\n"; - continue; - } - - // For consistency, convert all variations of not applicable to NA. - if (is_string($cell_entry) && in_array(strtolower($cell_entry), $not_applicable)) { - $cell_entry = 'NA'; - } - - // We don't want to insert empty data. - // That said, while PHP thinks 0 is empty, we do not. - if (!empty($cell_entry) OR (strval($cell_entry) === '0')) { - - // Get the column header of a cell. - $cell_colheader = trim(str_replace(array("\n", "\r", " "), ' ', $header[$cell_index])); - // Remove additional spaces from column headers. - $cell_colheader = preg_replace('/\s+/', ' ', $cell_colheader); - - - // Determine if user wants to save this trait. - if (count($arr_newheaders) > 0 AND array_key_exists($cell_colheader, $arr_newheaders)) { - if ($arr_newheaders[$cell_colheader]['flag'] == 0) { - // Skip this cell if it is a new column header and user does not want to save - // this new trait; - continue; - } - elseif ($arr_newheaders[$cell_colheader]['flag'] == 1) { - // Get the cvterm name for this new header. - $alt_name = $arr_newheaders[$cell_colheader]['alt_header']; - $n = array('cvterm_id' => $alt_name, 'cv_id' => array('name' => 'phenotype_measurement_types')); - - if (function_exists('chado_get_cvterm')) { - $name = chado_get_cvterm($n); - } - else { - $name = tripal_get_cvterm($n); - } - - $cell_colheader = $name->name; - } - } - - // Prior to saving, Remove non-breaking whitespace by converting it to a blank space instead of removing it, - // in case user intends a space between words/values. - // trim() implementation below should drop unecessary leading and trailing spaces. - if (preg_match('/\xc2\xa0/', $cell_entry)) { - $cell_entry = preg_replace('/\xc2\xa0/', ' ', $cell_entry); - } - - // We always want to strip flanking white space. - // FYI: This is done when the data is validated as well. - $cell_entry = trim($cell_entry); - $cell_colheader = trim($cell_colheader); - - // Determine which table to insert a column header. - // If this is the name column then doing nothing since we've already delt with it above. - if ($cell_index == $name_index) { continue; } - - // PLOT, ENTRY, REP and LOCATION - // Cells containing column headers that are required. - // Traits: plot, entry, rep, location into pheno_plantprop. - elseif (in_array($cell_colheader, $plantprop_headers) && !empty($cell_colheader)) { - $t = array('name' => $cell_colheader, 'cv_id' => array('name' => 'phenotype_plant_property_types')); - - if (function_exists('chado_get_cvterm')) { - $type = chado_get_cvterm($t); - } - else { - $type = tripal_get_cvterm($t); - } - - $type_id = $type->cvterm_id; - - // Ensure that cvterm_id is present before inserting to table - if(isset($type_id)) { - $tmp = db_insert('pheno_plantprop') - ->fields(array('plant_id' => $pheno_plantid, - 'type_id' => $type_id, - 'value' => $cell_entry)) - ->execute(); - - if (!$tmp) { - tripal_report_error( - 'rawpheno', - TRIPAL_ERROR, - 'Uploading Phenoypic Data: Unable to insert plant property. Values=@values', - array('@values' => print_r(array('plant_id' => $pheno_plantid, 'type_id' => $type_id, 'value' => $cell_entry),TRUE)), - array('print' => TRUE) - ); - $TRANSACTION->rollback(); - tripal_report_error('rawpheno', TRIPAL_CRITICAL, '[CODE 104] Failed to load phenoypic data (job !id)', array('!id' => $job_id), array('print' => TRUE)); - exit(33); - } - } - - else { - tripal_report_error( - 'rawpheno', - TRIPAL_ERROR, - 'Uploading Phenoypic Data: Plant Property type !type does\'t exist.', - array('!type' => $cell_colheader), - array('print' => TRUE) - ); - $TRANSACTION->rollback(); - tripal_report_error('rawpheno', TRIPAL_CRITICAL, '[CODE 105] Failed to load phenoypic data (job !id)', array('!id' => $job_id), array('print' => TRUE)); - exit(34); - } - - } - // THE REST OF THE COLUMN HEADERS - // Everything else into pheno_measurements. - elseif ((!empty($cell_colheader) && $cell_entry != 'NA') - || ($cell_colheader == 'Planting Date (date)' && $cell_entry == 'NA')) { - - // Allow NA only when header is planting date. - - $c_h = rawpheno_function_delformat($cell_colheader); - if (in_array($c_h, $skip)) { - // print 'skipping header : ' . $cell_colheader . "\n"; - continue; - } - - // Get the cvterm_id for the trait measurement. - $type_id = rawpheno_get_trait_id($cell_colheader); - - if (!$type_id) { - tripal_report_error( - 'rawpheno', - TRIPAL_ERROR, - 'Uploading Phenoypic Data: Missing Plant Measurement Type (Header=!colheader).', - array('!colheader' => $cell_colheader), - array('print' => TRUE) - ); - $TRANSACTION->rollback(); - tripal_report_error('rawpheno', TRIPAL_CRITICAL, '[CODE 106] Failed to load phenoypic data (job !id)', array('!id' => $job_id), array('print' => TRUE)); - exit(37); - } - - // Retrieve the unit for this trait. - $cv_unit = rawpheno_get_trait_unit($cell_colheader, $type_id); - - if ($cv_unit) { - $unit_id = $cv_unit['id']; - $unit = $cv_unit['name']; - } - else { - tripal_report_error( - 'rawpheno', - TRIPAL_ERROR, - 'Uploading Phenoypic Data: Unable to find unit for Plant Measurement Type (Term=!name; Type ID=!id).', - array('!name' => $cell_colheader, '!id' => $type_id), - array('print' => TRUE) - ); - $TRANSACTION->rollback(); - tripal_report_error('rawpheno', TRIPAL_CRITICAL, '[CODE 107] Failed to load phenoypic data (job !id)', array('!id' => $job_id), array('print' => TRUE)); - exit(37); - } - - // Determine if cell requires scale member code. - // When unit is scale, find code equivalent in pheno_scale_member table. - if ($unit == 'scale') { - // Get pheno scale member code - $cvalue_id = db_query("SELECT member_id FROM {pheno_scale_member} - WHERE code = :code LIMIT 1", - array(':code' => trim($cell_entry))) - ->fetchField(); - // We want to report an error if we can't find the scale memeber - // but only if there are any in the first place! - $num_members = db_query('SELECT count(*) FROM {pheno_scale_member} WHERE scale_id=:unit_id', - array(':unit_id' => $unit_id))->fetchField(); - if (!$cvalue_id AND !empty($num_members)) { - tripal_report_error( - 'rawpheno', - TRIPAL_WARNING, - 'Uploading Phenoypic Data: Unable to find scale id for Plant Measurement Type (Trait=!trait; Term=!name; Type ID=!id; Scale Value=!scale).', - array('!trait' => $cell_colheader, '!name' => $unit, '!id' => $unit_id, '!scale' => $cell_entry), - array('print' => TRUE) - ); - } - - // Use default value in the cell if query to find scale member code - // has no equivalent value. - $cvalue_id = (isset($cvalue_id) && $cvalue_id > 0) ? $cvalue_id : $cell_entry; - } - else { - // No scale member value for the rest of traits. - $cvalue_id = ''; - } - - // Insert trait only when type_id and unit_id are not null. - if (isset($type_id) && isset($unit_id)) { - - $temp = db_insert('pheno_measurements') - ->fields(array('plant_id' => $pheno_plantid, - 'type_id' => $type_id, - 'unit_id' => $unit_id, - 'cvalue_id' => $cvalue_id, - 'value' => $cell_entry, - 'modified' => date("D M d, Y h:i:s a", time()))) - ->execute(); - - if (!$temp) { - tripal_report_error( - 'rawpheno', - TRIPAL_ERROR, - 'Uploading Phenoypic Data: Unable to insert measurement. Values=@values.', - array('@values' => print_r(array('plant_id' => $pheno_plantid, - 'type_id' => $type_id, - 'unit_id' => $unit_id, - 'cvalue_id' => $cvalue_id, - 'value' => $cell_entry, - 'modified' => date("D M d, Y h:i:s a", time())),TRUE)), - array('print' => TRUE) - ); - $TRANSACTION->rollback(); - tripal_report_error('rawpheno', TRIPAL_CRITICAL, '[CODE 108] Failed to load phenoypic data (job !id)', array('!id' => $job_id), array('print' => TRUE)); - exit(38); - } - } - } - } - } - - $i++; - } - } - catch (Exception $e) { - $TRANSACTION->rollback(); - watchdog_exception('rawpheno', $e); - tripal_report_error('rawpheno', TRIPAL_CRITICAL, '[CODE 109] Failed to load phenoypic data (job !id)', array('!id' => $job_id), array('print' => TRUE)); - exit(4); - } - - unset($TRANSACTION); //Commit - print "Upload complete.\n"; - - print "\nUpdating the materialized view summarizing phenotypic data.\n"; - $mview_id = tripal_get_mview_id('rawpheno_rawdata_summary'); - if ($mview_id) tripal_populate_mview($mview_id); -} - - -/** - * Validates an excel file using any validators registered with rawpheno. - * - * @param $file - * A drupal managed_file object describing the uploaded spreadsheet. - * @param $project_id - * An integer containing project id selected in the project select box. - * This will map the data submitted to a project. - * @param $source - * A string containing the source of the file upload - Upload Data or Backup File. - * - * @return - * An array containing the validation result from each validator. - */ -function rawpheno_validate_excel_file($file, $project_id, $source) { - $status = array(); - - // Process the validators to make them easier to use. - // Specifically, sort them by their scope. - $validators = array(); - $all_validators = module_invoke_all('rawpheno_validators'); - foreach($all_validators as $k => $v) { - $validators[ $v['scope'] ][ $k ] = $v; - } - - // Todo list. - $all_scope_validators = array('project', 'file', 'all', 'header', 'subset'); - - // Add the libraries needed to parse excel files. - rawpheno_add_parsing_libraries(); - - // Before performing any validation to the excel file. Ensure first that a project is selected. - foreach ($validators['project'] as $prj_validator_name => $prj_validator) { - if (isset($prj_validator['validation callback']) AND function_exists($prj_validator['validation callback'])) { - $status[ $prj_validator_name ] = call_user_func($prj_validator['validation callback'], $project_id); - - // If returned false then halt validation. - if ($status[ $prj_validator_name ] === FALSE) { - // Fail the project and set the rest to TODO. - $status[ $prj_validator_name ] = FALSE; - - // Todo the rest of validators. - // Since this is project scope and it got falsed - remove the project. - unset($all_scope_validators[0]); - foreach($all_scope_validators as $v) { - foreach($validators[ $v ] as $v_name => $validator) { - $status[ $v_name ] = 'todo'; - } - } - - return $status; - } - } - } - - // First validate the whole file. If any of these fail then halt validation. - foreach ($validators['file'] as $validator_name => $validator) { - if (isset($validator['validation callback']) AND function_exists($validator['validation callback'])) { - $status[ $validator_name ] = call_user_func($validator['validation callback'], $file); - - // If returned false then halt validation. - if ($status[ $validator_name ] === FALSE) { - // Fail the file and set the rest to TODO but set the project to passed - // first since it is assumed that project validator returned a passed value. - $status[ 'project_selected' ] = TRUE; - $status[ $validator_name ] = FALSE; - - // Todo the rest of validators. - // Since project is completed. skip this scope. - unset($all_scope_validators[0]); - foreach($all_scope_validators as $v) { - foreach($validators[ $v ] as $v_name => $validator) { - if ($status[ $v_name ] === TRUE) { - $status[ $v_name ] = TRUE; - } - elseif ($status[ $v_name ] === FALSE) { - $status[ $v_name ] = FALSE; - } - else { - $status[ $v_name ] = 'todo'; - } - } - } - - return $status; - } - } - } - - // Open the file for reading - $xls_obj = rawpheno_open_file($file); - - // Change to the correct spreadsheet. - rawpheno_change_sheet($xls_obj, 'measurements'); - - // This increment variable $i is required since xls and xlsx - // parsers assign array index differently. - // XLS starts at 1, while XLSX at 0; - $i = 0; - - // Variations of Not Applicable. - $not_applicable = array('na', 'n/a', 'n.a.'); - - // Skip columns. - $skip = array(); - // Project name. - $project_name = rawpheno_function_getproject($project_id); - // Calling all modules implementing hook_rawpheno_ignorecols_valsave_alter(): - drupal_alter('rawpheno_ignorecols_valsave', $skip, $project_name); - - // Iterate though each row. - $num_errored_rows = 0; - $storage = array(); - foreach($xls_obj as $row) { - $i++; - - // Convert row into a string and check the length. - // This will exclude empty rows. - if (strlen(trim(implode('', $row))) >= 5) { - - // VALIDATE THE HEADER. - if ($i == 1) { - // Save the header for later. - $header = array(); - $new_header = array(); - // Checking plot value requires cell value in Planting Date (date) and Location. - // Store index numbers of these two traits. - $plot_req = array(); - - $o = 0; - foreach ($row as $r) { - $without_format = rawpheno_function_delformat($r); - - // To maintain index of both cells and header, tag either to skip or process - // based on headers in drupal_alter hook. - $s = (in_array($without_format, $skip)) ? 1 : 0; - - // Remove new lines. - $rem_newline = str_replace(array("\n", "\r"), ' ', $r); - // Remove extra spaces. - $rem_spaces = preg_replace('/\s+/', ' ', $rem_newline); - // Remove leading and trailing spaces. - $r = trim($rem_spaces); - $no_units = rawpheno_get_trait_name($r); - - $header[] = array( - 'no format' => $without_format, - 'original' => $r, - 'units' => rawpheno_function_unit($without_format), - 'no units' => $no_units, - 'skip' => $s, - ); - - // Store index number of Plot trait requirements. - if (!isset($plot_req['planting date (date)']) && $without_format == 'plantingdate(date)') { - $plot_req['planting date (date)'] = $o; - } - elseif (!isset($plot_req['location']) && $without_format == 'location') { - $plot_req['location'] = $o; - } - - $o++; - } - - // Foreach validator with a scope of header, execute the validation callback & save the results. - foreach($validators['header'] as $validator_name => $validator) { - if (isset($validator['validation callback']) AND function_exists($validator['validation callback'])) { - $result = call_user_func($validator['validation callback'], $header, $project_id); - - // The status needs to keep track of which rows failed for a given header. - if ($result === FALSE) { - $status[ $validator_name ] = $i; - } - elseif (is_array($result)) { - $status[ $validator_name ] = $result; - } - } - } - } - // VALIDATE THE ROW. - else { - - $row_has_error = FALSE; - foreach ($row as $column_index => $cell) { - if ($header[$column_index]['skip'] == 1) continue; - - $column_name = $header[$column_index]['no units']; - if (empty($column_name)) continue; - - // Prior to validating, Remove non-breaking whitespace by converting it to a blank space instead of removing it, - // in case user intends a space between words/values. - // trim() implementation below should drop unecessary leading and trailing spaces. - if (preg_match('/\xc2\xa0/', $cell)) { - $cell = preg_replace('/\xc2\xa0/', ' ', $cell); - } - - // We always want to strip flanking white space. - // FYI: This is done when the data is loaded as well. - $cell = trim($cell); - - // For consistency, convert all variations of not applicable to NA. - if (is_string($cell) && in_array(strtolower($cell), $not_applicable)) { - $cell = 'NA'; - } - - // Foreach validator: - foreach (array('all','subset') as $scope) { - foreach($validators[$scope] as $validator_name => $validator) { - - // Only validate if there is a validation callback. - if (isset($validator['validation callback']) AND function_exists($validator['validation callback'])) { - - // Only validate if the current validator applies to the current column. - // Specifically, if there are no defined headers it's applicable to - // OR if the current header is in the list of applicable headers. - if (!isset($validator['headers']) OR in_array($column_name, $validator['headers'])) { - - // Execute the validation callback & save the results. - $tmp_storage = (isset($storage[$validator_name])) ? $storage[$validator_name] : array(); - $context = array( - 'row index' => $i, - 'column index' => $column_index, - 'row' => $row, - 'header' => $header - ); - - // If column header is Plot, attach Plot validation requirement to - // $context array. The indexes will be used to fetch the cell value in context row. - if ($column_name == 'Plot') { - $context['plot_req'] = $plot_req; - } - - $result = $validator['validation callback']($cell, $context, $tmp_storage, $project_id); - - // Note: we use tmp storage b/c passing $storage[$validator_name] directly - // doesn't seem to work. - $storage[$validator_name] = $tmp_storage; - - // The status needs to keep track of which rows failed for a given header. - if (is_array($result)) { - $status[ $validator_name ][ $column_name ][$i] = $result; - $row_has_error = TRUE; - } - elseif ($result !== TRUE) { - $status[ $validator_name ][ $column_name ][$i] = $i; - $row_has_error = TRUE; - } - } - } - } - } - } - - if ($row_has_error) $num_errored_rows++; - } - - // Only check until you have 10 rows with errors. - if ($num_errored_rows >= 10) { - // We only want to present the warning if this is not the end of the file ;-) - $has_next = $xls_obj->next(); - if ($has_next AND strlen(trim(implode('', $has_next))) >= 1) { - $check_limit_message = "We have only checked the first $i lines of your file. Please fix the errors reported below and then upload the fixed file."; - - if ($source == 'upload') { - drupal_set_message($check_limit_message, 'error'); - return $status; - } - elseif ($source == 'backup') { - return array('status' => $status, 'check_limit' => $check_limit_message); - } - } - } - } - } - - // Make sure all validators are represented in status. - // If they are not already then a failure wasn't recorded -thus they passed :-). - foreach($all_validators as $validator_name => $validator) { - if (!isset($status[$validator_name])) { - $status[$validator_name] = TRUE; - } - } - - return $status; -} - -/** - * Open the Excel file using the spreadsheet reader. - * - * @param $file - * A Drupal managed file object. - * @return - * An object representing the Excel file. - */ -function rawpheno_open_file($file) { - // Grab the path and extension from the file. - $xls_file = drupal_realpath($file->uri); - $xls_extension = pathinfo($file->filename, PATHINFO_EXTENSION); - - // Validate that the spreadsheet is either xlsx or xls and open the spreadsheet using - // the correct class. - // XLSX: - if ($xls_extension == 'xlsx') { - $xls_obj = new SpreadsheetReader_XLSX($xls_file); - } - // XLS: - elseif ($xls_extension == 'xls') { - // PLS INCLUDE THIS FILE ONLY FOR XLS TYPE. - $xls_lib = libraries_load('spreadsheet_reader'); - $lib_path = $xls_lib['path']; - - include_once $lib_path . 'SpreadsheetReader_XLS.php'; - $xls_obj = new SpreadsheetReader_XLS($xls_file); - } - - return $xls_obj; -} - -/** - * Changes the worksheet in the Excel Object. - * - * @param $xls_obj - * The object describing this Excel workbook. - * @param $tab_name - * The name of the tab you would like to switch to. - * @return - * TRUE if it found the tab and FALSE otherwise. - */ -function rawpheno_change_sheet(&$xls_obj, $tab_name) { - // Get all the sheets in the workbook. - $xls_sheets = $xls_obj->Sheets(); - - // Locate the measurements sheet. - foreach($xls_sheets as $sheet_key => $sheet_value) { - $xls_obj->ChangeSheet($sheet_key); - - // Only process the measurements worksheet. - if (rawpheno_function_delformat($sheet_value) == 'measurements') { - return TRUE; - } - } - - return FALSE; -} - - -/** - * Adds the necessary files for EXCEL parsing. - */ -function rawpheno_add_parsing_libraries($file_type = 'XLSX') { - // Function call libraries_load() base on the implementation - // of hook_libraries_info() in rawpheno.module. - $xls_lib = libraries_load('spreadsheet_reader'); - // Library path information returned will be used - // to include individual library files required. - $lib_path = $xls_lib['path']; - - // Include parser library. PLS DO NOT ALTER ORDER!!! - // To stop parser from auto formatting date to MM/DD/YY, - // suggest a new date format YYYY-mm-dd in: - // line 678 in excel_reader2.php - // 0xe => "m/d/Y", to 0xe => "Y-m-d", - // line 834 in SpreadsheetReader_XLSX.php - // $Value = $Value -> format($Format['Code']); to $Value = $Value -> format('Y-m-d'); - // - include_once $lib_path . 'php-excel-reader/excel_reader2.php'; - include_once $lib_path . 'SpreadsheetReader_XLSX.php'; - include_once $lib_path . 'SpreadsheetReader.php'; - - if ($file_type == 'XLS') { - // PLS INCLUDE THIS FILE ONLY FOR XLS TYPE. - include_once $lib_path . 'SpreadsheetReader_XLS.php'; - } -} - - -/** - * Function to remove all formatting from a cell value. - * - * @param $xls_cell_value - * Contains a value of a cell. - * @return - * Contains a cell value with all formatting removed. - */ -function rawpheno_function_delformat($xls_cell_value) { - // Remove any extra spaces, new lines, leading and trainling spaces - // and covert the final result to lowercase. - return trim(strtolower(preg_replace('!\s+!', '', $xls_cell_value))); -} - - -/** - * Function to extract the unit from the column header. - * - * @param $xls_header_cell - * A string containing a column header. - * @return - * A string containing the unit found from the column header. - */ -function rawpheno_function_unit($xls_header_cell) { - // Remove all formatting. - $temp_value = rawpheno_function_delformat($xls_header_cell); - - // If this is a scale then return that. - if (preg_match('/\(scale/',$temp_value)) { - return 'scale'; - } - - // Remove the following characters. - $cell_value = str_replace(array(';', '1st', '2nd', 'r1', 'r3', 'r5', 'r7', ': 1-5'), '', $temp_value); - - // Extract text information inside the parenthesis. - preg_match("/.*\(([^)]*)\)/", $cell_value, $match); - - // Return unit found, or default to text if no unit. - return (isset($match[1])) ? trim($match[1]) : 'text'; -} - - -/** - * Function to determine additional column headers in the spreadsheet. Additional column headers are - * headers that are no part of the predefined headers set of the project. - * - * @param $file - * The full path to the excel file containing data. - * @param $project_id - * Project id number the spreadsheet is specific to. - * @return - * An array containing all additional column headers detected. - */ -function rawpheno_indicate_new_headers($file, $project_id) { - // Retrieve the header for the indicated file. - rawpheno_add_parsing_libraries(); - $xls_obj = rawpheno_open_file($file); - rawpheno_change_sheet($xls_obj, 'measurements'); - - // Note: we use the foreach here - // because the library documentation doesn't have a single fetch function. - foreach ($xls_obj as $xls_headers) { break; } - - // Array to hold epected column headers specific to a given project. - $expected_headers = rawpheno_project_traits($project_id); - - // Remove any formatting in each column headers. - $expected_headers = array_map('rawpheno_function_delformat', $expected_headers); - - // Array to hold new column headers. - $new_headers = array(); - - // Assuming the file actually has a non-empty header row... - if (count($xls_headers) > 0) { - // Read each column header and compare against expected column headers. - foreach($xls_headers as $value) { - $temp_value = rawpheno_function_delformat($value); - - // Determine if column header exists in the expected column headers. - if (!in_array($temp_value, $expected_headers) && !empty($value)) { - // Not in expected column headers, save it as new header. - $value = preg_replace('/\s+/', ' ', $value); - $new_headers[] = $value; - } - } - } - - return $new_headers; -} - - -/** - * Get all column headers. - * - * @param $file - * The full path to the excel file containing data. - * @return - * An array of headers. - */ -function rawpheno_all_headers($file) { - // Retrieve the header for the indicated file. - rawpheno_add_parsing_libraries(); - $xls_obj = rawpheno_open_file($file); - rawpheno_change_sheet($xls_obj, 'measurements'); - // Note: we use the foreach here - // because the library documentation doesn't have a single fetch function. - - $arr_headers = array(); - foreach ($xls_obj as $xls_headers) { - foreach($xls_headers as $h) { - if (strlen($h) > 2) { - $arr_headers[] = trim($h); - } - } - break; - } - - return $arr_headers; -} - - -/** - * Count all rows in a spreadsheet. - * - * @param $file - * The full path to the excel file containing data. - * @return - * An integer value of the total rows. - */ -function rawpheno_count_rows($xls_obj) { - // Row of 5 chars or more long is a row. - $count_rows = 0; - foreach ($xls_obj as $row) { - if (strlen(implode('', $row)) > 5) { - $count_rows++; - } - } - - // Less header row. - return $count_rows - 1; -} - - -/** - * Retrieve the cvterm_id for a given header. - * - * @param $header - * The unchanged/original header text for the trait. - * @return - * The cvterm_id for the trait. - */ -function rawpheno_get_trait_id($header) { - // New lines. - $header = str_replace(array("\n", "\r"), ' ', $header); - // Extra spaces. - $header = preg_replace('!\s+!', ' ', $header); - - // Query trait. Module stores unit in lowercase but user can use any case - // in the spreadsheet. eg Planting Date (date) and Planting Date (Date). - // @note cvterm.name + cv.name + not obsolete combination is unique (constraints). - $sql = "SELECT t2.cvterm_id - FROM {cv} AS t1 INNER JOIN {cvterm} AS t2 USING(cv_id) - WHERE lower(t2.name) = :cvterm_name AND t1.name = :cv_name AND is_obsolete = 0"; - - $args = array(':cvterm_name' => trim(strtolower($header)), ':cv_name' => 'phenotype_measurement_types'); - $type = chado_query($sql, $args) - ->fetchObject(); - - if ($type->cvterm_id) { - return $type->cvterm_id; - } - - return FALSE; -} - - -/** - * Retrieve the unit for the trait. - * - * @param $trait_name - * The name of the trait as found in the column header. - * @param $trait_id - * The cvterm_id of the trait if you have it (OPTIONAL). - * @return - * Returns an array with the cvterm_id and name of the unit. - */ -function rawpheno_get_trait_unit($trait_name, $trait_id = NULL) { - // Get the trait id if that is not provided to us. - if ($trait_id == NULL) { - $trait_id = rawpheno_get_trait_id($trait_name); - } - - // First we try to get the unit through relationships since that avoids making assumptions. - // in chado.cvterm_relationship. - // @todo make this more specific (restrict relationship by type?) - // @todo rather then limit, check if there are 1+ and warn the admin. - $sql = "SELECT cvterm_id, name FROM {cvterm} WHERE cvterm_id = - (SELECT subject_id FROM {cvterm_relationship} WHERE object_id = :trait LIMIT 1)"; - - $args = array(':trait' => $trait_id); - $unit = chado_query($sql, $args); - - if ($unit->rowCount() > 0) { - $r = $unit->fetchObject(); - return array('id' => $r->cvterm_id, 'name' => $r->name); - } - - // If that doesn't work then we try to extract it from the name. - // Note: if the following function is unable to extract the unit then it will default to text. - $unit_name = rawpheno_function_unit($trait_name); - - // Column header does not contain unit, use text as default - if (function_exists('chado_get_cvterm')) { - $cvterm = chado_get_cvterm(array('name' => $unit_name, 'cv_id' => array('name' => 'phenotype_measurement_units'))); - } - else { - $cvterm = tripal_get_cvterm(array('name' => $unit_name, 'cv_id' => array('name' => 'phenotype_measurement_units'))); - } - - if ($cvterm) { - return array('id' => $cvterm->cvterm_id, 'name' => $cvterm->name); - } - - return FALSE; -} - - -/** - * Remove the unit part from a trait. - * - * @param $trait_name - * A string containing the trait name as formatted in cvterm name. - * @return - * A string containing the trait name without the unit. - */ -function rawpheno_get_trait_name($trait_name) { - $t = explode('(', $trait_name); - - // Given a trait as defined in cvterm name in the following format: - // Trait name (Trait Rep; Unit), extract the trait name only and return - // the extracted name. - return (count($t) > 1) ? trim(preg_replace('/\(.*/', ' ', $trait_name)) : $trait_name; -} - - -/** - * Get all the essential traits in a project. - * - * @param $project_id - * An integer containing the project ID number. - * @return - * An array containing all essential traits in a project. - */ -function rawpheno_project_essential_traits($project_id) { - if (isset($project_id) AND $project_id > 0) { - // Get array of trait types - $trait_type = rawpheno_function_trait_types(); - - // Array to hold trait names. - $arr_essential_traits = array(); - - // Query essential traits in a project. - $sql = "SELECT TRIM(t1.name) AS cvterm - FROM {cvterm} AS t1 RIGHT JOIN pheno_project_cvterm AS t2 USING (cvterm_id) - WHERE - t2.project_id = :project_id - AND t2.type IN (:essential) - ORDER BY t1.name ASC"; - - $args = array(':project_id' => $project_id, ':essential' => array($trait_type['type1'], $trait_type['type4'])); - $trait = chado_query($sql, $args); - - foreach($trait as $t) { - $m = rawpheno_get_trait_name($t->cvterm); - $arr_essential_traits[] = $m; - } - - // Add Name column header to the traits returned. - $arr_essential_traits[] = 'Name'; - - return $arr_essential_traits; - } -} - - -/** - * Get all the plant property traits in a project selected. - * - * @param $project_id - * An integer containing the project ID number. - * @return - * An array containing all essential traits in a project. - */ -function rawpheno_project_plantproperty_traits($project_id) { - if (isset($project_id) AND $project_id > 0) { - // Get array of trait types - $trait_type = rawpheno_function_trait_types(); - - // Array to hold trait names. - $arr_plantproperty_traits = array(); - - // Query plant property traits in a project. - $sql = "SELECT TRIM(t1.name) AS cvterm - FROM {cvterm} AS t1 RIGHT JOIN pheno_project_cvterm AS t2 USING (cvterm_id) - WHERE t2.project_id = :project_id AND t2.type = :plantproperty - ORDER BY t1.name ASC"; - - // traits of type plantproperty. - $args = array(':project_id' => $project_id, ':plantproperty' => $trait_type['type4']); - $trait = chado_query($sql, $args); - - foreach($trait as $t) { - // Remove the trait rep and unit from the trait. - $m = rawpheno_get_trait_name($t->cvterm); - $arr_plantproperty_traits[] = $m; - } - - return $arr_plantproperty_traits; - } -} - - -/** - * Get all traits available in a project whether essential or not. - * - * @param $project_id - * An integer containing the project ID number. - * @return - * An array containing all traits available in a project. - */ -function rawpheno_project_traits($project_id) { - if (isset($project_id) AND $project_id > 0) { - $arr_trait = array(); - - // Query column headers in a project. - $sql = "SELECT TRIM(t1.name) AS cvterm - FROM {cvterm} AS t1 RIGHT JOIN pheno_project_cvterm AS t2 USING (cvterm_id) - WHERE t2.project_id = :project_id - ORDER BY t1.name ASC"; - - $args = array(':project_id' => $project_id); - $trait = chado_query($sql, $args); - - foreach($trait as $t) { - $arr_trait[] = $t->cvterm; - } - - // Add Name column header to the traits returned. - $arr_trait[] = 'Name'; - - return $arr_trait; - } -} - - -/** - * Function to fetch information about a unit (Describe method) when available. - * - * @param $cvterm_id - * An integer containing the cvterm id of a trait. - */ -function rawpheno_function_cvterm_properties($cvterm_id) { - // Narrow the search to cvterm of type measurement units. - if (function_exists('chado_get_cv')) { - $cv_unit = chado_get_cv(array('name' => 'phenotype_measurement_units')); - } - else { - $cv_unit = tripal_get_cv(array('name' => 'phenotype_measurement_units')); - } - - // In form state 2, describe header, user has the opportunity to describe the unit (Describe method field). - // This information is stored in cvterm relationship together with the cvterm id of the unit as the subject_id - // and cvterm_id of the header as the object_id. Given a header cvterm id, get the subject id and use it to - // get the information required from cvtermprop table. - // @todo check if there is more then one unit for a given trait and if so, warn the admin. - $sql = "SELECT subject_id FROM {cvterm_relationship} WHERE object_id = :cvterm_id AND type_id = :cv_unit LIMIT 1"; - $args = array(':cvterm_id' => $cvterm_id, ':cv_unit' => $cv_unit->cv_id); - - $d = chado_query($sql, $args) - ->fetchField(); - - // @note cvterm_id + type_id + rank is unique (constraint). - $sql = "SELECT value FROM {cvtermprop} WHERE type_id = :cv_unit AND cvterm_id = :cvterm_id AND rank = 0"; - $args = array(':cv_unit' => $cv_unit->cv_id, ':cvterm_id' => $d); - - $d = chado_query($sql, $args); - - if ($d->rowCount() == 1) { - return $d->fetchField(); - } - else { - return 'Describe the method used not available'; - } -} + $fid), + array('print' => TRUE) + ); + tripal_report_error('rawpheno', TRIPAL_CRITICAL, '[CODE 100] Failed to load phenoypic data (job !id)', array('!id' => $job_id), array('print' => TRUE)); + exit(1); + } + + $xls_file = drupal_realpath($file->uri); + + // INFO: + // Keep a record of which file we are loading in the logs. + // print "\nXLSX File: " . $xls_file . "\n"; + + // Add the libraries needed to parse excel files. + rawpheno_add_parsing_libraries(); + + // Open the file for reading + $xls_obj = rawpheno_open_file($file); + if (!$xls_obj) { + tripal_report_error( + 'rawpheno', + TRIPAL_CRITICAL, + 'Uploading Phenoypic Data: Unable to open file. File=@file', + array('@file' => print_r($file, TRUE)), + array('print' => TRUE) + ); + tripal_report_error('rawpheno', TRIPAL_CRITICAL, '[CODE 101] Failed to load phenoypic data (job !id)', array('!id' => $job_id), array('print' => TRUE)); + exit(2); + } + + // Change to the correct spreadsheet. + rawpheno_change_sheet($xls_obj, 'measurements'); + + // Compute the total number of rows parsed. + $row_count = rawpheno_count_rows($xls_obj); + + // File to write progress status. + $tmp = file_directory_temp(); + $filename = $tmp . '/' . 'job-progress' . $job_id . '.txt'; + + // Variations of Not Applicable. + $not_applicable = array('na', 'n/a', 'n.a.'); + + // Start Transaction. + $TRANSACTION = db_transaction(); + try { + + // Read each row. + // INFO: + // print "\nNow parsing each row and saving it to the database...\nNumber of rows saved: \n"; + $i = 0; + + // Skip columns. + $skip = array(); + // Project name. + $project_name = rawpheno_function_getproject($project_id); + // Calling all modules implementing hook_rawpheno_ignorecols_valsave_alter(): + drupal_alter('rawpheno_ignorecols_valsave', $skip, $project_name); + + // Each row in the spreadsheet. + foreach ($xls_obj as $row) { + // Create progress update by computing the number of rows saved in percent. + // Write to file and progress bar API reads the content and pass it JSON generator + // in file rawpheno.module function: rawpheno_upload_job_progress_json(). + // Echo to terminal. + + $percent = round(($i / $row_count) * 100); + if ($percent % 10 == 0) { + print $percent . '% complete...' . "\n"; + } + + // To file. + file_unmanaged_save_data($percent, $filename, FILE_EXISTS_REPLACE); + + // HEADER! + // This is the header. + if ($i == 0) { + $header = $row; + + // Find the index number of name header in the spreadsheet. + $name_index = array_search('name', array_map('rawpheno_function_delformat', $header)); + if ($name_index === FALSE) { + tripal_report_error( + 'rawpheno', + TRIPAL_CRITICAL, + 'Uploading Phenoypic Data: Unable to determine the name column.', + array(), + array('print' => TRUE) + ); + $TRANSACTION->rollback(); + tripal_report_error('rawpheno', TRIPAL_CRITICAL, '[CODE 102] Failed to load phenoypic data (job !id)', array('!id' => $job_id), array('print' => TRUE)); + exit(31); + } + + $i++; + continue; + } + + // NEXT ROW IF EMPTY! + // Don't continue processing if this row is empty. + if (strlen(trim(implode('', $row))) <= 5) { + break; + } + + // VALID EACH ROW! + // Process the name column first since we need a plant_id before we can insert any more data. + // Name column header goes into pheno_plant. + // Check if stock name exists. + unset($stock_id); + + // Prior to saving, Remove non-breaking whitespace by converting it to a blank space instead of removing it, + // in case user intends a space between words/values. + // trim() implementation below should drop unecessary leading and trailing spaces. + if (preg_match('/\xc2\xa0/', $row[$name_index])) { + $row[$name_index] = preg_replace('/\xc2\xa0/', ' ', $row[$name_index]); + } + + $stock_id = rawpheno_function_getstockid(trim($row[$name_index]), $project_name); + // print $stock_id . ' -- ' . $row[$name_index] . "\n"; + + // Determine if name has a stock id number. + if (isset($stock_id) && $stock_id > 0) { + $p_id = 0; + + // Test if stock was measured in the active project, if not, insert as a new record. + // Otherwise, do more check (plot) to see if plant_id should be re-used. + $sql = " + SELECT plant_id + FROM pheno_plant AS t1 INNER JOIN pheno_plant_project AS t2 USING(plant_id) + WHERE t1.stock_id = :stock_id AND t2.project_id = :project_id LIMIT 1"; + + $args = array(':stock_id' => $stock_id, ':project_id' => $project_id); + $p = chado_query($sql, $args); + + if ($p->rowCount()) { + // Found a stock record in the project. Do more test. + // Array to hold plot headers. + // Plot, Rep, Location, Planting Date (date) + $arr_plot_cols = rawpheno_function_headers('plot'); + + // Construct query string. + // String : stock_id - plot - rep - location - year + // eg. 147-5-2-Saskatoon-2015 + $plot = $stock_id; + + // Given a row, construct the search string (format) above and use it to search if + // the such combination matched any record in the database. + foreach($arr_plot_cols as $plot_col) { + $plot_col = rawpheno_function_delformat($plot_col); + + // Cell value of plot property header. + $col_index = array_search(strtolower($plot_col), array_map('rawpheno_function_delformat', $header)); + $cell_val = trim($row[$col_index]); + + // If planting date - extract the year value. + // Support NA and YYYY in planting date. Use this value when + // value cannot be split by -. + if ($plot_col == 'plantingdate(date)') { + $y = explode('-', $cell_val); + + if (is_array($y)) { + // Extract the year only from planting date. + $cell_val = $y[0]; + } + + // Else use the NA or YYYY. + } + + $plot .= '-' . $cell_val; + } + + // Search the query string. + $p_id = rawpheno_function_plot_exists($plot, $project_id); + } + + + if ($p_id) { + // Plot found - re-use the plant_id. + $pheno_plantid = $p_id; + // INFO: + // print 'FOUND PLOT: ' . $plot . ' [re-using plot id #' . $pheno_plantid . '] ~ '; + } + else { + // Plot not found - insert as new row. + $pheno_plantid = db_insert('pheno_plant') + ->fields(array('stock_id' => $stock_id)) + ->execute(); + + // Map this record/stock to a project. + db_insert('pheno_plant_project') + ->fields(array('project_id' => $project_id, + 'plant_id' => $pheno_plantid)) + ->execute(); + + // INFO: + // print 'NEW STOCK: #' . $stock_id . ' [adding plot id #' . $pheno_plantid . '] ~ '; + } + } + else { + // Warn the admin that germplasm is not available... + // We want to stop loading if this is the case. + tripal_report_error( + 'rawpheno', + TRIPAL_CRITICAL, + 'Uploading Phenoypic Data: Germplasm doesn\'t exist (name=!name; row=!row)', + array('!name' => $row[$name_index], '!row' => $i), + array('print' => TRUE) + ); + $TRANSACTION->rollback(); + tripal_report_error('rawpheno', TRIPAL_CRITICAL, '[CODE 103] Failed to load phenoypic data (job !id)', array('!id' => $job_id), array('print' => TRUE)); + exit(32); + } + + // Read each row and each cell. + // Each row will be an array where name is always the first element. + foreach($row as $cell_index => $cell_entry) { + // Skip this cell when col from durpal_alter hook matches the col. + $h = rawpheno_function_delformat($header[$cell_index]); + + if (count($skip) > 0 && in_array($h, $skip)) { + // print 'skipping value : ' . $h . '=' . $cell_entry . "\n"; + continue; + } + + // For consistency, convert all variations of not applicable to NA. + if (is_string($cell_entry) && in_array(strtolower($cell_entry), $not_applicable)) { + $cell_entry = 'NA'; + } + + // We don't want to insert empty data. + // That said, while PHP thinks 0 is empty, we do not. + if (!empty($cell_entry) OR (strval($cell_entry) === '0')) { + + // Get the column header of a cell. + $cell_colheader = trim(str_replace(array("\n", "\r", " "), ' ', $header[$cell_index])); + // Remove additional spaces from column headers. + $cell_colheader = preg_replace('/\s+/', ' ', $cell_colheader); + + + // Determine if user wants to save this trait. + if (count($arr_newheaders) > 0 AND array_key_exists($cell_colheader, $arr_newheaders)) { + if ($arr_newheaders[$cell_colheader]['flag'] == 0) { + // Skip this cell if it is a new column header and user does not want to save + // this new trait; + continue; + } + elseif ($arr_newheaders[$cell_colheader]['flag'] == 1) { + // Get the cvterm name for this new header. + $alt_name = $arr_newheaders[$cell_colheader]['alt_header']; + $n = array('cvterm_id' => $alt_name, 'cv_id' => array('name' => 'phenotype_measurement_types')); + + if (function_exists('chado_get_cvterm')) { + $name = chado_get_cvterm($n); + } + else { + $name = tripal_get_cvterm($n); + } + + $cell_colheader = $name->name; + } + } + + // Prior to saving, Remove non-breaking whitespace by converting it to a blank space instead of removing it, + // in case user intends a space between words/values. + // trim() implementation below should drop unecessary leading and trailing spaces. + if (preg_match('/\xc2\xa0/', $cell_entry)) { + $cell_entry = preg_replace('/\xc2\xa0/', ' ', $cell_entry); + } + + // We always want to strip flanking white space. + // FYI: This is done when the data is validated as well. + $cell_entry = trim($cell_entry); + $cell_colheader = trim($cell_colheader); + + // Determine which table to insert a column header. + // If this is the name column then doing nothing since we've already delt with it above. + if ($cell_index == $name_index) { continue; } + + // PLOT, ENTRY, REP and LOCATION + // Cells containing column headers that are required. + // Traits: plot, entry, rep, location into pheno_plantprop. + elseif (in_array($cell_colheader, $plantprop_headers) && !empty($cell_colheader)) { + $t = array('name' => $cell_colheader, 'cv_id' => array('name' => 'phenotype_plant_property_types')); + + if (function_exists('chado_get_cvterm')) { + $type = chado_get_cvterm($t); + } + else { + $type = tripal_get_cvterm($t); + } + + $type_id = $type->cvterm_id; + + // Ensure that cvterm_id is present before inserting to table + if(isset($type_id)) { + $tmp = db_insert('pheno_plantprop') + ->fields(array('plant_id' => $pheno_plantid, + 'type_id' => $type_id, + 'value' => $cell_entry)) + ->execute(); + + if (!$tmp) { + tripal_report_error( + 'rawpheno', + TRIPAL_ERROR, + 'Uploading Phenoypic Data: Unable to insert plant property. Values=@values', + array('@values' => print_r(array('plant_id' => $pheno_plantid, 'type_id' => $type_id, 'value' => $cell_entry),TRUE)), + array('print' => TRUE) + ); + $TRANSACTION->rollback(); + tripal_report_error('rawpheno', TRIPAL_CRITICAL, '[CODE 104] Failed to load phenoypic data (job !id)', array('!id' => $job_id), array('print' => TRUE)); + exit(33); + } + } + + else { + tripal_report_error( + 'rawpheno', + TRIPAL_ERROR, + 'Uploading Phenoypic Data: Plant Property type !type does\'t exist.', + array('!type' => $cell_colheader), + array('print' => TRUE) + ); + $TRANSACTION->rollback(); + tripal_report_error('rawpheno', TRIPAL_CRITICAL, '[CODE 105] Failed to load phenoypic data (job !id)', array('!id' => $job_id), array('print' => TRUE)); + exit(34); + } + + } + // THE REST OF THE COLUMN HEADERS + // Everything else into pheno_measurements. + elseif ((!empty($cell_colheader) && $cell_entry != 'NA') + || ($cell_colheader == 'Planting Date (date)' && $cell_entry == 'NA')) { + + // Allow NA only when header is planting date. + + $c_h = rawpheno_function_delformat($cell_colheader); + if (in_array($c_h, $skip)) { + // print 'skipping header : ' . $cell_colheader . "\n"; + continue; + } + + // Get the cvterm_id for the trait measurement. + $type_id = rawpheno_get_trait_id($cell_colheader); + + if (!$type_id) { + tripal_report_error( + 'rawpheno', + TRIPAL_ERROR, + 'Uploading Phenoypic Data: Missing Plant Measurement Type (Header=!colheader).', + array('!colheader' => $cell_colheader), + array('print' => TRUE) + ); + $TRANSACTION->rollback(); + tripal_report_error('rawpheno', TRIPAL_CRITICAL, '[CODE 106] Failed to load phenoypic data (job !id)', array('!id' => $job_id), array('print' => TRUE)); + exit(37); + } + + // Retrieve the unit for this trait. + $cv_unit = rawpheno_get_trait_unit($cell_colheader, $type_id); + + if ($cv_unit) { + $unit_id = $cv_unit['id']; + $unit = $cv_unit['name']; + } + else { + tripal_report_error( + 'rawpheno', + TRIPAL_ERROR, + 'Uploading Phenoypic Data: Unable to find unit for Plant Measurement Type (Term=!name; Type ID=!id).', + array('!name' => $cell_colheader, '!id' => $type_id), + array('print' => TRUE) + ); + $TRANSACTION->rollback(); + tripal_report_error('rawpheno', TRIPAL_CRITICAL, '[CODE 107] Failed to load phenoypic data (job !id)', array('!id' => $job_id), array('print' => TRUE)); + exit(37); + } + + // Determine if cell requires scale member code. + // When unit is scale, find code equivalent in pheno_scale_member table. + if ($unit == 'scale') { + // Get pheno scale member code + $cvalue_id = db_query("SELECT member_id FROM {pheno_scale_member} + WHERE code = :code LIMIT 1", + array(':code' => trim($cell_entry))) + ->fetchField(); + // We want to report an error if we can't find the scale memeber + // but only if there are any in the first place! + $num_members = db_query('SELECT count(*) FROM {pheno_scale_member} WHERE scale_id=:unit_id', + array(':unit_id' => $unit_id))->fetchField(); + if (!$cvalue_id AND !empty($num_members)) { + tripal_report_error( + 'rawpheno', + TRIPAL_WARNING, + 'Uploading Phenoypic Data: Unable to find scale id for Plant Measurement Type (Trait=!trait; Term=!name; Type ID=!id; Scale Value=!scale).', + array('!trait' => $cell_colheader, '!name' => $unit, '!id' => $unit_id, '!scale' => $cell_entry), + array('print' => TRUE) + ); + } + + // Use default value in the cell if query to find scale member code + // has no equivalent value. + $cvalue_id = (isset($cvalue_id) && $cvalue_id > 0) ? $cvalue_id : $cell_entry; + } + else { + // No scale member value for the rest of traits. + $cvalue_id = ''; + } + + // Insert trait only when type_id and unit_id are not null. + if (isset($type_id) && isset($unit_id)) { + + $temp = db_insert('pheno_measurements') + ->fields(array('plant_id' => $pheno_plantid, + 'type_id' => $type_id, + 'unit_id' => $unit_id, + 'cvalue_id' => $cvalue_id, + 'value' => $cell_entry, + 'modified' => date("D M d, Y h:i:s a", time()))) + ->execute(); + + if (!$temp) { + tripal_report_error( + 'rawpheno', + TRIPAL_ERROR, + 'Uploading Phenoypic Data: Unable to insert measurement. Values=@values.', + array('@values' => print_r(array('plant_id' => $pheno_plantid, + 'type_id' => $type_id, + 'unit_id' => $unit_id, + 'cvalue_id' => $cvalue_id, + 'value' => $cell_entry, + 'modified' => date("D M d, Y h:i:s a", time())),TRUE)), + array('print' => TRUE) + ); + $TRANSACTION->rollback(); + tripal_report_error('rawpheno', TRIPAL_CRITICAL, '[CODE 108] Failed to load phenoypic data (job !id)', array('!id' => $job_id), array('print' => TRUE)); + exit(38); + } + } + } + } + } + + $i++; + } + } + catch (Exception $e) { + $TRANSACTION->rollback(); + watchdog_exception('rawpheno', $e); + tripal_report_error('rawpheno', TRIPAL_CRITICAL, '[CODE 109] Failed to load phenoypic data (job !id)', array('!id' => $job_id), array('print' => TRUE)); + exit(4); + } + + unset($TRANSACTION); //Commit + print "Upload complete.\n"; + + print "\nUpdating the materialized view summarizing phenotypic data.\n"; + $mview_id = tripal_get_mview_id('rawpheno_rawdata_summary'); + if ($mview_id) tripal_populate_mview($mview_id); +} + + +/** + * Validates an excel file using any validators registered with rawpheno. + * + * @param $file + * A drupal managed_file object describing the uploaded spreadsheet. + * @param $project_id + * An integer containing project id selected in the project select box. + * This will map the data submitted to a project. + * @param $source + * A string containing the source of the file upload - Upload Data or Backup File. + * + * @return + * An array containing the validation result from each validator. + */ +function rawpheno_validate_excel_file($file, $project_id, $source) { + $status = array(); + + // Process the validators to make them easier to use. + // Specifically, sort them by their scope. + $validators = array(); + $all_validators = module_invoke_all('rawpheno_validators'); + foreach($all_validators as $k => $v) { + $validators[ $v['scope'] ][ $k ] = $v; + } + + // Todo list. + $all_scope_validators = array('project', 'file', 'all', 'header', 'subset'); + + // Add the libraries needed to parse excel files. + rawpheno_add_parsing_libraries(); + + // Before performing any validation to the excel file. Ensure first that a project is selected. + foreach ($validators['project'] as $prj_validator_name => $prj_validator) { + if (isset($prj_validator['validation callback']) AND function_exists($prj_validator['validation callback'])) { + $status[ $prj_validator_name ] = call_user_func($prj_validator['validation callback'], $project_id); + + // If returned false then halt validation. + if ($status[ $prj_validator_name ] === FALSE) { + // Fail the project and set the rest to TODO. + $status[ $prj_validator_name ] = FALSE; + + // Todo the rest of validators. + // Since this is project scope and it got falsed - remove the project. + unset($all_scope_validators[0]); + foreach($all_scope_validators as $v) { + foreach($validators[ $v ] as $v_name => $validator) { + $status[ $v_name ] = 'todo'; + } + } + + return $status; + } + } + } + + // First validate the whole file. If any of these fail then halt validation. + foreach ($validators['file'] as $validator_name => $validator) { + if (isset($validator['validation callback']) AND function_exists($validator['validation callback'])) { + $status[ $validator_name ] = call_user_func($validator['validation callback'], $file); + + // If returned false then halt validation. + if ($status[ $validator_name ] === FALSE) { + // Fail the file and set the rest to TODO but set the project to passed + // first since it is assumed that project validator returned a passed value. + $status[ 'project_selected' ] = TRUE; + $status[ $validator_name ] = FALSE; + + // Todo the rest of validators. + // Since project is completed. skip this scope. + unset($all_scope_validators[0]); + foreach($all_scope_validators as $v) { + foreach($validators[ $v ] as $v_name => $validator) { + if ($status[ $v_name ] === TRUE) { + $status[ $v_name ] = TRUE; + } + elseif ($status[ $v_name ] === FALSE) { + $status[ $v_name ] = FALSE; + } + else { + $status[ $v_name ] = 'todo'; + } + } + } + + return $status; + } + } + } + + // Open the file for reading + $xls_obj = rawpheno_open_file($file); + + // Change to the correct spreadsheet. + rawpheno_change_sheet($xls_obj, 'measurements'); + + // This increment variable $i is required since xls and xlsx + // parsers assign array index differently. + // XLS starts at 1, while XLSX at 0; + $i = 0; + + // Variations of Not Applicable. + $not_applicable = array('na', 'n/a', 'n.a.'); + + // Skip columns. + $skip = array(); + // Project name. + $project_name = rawpheno_function_getproject($project_id); + // Calling all modules implementing hook_rawpheno_ignorecols_valsave_alter(): + drupal_alter('rawpheno_ignorecols_valsave', $skip, $project_name); + + // Iterate though each row. + $num_errored_rows = 0; + $storage = array(); + foreach($xls_obj as $row) { + $i++; + + // Convert row into a string and check the length. + // This will exclude empty rows. + if (strlen(trim(implode('', $row))) >= 5) { + + // VALIDATE THE HEADER. + if ($i == 1) { + // Save the header for later. + $header = array(); + $new_header = array(); + // Checking plot value requires cell value in Planting Date (date) and Location. + // Store index numbers of these two traits. + $plot_req = array(); + + $o = 0; + foreach ($row as $r) { + $without_format = rawpheno_function_delformat($r); + + // To maintain index of both cells and header, tag either to skip or process + // based on headers in drupal_alter hook. + $s = (in_array($without_format, $skip)) ? 1 : 0; + + // Remove new lines. + $rem_newline = str_replace(array("\n", "\r"), ' ', $r); + // Remove extra spaces. + $rem_spaces = preg_replace('/\s+/', ' ', $rem_newline); + // Remove leading and trailing spaces. + $r = trim($rem_spaces); + $no_units = rawpheno_get_trait_name($r); + + $header[] = array( + 'no format' => $without_format, + 'original' => $r, + 'units' => rawpheno_function_unit($without_format), + 'no units' => $no_units, + 'skip' => $s, + ); + + // Store index number of Plot trait requirements. + if (!isset($plot_req['planting date (date)']) && $without_format == 'plantingdate(date)') { + $plot_req['planting date (date)'] = $o; + } + elseif (!isset($plot_req['location']) && $without_format == 'location') { + $plot_req['location'] = $o; + } + + $o++; + } + + // Foreach validator with a scope of header, execute the validation callback & save the results. + foreach($validators['header'] as $validator_name => $validator) { + if (isset($validator['validation callback']) AND function_exists($validator['validation callback'])) { + $result = call_user_func($validator['validation callback'], $header, $project_id); + + // The status needs to keep track of which rows failed for a given header. + if ($result === FALSE) { + $status[ $validator_name ] = $i; + } + elseif (is_array($result)) { + $status[ $validator_name ] = $result; + } + } + } + } + // VALIDATE THE ROW. + else { + + $row_has_error = FALSE; + foreach ($row as $column_index => $cell) { + if ($header[$column_index]['skip'] == 1) continue; + + $column_name = $header[$column_index]['no units']; + if (empty($column_name)) continue; + + // Prior to validating, Remove non-breaking whitespace by converting it to a blank space instead of removing it, + // in case user intends a space between words/values. + // trim() implementation below should drop unecessary leading and trailing spaces. + if (preg_match('/\xc2\xa0/', $cell)) { + $cell = preg_replace('/\xc2\xa0/', ' ', $cell); + } + + // We always want to strip flanking white space. + // FYI: This is done when the data is loaded as well. + $cell = trim($cell); + + // For consistency, convert all variations of not applicable to NA. + if (is_string($cell) && in_array(strtolower($cell), $not_applicable)) { + $cell = 'NA'; + } + + // Foreach validator: + foreach (array('all','subset') as $scope) { + foreach($validators[$scope] as $validator_name => $validator) { + + // Only validate if there is a validation callback. + if (isset($validator['validation callback']) AND function_exists($validator['validation callback'])) { + + // Only validate if the current validator applies to the current column. + // Specifically, if there are no defined headers it's applicable to + // OR if the current header is in the list of applicable headers. + if (!isset($validator['headers']) OR in_array($column_name, $validator['headers'])) { + + // Execute the validation callback & save the results. + $tmp_storage = (isset($storage[$validator_name])) ? $storage[$validator_name] : array(); + $context = array( + 'row index' => $i, + 'column index' => $column_index, + 'row' => $row, + 'header' => $header + ); + + // If column header is Plot, attach Plot validation requirement to + // $context array. The indexes will be used to fetch the cell value in context row. + if ($column_name == 'Plot') { + $context['plot_req'] = $plot_req; + } + + $result = $validator['validation callback']($cell, $context, $tmp_storage, $project_id); + + // Note: we use tmp storage b/c passing $storage[$validator_name] directly + // doesn't seem to work. + $storage[$validator_name] = $tmp_storage; + + // The status needs to keep track of which rows failed for a given header. + if (is_array($result)) { + $status[ $validator_name ][ $column_name ][$i] = $result; + $row_has_error = TRUE; + } + elseif ($result !== TRUE) { + $status[ $validator_name ][ $column_name ][$i] = $i; + $row_has_error = TRUE; + } + } + } + } + } + } + + if ($row_has_error) $num_errored_rows++; + } + + // Only check until you have 10 rows with errors. + if ($num_errored_rows >= 10) { + // We only want to present the warning if this is not the end of the file ;-) + $has_next = $xls_obj->next(); + if ($has_next AND strlen(trim(implode('', $has_next))) >= 1) { + $check_limit_message = "We have only checked the first $i lines of your file. Please fix the errors reported below and then upload the fixed file."; + + if ($source == 'upload') { + drupal_set_message($check_limit_message, 'error'); + return $status; + } + elseif ($source == 'backup') { + return array('status' => $status, 'check_limit' => $check_limit_message); + } + } + } + } + } + + // Make sure all validators are represented in status. + // If they are not already then a failure wasn't recorded -thus they passed :-). + foreach($all_validators as $validator_name => $validator) { + if (!isset($status[$validator_name])) { + $status[$validator_name] = TRUE; + } + } + + return $status; +} + +/** + * Open the Excel file using the spreadsheet reader. + * + * @param $file + * A Drupal managed file object. + * @return + * An object representing the Excel file. + */ +function rawpheno_open_file($file) { + // Grab the path and extension from the file. + $xls_file = drupal_realpath($file->uri); + $xls_extension = pathinfo($file->filename, PATHINFO_EXTENSION); + + // Validate that the spreadsheet is either xlsx or xls and open the spreadsheet using + // the correct class. + // XLSX: + if ($xls_extension == 'xlsx') { + $xls_obj = new SpreadsheetReader_XLSX($xls_file); + } + // XLS: + elseif ($xls_extension == 'xls') { + // PLS INCLUDE THIS FILE ONLY FOR XLS TYPE. + $xls_lib = libraries_load('spreadsheet_reader'); + $lib_path = $xls_lib['path']; + + include_once $lib_path . 'SpreadsheetReader_XLS.php'; + $xls_obj = new SpreadsheetReader_XLS($xls_file); + } + + return $xls_obj; +} + +/** + * Changes the worksheet in the Excel Object. + * + * @param $xls_obj + * The object describing this Excel workbook. + * @param $tab_name + * The name of the tab you would like to switch to. + * @return + * TRUE if it found the tab and FALSE otherwise. + */ +function rawpheno_change_sheet(&$xls_obj, $tab_name) { + // Get all the sheets in the workbook. + $xls_sheets = $xls_obj->Sheets(); + + // Locate the measurements sheet. + foreach($xls_sheets as $sheet_key => $sheet_value) { + $xls_obj->ChangeSheet($sheet_key); + + // Only process the measurements worksheet. + if (rawpheno_function_delformat($sheet_value) == 'measurements') { + return TRUE; + } + } + + return FALSE; +} + + +/** + * Adds the necessary files for EXCEL parsing. + */ +function rawpheno_add_parsing_libraries($file_type = 'XLSX') { + // Function call libraries_load() base on the implementation + // of hook_libraries_info() in rawpheno.module. + $xls_lib = libraries_load('spreadsheet_reader'); + // Library path information returned will be used + // to include individual library files required. + $lib_path = $xls_lib['path']; + + // Include parser library. PLS DO NOT ALTER ORDER!!! + // To stop parser from auto formatting date to MM/DD/YY, + // suggest a new date format YYYY-mm-dd in: + // line 678 in excel_reader2.php + // 0xe => "m/d/Y", to 0xe => "Y-m-d", + // line 834 in SpreadsheetReader_XLSX.php + // $Value = $Value -> format($Format['Code']); to $Value = $Value -> format('Y-m-d'); + // + include_once $lib_path . 'php-excel-reader/excel_reader2.php'; + include_once $lib_path . 'SpreadsheetReader_XLSX.php'; + include_once $lib_path . 'SpreadsheetReader.php'; + + if ($file_type == 'XLS') { + // PLS INCLUDE THIS FILE ONLY FOR XLS TYPE. + include_once $lib_path . 'SpreadsheetReader_XLS.php'; + } +} + + +/** + * Function to remove all formatting from a cell value. + * + * @param $xls_cell_value + * Contains a value of a cell. + * @return + * Contains a cell value with all formatting removed. + */ +function rawpheno_function_delformat($xls_cell_value) { + // Remove any extra spaces, new lines, leading and trainling spaces + // and covert the final result to lowercase. + return trim(strtolower(preg_replace('!\s+!', '', $xls_cell_value))); +} + + +/** + * Function to extract the unit from the column header. + * + * @param $xls_header_cell + * A string containing a column header. + * @return + * A string containing the unit found from the column header. + */ +function rawpheno_function_unit($xls_header_cell) { + // Remove all formatting. + $temp_value = rawpheno_function_delformat($xls_header_cell); + + // If this is a scale then return that. + if (preg_match('/\(scale/',$temp_value)) { + return 'scale'; + } + + // Remove the following characters. + $cell_value = str_replace(array(';', '1st', '2nd', 'r1', 'r3', 'r5', 'r7', ': 1-5'), '', $temp_value); + + // Extract text information inside the parenthesis. + preg_match("/.*\(([^)]*)\)/", $cell_value, $match); + + // Return unit found, or default to text if no unit. + return (isset($match[1])) ? trim($match[1]) : 'text'; +} + + +/** + * Function to determine additional column headers in the spreadsheet. Additional column headers are + * headers that are no part of the predefined headers set of the project. + * + * @param $file + * The full path to the excel file containing data. + * @param $project_id + * Project id number the spreadsheet is specific to. + * @return + * An array containing all additional column headers detected. + */ +function rawpheno_indicate_new_headers($file, $project_id) { + // Retrieve the header for the indicated file. + rawpheno_add_parsing_libraries(); + $xls_obj = rawpheno_open_file($file); + rawpheno_change_sheet($xls_obj, 'measurements'); + + // Note: we use the foreach here + // because the library documentation doesn't have a single fetch function. + foreach ($xls_obj as $xls_headers) { break; } + + // Array to hold epected column headers specific to a given project. + $expected_headers = rawpheno_project_traits($project_id); + + // Remove any formatting in each column headers. + $expected_headers = array_map('rawpheno_function_delformat', $expected_headers); + + // Array to hold new column headers. + $new_headers = array(); + + // Assuming the file actually has a non-empty header row... + if (count($xls_headers) > 0) { + // Read each column header and compare against expected column headers. + foreach($xls_headers as $value) { + $temp_value = rawpheno_function_delformat($value); + + // Determine if column header exists in the expected column headers. + if (!in_array($temp_value, $expected_headers) && !empty($value)) { + // Not in expected column headers, save it as new header. + $value = preg_replace('/\s+/', ' ', $value); + $new_headers[] = $value; + } + } + } + + return $new_headers; +} + + +/** + * Get all column headers. + * + * @param $file + * The full path to the excel file containing data. + * @return + * An array of headers. + */ +function rawpheno_all_headers($file) { + // Retrieve the header for the indicated file. + rawpheno_add_parsing_libraries(); + $xls_obj = rawpheno_open_file($file); + rawpheno_change_sheet($xls_obj, 'measurements'); + // Note: we use the foreach here + // because the library documentation doesn't have a single fetch function. + + $arr_headers = array(); + foreach ($xls_obj as $xls_headers) { + foreach($xls_headers as $h) { + if (strlen($h) > 2) { + $arr_headers[] = trim($h); + } + } + break; + } + + return $arr_headers; +} + + +/** + * Count all rows in a spreadsheet. + * + * @param $file + * The full path to the excel file containing data. + * @return + * An integer value of the total rows. + */ +function rawpheno_count_rows($xls_obj) { + // Row of 5 chars or more long is a row. + $count_rows = 0; + foreach ($xls_obj as $row) { + if (strlen(implode('', $row)) > 5) { + $count_rows++; + } + } + + // Less header row. + return $count_rows - 1; +} + + +/** + * Retrieve the cvterm_id for a given header. + * + * @param $header + * The unchanged/original header text for the trait. + * @return + * The cvterm_id for the trait. + */ +function rawpheno_get_trait_id($header) { + // New lines. + $header = str_replace(array("\n", "\r"), ' ', $header); + // Extra spaces. + $header = preg_replace('!\s+!', ' ', $header); + + // Query trait. Module stores unit in lowercase but user can use any case + // in the spreadsheet. eg Planting Date (date) and Planting Date (Date). + // @note cvterm.name + cv.name + not obsolete combination is unique (constraints). + $sql = "SELECT t2.cvterm_id + FROM {cv} AS t1 INNER JOIN {cvterm} AS t2 USING(cv_id) + WHERE lower(t2.name) = :cvterm_name AND t1.name = :cv_name AND is_obsolete = 0"; + + $args = array(':cvterm_name' => trim(strtolower($header)), ':cv_name' => 'phenotype_measurement_types'); + $type = chado_query($sql, $args) + ->fetchObject(); + + if ($type->cvterm_id) { + return $type->cvterm_id; + } + + return FALSE; +} + + +/** + * Retrieve the unit for the trait. + * + * @param $trait_name + * The name of the trait as found in the column header. + * @param $trait_id + * The cvterm_id of the trait if you have it (OPTIONAL). + * @return + * Returns an array with the cvterm_id and name of the unit. + */ +function rawpheno_get_trait_unit($trait_name, $trait_id = NULL) { + // Get the trait id if that is not provided to us. + if ($trait_id == NULL) { + $trait_id = rawpheno_get_trait_id($trait_name); + } + + // First we try to get the unit through relationships since that avoids making assumptions. + // in chado.cvterm_relationship. + // @todo make this more specific (restrict relationship by type?) + // @todo rather then limit, check if there are 1+ and warn the admin. + $sql = "SELECT cvterm_id, name FROM {cvterm} WHERE cvterm_id = + (SELECT subject_id FROM {cvterm_relationship} WHERE object_id = :trait LIMIT 1)"; + + $args = array(':trait' => $trait_id); + $unit = chado_query($sql, $args); + + if ($unit->rowCount() > 0) { + $r = $unit->fetchObject(); + return array('id' => $r->cvterm_id, 'name' => $r->name); + } + + // If that doesn't work then we try to extract it from the name. + // Note: if the following function is unable to extract the unit then it will default to text. + $unit_name = rawpheno_function_unit($trait_name); + + // Column header does not contain unit, use text as default + if (function_exists('chado_get_cvterm')) { + $cvterm = chado_get_cvterm(array('name' => $unit_name, 'cv_id' => array('name' => 'phenotype_measurement_units'))); + } + else { + $cvterm = tripal_get_cvterm(array('name' => $unit_name, 'cv_id' => array('name' => 'phenotype_measurement_units'))); + } + + if ($cvterm) { + return array('id' => $cvterm->cvterm_id, 'name' => $cvterm->name); + } + + return FALSE; +} + + +/** + * Remove the unit part from a trait. + * + * @param $trait_name + * A string containing the trait name as formatted in cvterm name. + * @return + * A string containing the trait name without the unit. + */ +function rawpheno_get_trait_name($trait_name) { + $t = explode('(', $trait_name); + + // Given a trait as defined in cvterm name in the following format: + // Trait name (Trait Rep; Unit), extract the trait name only and return + // the extracted name. + return (count($t) > 1) ? trim(preg_replace('/\(.*/', ' ', $trait_name)) : $trait_name; +} + + +/** + * Get all the essential traits in a project. + * + * @param $project_id + * An integer containing the project ID number. + * @return + * An array containing all essential traits in a project. + */ +function rawpheno_project_essential_traits($project_id) { + if (isset($project_id) AND $project_id > 0) { + // Get array of trait types + $trait_type = rawpheno_function_trait_types(); + + // Array to hold trait names. + $arr_essential_traits = array(); + + // Query essential traits in a project. + $sql = "SELECT TRIM(t1.name) AS cvterm + FROM {cvterm} AS t1 RIGHT JOIN pheno_project_cvterm AS t2 USING (cvterm_id) + WHERE + t2.project_id = :project_id + AND t2.type IN (:essential) + ORDER BY t1.name ASC"; + + $args = array(':project_id' => $project_id, ':essential' => array($trait_type['type1'], $trait_type['type4'])); + $trait = chado_query($sql, $args); + + foreach($trait as $t) { + $m = rawpheno_get_trait_name($t->cvterm); + $arr_essential_traits[] = $m; + } + + // Add Name column header to the traits returned. + $arr_essential_traits[] = 'Name'; + + return $arr_essential_traits; + } +} + + +/** + * Get all the plant property traits in a project selected. + * + * @param $project_id + * An integer containing the project ID number. + * @return + * An array containing all essential traits in a project. + */ +function rawpheno_project_plantproperty_traits($project_id) { + if (isset($project_id) AND $project_id > 0) { + // Get array of trait types + $trait_type = rawpheno_function_trait_types(); + + // Array to hold trait names. + $arr_plantproperty_traits = array(); + + // Query plant property traits in a project. + $sql = "SELECT TRIM(t1.name) AS cvterm + FROM {cvterm} AS t1 RIGHT JOIN pheno_project_cvterm AS t2 USING (cvterm_id) + WHERE t2.project_id = :project_id AND t2.type = :plantproperty + ORDER BY t1.name ASC"; + + // traits of type plantproperty. + $args = array(':project_id' => $project_id, ':plantproperty' => $trait_type['type4']); + $trait = chado_query($sql, $args); + + foreach($trait as $t) { + // Remove the trait rep and unit from the trait. + $m = rawpheno_get_trait_name($t->cvterm); + $arr_plantproperty_traits[] = $m; + } + + return $arr_plantproperty_traits; + } +} + + +/** + * Get all traits available in a project whether essential or not. + * + * @param $project_id + * An integer containing the project ID number. + * @return + * An array containing all traits available in a project. + */ +function rawpheno_project_traits($project_id) { + if (isset($project_id) AND $project_id > 0) { + $arr_trait = array(); + + // Query column headers in a project. + $sql = "SELECT TRIM(t1.name) AS cvterm + FROM {cvterm} AS t1 RIGHT JOIN pheno_project_cvterm AS t2 USING (cvterm_id) + WHERE t2.project_id = :project_id + ORDER BY t1.name ASC"; + + $args = array(':project_id' => $project_id); + $trait = chado_query($sql, $args); + + foreach($trait as $t) { + $arr_trait[] = $t->cvterm; + } + + // Add Name column header to the traits returned. + $arr_trait[] = 'Name'; + + return $arr_trait; + } +} + + +/** + * Function to fetch information about a unit (Describe method) when available. + * + * @param $cvterm_id + * An integer containing the cvterm id of a trait. + */ +function rawpheno_function_cvterm_properties($cvterm_id) { + // Narrow the search to cvterm of type measurement units. + if (function_exists('chado_get_cv')) { + $cv_unit = chado_get_cv(array('name' => 'phenotype_measurement_units')); + } + else { + $cv_unit = tripal_get_cv(array('name' => 'phenotype_measurement_units')); + } + + // In form state 2, describe header, user has the opportunity to describe the unit (Describe method field). + // This information is stored in cvterm relationship together with the cvterm id of the unit as the subject_id + // and cvterm_id of the header as the object_id. Given a header cvterm id, get the subject id and use it to + // get the information required from cvtermprop table. + // @todo check if there is more then one unit for a given trait and if so, warn the admin. + $sql = "SELECT subject_id FROM {cvterm_relationship} WHERE object_id = :cvterm_id AND type_id = :cv_unit LIMIT 1"; + $args = array(':cvterm_id' => $cvterm_id, ':cv_unit' => $cv_unit->cv_id); + + $d = chado_query($sql, $args) + ->fetchField(); + + // @note cvterm_id + type_id + rank is unique (constraint). + $sql = "SELECT value FROM {cvtermprop} WHERE type_id = :cv_unit AND cvterm_id = :cvterm_id AND rank = 0"; + $args = array(':cv_unit' => $cv_unit->cv_id, ':cvterm_id' => $d); + + $d = chado_query($sql, $args); + + if ($d->rowCount() == 1) { + return $d->fetchField(); + } + else { + return 'Describe the method used not available'; + } +} diff --git a/include/rawpheno.upload.form.inc b/includes/rawpheno.upload.form.inc old mode 100755 new mode 100644 similarity index 97% rename from include/rawpheno.upload.form.inc rename to includes/rawpheno.upload.form.inc index d03a742..b33378b --- a/include/rawpheno.upload.form.inc +++ b/includes/rawpheno.upload.form.inc @@ -1,1260 +1,1260 @@ - 'markup', - '#markup' => t('Standard Procedure ❯'), - ); - - // If the stage is not set then default to the first stage (i.e. 'check' ) - if (!isset($form_state['stage'])) { - $form_state['stage'] = 'check'; - } - - // If a job_id was provided in the URL then the user wants information - // on a prvious bulk loading job. Thus we should show them the last step. - if (isset($form_state['build_info']['args'][0])) { - $form_state['stage'] = 'save'; - } - - // Add the stage tracker/header. - $form = rawpheno_get_header($form, $form_state); - // Holds the current stage. - $form_stage = $form_state['stage']; - - // Stage indicator for theme function. - $form['current_stage'] = array( - '#type' => 'value', - '#value' => $form_stage, - ); - - // Add the next button for all but the last step. - if ($form_stage != 'save') { - $form['next_step'] = array( - '#type' => 'submit', - '#value' => 'Next Step', - '#weight' => 100 - ); - } - - // Create a select box containing projects available - these are projects - // that have associated column header set and must have at least 1 essential column header. - // The projects are filtered to show only projects assigned to user. - if ($form_stage != 'save') { - $my_project = rawpheno_function_user_project($GLOBALS['user']->uid); - - if (count($my_project) > 0) { - // When there is more than 1 project assigned to user, tell user to select a project - // otherwise default to the only project available. - if (count($my_project) > 1) { - $my_project = array(0 => 'Please select an experiment') + $my_project; - } - - // Default Project in project selector field. - if (isset($_POST['sel_project']) AND $_POST['sel_project'] > 0) { - // HTTP method post has the project id. - $default_value = $_POST['sel_project']; - } - elseif (isset($form_state['values']['sel_project'])) { - // Form state has the project id. - $default_value = $form_state['values']['sel_project']; - } - else { - // Neither has the project id number default to the first project in the project list. - $default_value = 0; - } - - // Determine if the project select box should be enabled. - // Disabled in all stages but stage 01. - $disabled = ($form_stage == 'check') ? FALSE : TRUE; - - $form['sel_project'] = array( - '#type' => 'select', - '#options' => $my_project, - '#default_value' => $default_value, - '#disabled' => $disabled, - '#id' => 'rawpheno-select-project-field', - ); - - // This block is to ensure select project select box is always default to - // "please select a project" or to the first project option in the beginning of the upload process. - // It will also default to this option when user refreshes the page. - if ($form_stage == 'check') { - drupal_add_js('jQuery(document).ready(function() { - jQuery("#rawpheno-select-project-field").val(0); - })', 'inline'); - } - } - else { - // No project is assigned to user. - $form['no_project'] = array( - '#markup' => '
' . t('No experiment is assigned to this account. Please contact the administrator of this website.') . '
', - ); - } - } - - // Note: - // When there is no project defined in the module, the error message is handled by the theme. - - // Get the directory path of rawpheno module. - $path = drupal_get_path('module', 'rawpheno') . '/theme/'; - - // Load corresponding function callback together with JavaScript and CSS. - switch($form_stage) { - case 'check': - // Stage 01 - upload and check spreadsheet. - $form['#attached']['css'] = array($path . 'css/rawpheno.upload.stage01.css'); - $form['#attached']['js'] = array($path . 'js/rawpheno.upload.stage01.js', - $path . 'js/rawpheno.upload.script.js'); - - $form = rawpheno_upload_form_stage_check($form, $form_state); - break; - - case 'review': - // Stage 02 - describe form (only when there is additional trait). - $form['#attached']['css'] = array($path . 'css/rawpheno.upload.stage02.css'); - $form['#attached']['js'] = array($path . 'js/rawpheno.upload.script.js'); - - $form = rawpheno_upload_form_stage_review($form, $form_state); - break; - - case 'save': - // Stage 03 - save to databse and success page. - $form['#attached']['css'] = array($path . 'css/rawpheno.upload.stage03.css'); - $form['#attached']['js'] = array($path . 'js/rawpheno.upload.stage03.js', - $path . 'js/rawpheno.upload.script.js'); - - $form = rawpheno_upload_form_stage_save($form, $form_state); - break; - } - - return $form; -} - - -/** - * Function callback: Construct form for Stage 01. - * - * Stage 01 form allows user to upload data collection spreadsheet and perform basic compliance test. - */ -function rawpheno_upload_form_stage_check($form, &$form_state) { - // Create an instance of DragNDrop Upload. - // SETTINGS: - // #file_upload_max_size: max file size allowed - // #upload_location: destination of file - // #upload_event: manual - show an upload button or auto - uploads after drag drop - // #upload_validators: allowed file extensions - // #upload_button_text: label of upload button - // #droppable_area_text: text in drop area - // #progress_indicator: none, throbber or bar - // #progress_message: message to display while processing - // #allow_replace: allow user to replace file by drag and drop another file - // #standard_upload: show browse button or not - // #upload_button_text: submit button text (not required when auto submit is auto) - - $form['dnd'] = array( - '#type' => 'dragndrop_upload', - '#file_upload_max_size' => '10M', - '#upload_location' => 'public://', - '#upload_event' => 'auto', - // NOTE: Accept the listed file extension and let the spreadsheet reader tell if file is valid to generate an an error message. - // No silent treatment. - '#upload_validators' => array( - 'file_validate_extensions' => array('xlsx xls jpg jpeg gif png txt doc pdf ppt pps odt ods odp csv'), - ), - '#droppable_area_text' => t('Drag your Microsoft Excel Spreadsheet file here'), - '#progress_indicator' => 'throbber', - '#progress_message' => 'Validating your spreadsheet file. Please wait...', - '#allow_replace' => 1, - '#standard_upload' => 1, - '#upload_button_text' => '', - - // We are adding our own element process function so that we can make a successfully - // uploaded/validated file permanent during the AJAX process rather than waiting for - // them to click the "Next" button. - '#process' => array( - 'file_managed_file_process', - 'dragndrop_upload_element_element_process', - 'rawpheno_phenotype_upload_file_element_process', - ), - ); - - return $form; -} - - -/** - * Function callback: Construct form for Stage 02. - * - * Stage 02 form allows user to describe and save a additional trait/s found in the spreadsheet submitted in Stage 01. - * - * Assuming the file uploaded properly, we have access to an excel file and need to - * ensure that all traits have been described. If there are any that haven't then - * we need to ask the the user to define them now. - * - * NOTE: User has the option to skip this stage by not checking any of the new traits found and clicking next step. - */ -function rawpheno_upload_form_stage_review(&$form, &$form_state) { - // Array to hold new headers. - $new_header = array(); - - // The project id number the spreadsheet and column headers are specific to. - $project_id = $form_state['values']['sel_project']; - $project_name = rawpheno_function_getproject($project_id); - - // FIND NEW HEADERS. - // First step, determine which headers/traits need to be described. - if (isset($form_state['multistep_values']['fid'])) { - // Get Drupal file object. - $file = file_load($form_state['multistep_values']['fid']); - - // Ensure that the file exits and project id is selected. - // The form will unset the project id upon page refresh, this will catch the condition - // when no project id is selected, user will be stopped and is requested to retry the process. - if ($file AND !empty($project_id)) { - $new_header = rawpheno_indicate_new_headers($file, $project_id); - - // Calling all modules implementing hook_rawpheno_AGILE_stock_name_alter(): - drupal_alter('rawpheno_ignorecols_newcolumn', $new_header, $project_name); - - $form_state['multistep_values']['new_headers'] = $new_header; - } - else { - drupal_set_message(t('Unable to access your file. Please try uploading again.'), 'error'); - } - } - else { - drupal_set_message(t('We have no record of your uploaded file. Please try uploading it again.'), 'error'); - } - - // If we were unable to access the file or project is not selected then don't let them proceed. - if (!isset($file) OR empty($file) OR empty($project_id)) { - $form['notice'] = array( - '#type' => 'markup', - '#markup' => '
' - . t('Unable to access uploaded file. Please attempt to upload your file again on the previous page. If the problem persists then contact the administrator.', - array('@upload-page' => url('phenotypes/raw/upload'))) - . '
', - ); - - // No submit button as well. - unset($form['next_step']); - - return $form; - } - - - // NO NEW HEADER. - // If there are no new headers then they don't have to do anything. The module will display a summary - // showing the number of parsed column headers in the spreadsheet. - if (empty($new_header)) { - $all_headers = rawpheno_all_headers($file); - - $markup = ' - - '; - - $form['xls_summary_fldset'] = array( - '#type' => 'fieldset', - '#title' => t('Describe new trait'), - ); - - $form['xls_summary_fldset']['information'] = array( - '#type' => 'markup', - '#markup' => $markup, - ); - - $form['notice'] = array( - '#type' => 'markup', - '#markup' => '
No new traits were detected in the spreadsheet. Please click "Next Step".
', - ); - - return $form; - } - - - // NEW HEADERS FOUND. - if (function_exists('chado_get_cv')) { - $cv = chado_get_cv(array('name' => 'phenotype_measurement_types')); - } - else { - $cv = tripal_get_cv(array('name' => 'phenotype_measurement_types')); - } - - $cv_id = $cv->cv_id; - - // Where clause that form part of the SQL below. - $where = array( - 'yes' => "TRIM(LOWER(name)) = :cvterm LIMIT 1", - 'no' => "TRIM(LOWER(SPLIT_PART(name, '(', 1))) LIKE :cvterm" - ); - - $sel = "SELECT * FROM {cvterm} WHERE - cvterm_id NOT IN (SELECT cvterm_id FROM pheno_project_cvterm WHERE project_id = :project_id) - AND cv_id = :cv_id AND %s"; - - // Otherwise, we need a form! - // Main fieldset container for form elements. - $form['xls_review_fldset'] = array( - '#type' => 'fieldset', - '#title' => t('Check the traits that you want to describe and save'), - ); - - $headers_no_format = array_map('rawpheno_function_delformat', $new_header); - - // Array to hold all checked headers. - $arr_checked_header = array(); - - foreach($new_header as $i => $k) { - if (isset($k) AND !empty($k)) { - // To prevent spills of information to other form set, reset this variable that holds - // query result object. - if (isset($cvterm_info)) { - unset($cvterm_info); - } - - // CHECKBOX to let user select a trait to describe and save. If left unchecked, system will not save it. - $form['xls_review_fldset']['chk_' . $i] = array( - '#type' => 'checkbox', - '#title' => t(ucwords($k)), - '#ajax' => array( - 'callback' => 'ajax_rawpheno_upload_form_step2_expand_trait_callback', - 'wrapper' => 'trait-description-' . $i, - 'effect' => 'fade', - 'trait_index' => $i, - ), - ); - - // Container div that holds form elements. - $form['xls_review_fldset']['fldset_' . $i] = array( - '#type' => 'markup', - '#prefix' => '
', - '#suffix' => '
', - ); - - // By default, show the describe form. - $show_form = 'yes'; - - // If the checkbox is checked then show the fields user want to described. - if (isset($form_state['values']['chk_' . $i]) AND ($form_state['values']['chk_' . $i] == TRUE)) { - // TERM NAME/TRAIT/HEADER - $form['xls_review_fldset']['fldset_' . $i]['txt_header_' . $i] = array( - '#type' => 'hidden', - '#value' => $k, - ); - - // Clean up the current header. - $name = trim(strtolower(preg_replace('!\s+!', ' ', $k))); - $arr_checked_header[] = $name; - - // Test if the header has a unit component and set the variable accordingly. - $has_unit = (strpbrk($name, '()')) ? 'yes' : 'no'; - - // Format the name to be used in the following SQL. When the name has a unit component, - // we just feed the name to the SQL using equal operator, otherwise, we use like operator - // to find all similar headers and suggest it. - $cvterm = ($has_unit == 'yes') ? $name : '%' . $name . '%'; - - // Construct the query statement. - $sql = sprintf($sel, $where[$has_unit]); - $args = array(':project_id' => $project_id, ':cv_id' => $cv_id, ':cvterm' => $cvterm); - $h = chado_query($sql, $args); - - if ($h->rowCount() > 0) { - if ($has_unit == 'yes') { - // Load information about the header. - $cvterm_info = $h->fetchObject(); - - // Tell user that the column header exists already. - $form['xls_review_fldset']['fldset_' . $i]['notice_' . $i] = array( - '#markup' => '
The system has detected this column header in the database. - All form fields are disabled to prevent alteration to the original version. - To save this header and data associated to it, please keep the checkbox checked.
', - ); - - $show_form = 'yes'; - } - else { - // Suggest similar header. - // Ensure that the list of headers to be suggested is not in the list of headers detected, - // that way we can avoid duplicate headers. - $header_options = array(); - foreach($h as $m) { - $this_header = trim(strtolower($m->name)); - - if (!in_array($this_header, $headers_no_format)) { - $header_options[$m->cvterm_id] = $m->name; - } - } - - if (count($header_options) > 0) { - $form['xls_review_fldset']['fldset_' . $i]['sel_header_' . $i] = array( - '#type' => 'select', - '#title' => t('Did you mean?'), - '#options' => array('-1' => '---', 0 => 'None of these apply') + $header_options, - '#ajax' => array( - 'callback' => 'ajax_rawpheno_upload_form_step2_load_header_info', - 'wrapper' => 'trait-description-' . $i, - 'effect' => 'fade', - 'trait_index' => $i, - ), - '#element_validate' => array('rawpheno_newheader_didyoumean_validate'), - '#attributes' => array('class' => array('sel-header')), - '#description' => t('The system has detected a similar header in the database. - It is recommended that you select the header from the select box that best describes your data. - If the header is not listed, please select None of these apply option and use the form below to describe this column header.'), - ); - - $show_form = 'no'; - } - else { - $show_form = 'yes'; - } - - if (isset($form_state['values']['sel_header_' . $i])) { - if ($form_state['values']['sel_header_' . $i] > 0) { - $cvterm_id = $form_state['values']['sel_header_' . $i]; - - if (function_exists('chado_get_cvterm')) { - $cvterm_info = chado_get_cvterm(array('cvterm_id' => $cvterm_id)); - } - else { - $cvterm_info = tripal_get_cvterm(array('cvterm_id' => $cvterm_id)); - } - - $show_form = 'yes'; - } - elseif ($form_state['values']['sel_header_' . $i] == 0) { - $show_form = 'yes'; - } - } - } - } - - - if ($show_form == 'yes') { - // TERM DEFINITION - $form['xls_review_fldset']['fldset_' . $i]['txt_def_' . $i] = array( - '#type' => 'textarea', - '#title' => t('Definition'), - '#required' => TRUE, - '#description' => t('A human-readable text definition'), - ); - - if (isset($cvterm_info) && isset($cvterm_info->definition)) { - $form['xls_review_fldset']['fldset_' . $i]['txt_def_' . $i]['#value'] = $cvterm_info->definition; - $form['xls_review_fldset']['fldset_' . $i]['txt_def_' . $i]['#disabled'] = TRUE; - } - - - // UNIT - $form['xls_review_fldset']['fldset_' . $i]['txt_unit_' . $i] = array( - '#type' => 'textfield', - '#title' => t('Unit'), - '#required' => TRUE, - '#maxlength' => 100, - '#element_validate' => array('rawpheno_newheader_unit_validate'), - '#description' => t('Unit of measurement used'), - ); - - if (isset($cvterm_info)) { - $unit_val = strpbrk($cvterm_info->name, '()'); - // Remove any parenthesis making its way to the final value. - $unit_val = str_replace(array('(', ')'), '', $unit_val); - - $form['xls_review_fldset']['fldset_' . $i]['txt_unit_' . $i]['#value'] = trim($unit_val); - $form['xls_review_fldset']['fldset_' . $i]['txt_unit_' . $i]['#disabled'] = TRUE; - } - else { - if ($has_unit == 'yes') { - $u = strpbrk($name, '()'); - // Remove any parenthesis making its way to the final value. - $u = str_replace(array('(', ')'), '', $u); - - $form['xls_review_fldset']['fldset_' . $i]['txt_unit_' . $i]['#value'] = trim($u); - $form['xls_review_fldset']['fldset_' . $i]['txt_unit_' . $i]['#disabled'] = FALSE; - } - } - - - // DESCRIPTION - describe the trait. - $form['xls_review_fldset']['fldset_' . $i]['txtarea_describe_' . $i] = array( - '#type' => 'textarea', - '#title' => t('Describe the method used'), - '#required' => TRUE, - '#description' => t('Describe the method used to collect this data if you used a scale, be specific'), - ); - - if (isset($cvterm_info)) { - $cvterm_describe_unit = rawpheno_function_cvterm_properties($cvterm_info->cvterm_id); - - $form['xls_review_fldset']['fldset_' . $i]['txtarea_describe_' . $i]['#value'] = $cvterm_describe_unit; - $form['xls_review_fldset']['fldset_' . $i]['txtarea_describe_' . $i]['#disabled'] = TRUE; - } - - - // Note fields are required - $form['xls_review_fldset']['fldset_' . $i]['required_' . $i] = array( - '#markup' => '
* means field is required
' - ); - } - } - } - } - - // Hidden field containing all the checked new headers - if (isset($arr_checked_header) AND count($arr_checked_header) > 0) { - $form['all_header_checked'] = array( - '#type' => 'hidden', - '#value' => implode(',', $arr_checked_header), - ); - } - - // Indicator to user of how many of the new traits found has been described. - $form['traits_checked'] = array( - '#type' => 'markup', - '#markup' => '
You have described 0 trait. Please click "Next Step".
' - ); - - return $form; -} - - -/** - * Function validate the unit field when new header is detected in the spreadsheet. - * Validation includes ensuring that user does not use parenthesis ( and ) in the unit. - */ -function rawpheno_newheader_unit_validate($element, &$form_state) { - $unit_value = trim($element['#value']); - $project_id = $form_state['values']['sel_project']; - - // strpbrk() Returns a string starting from the character found, or FALSE if it is not found. - if (strpbrk($unit_value, '()')) { - form_set_error($element['#name'], 'The value in the unit field contains characters "(" and/or ")". Please remove these characters and try again.'); - } - else { - // Test the name plus the unit combination if it is in the project. - $header_field = str_replace('unit', 'header', $element['#name']); - $header_value = $form_state['values'][$header_field]; - - if (!strpbrk($header_value, '()')) { - $name_value = trim(strtolower($header_value . ' (' . strtolower($unit_value) . ')')); - - $sql = "SELECT cvterm_id - FROM {cvterm} INNER JOIN pheno_project_cvterm USING(cvterm_id) - WHERE project_id = :project_id AND TRIM(LOWER(name)) = :cvterm LIMIT 1"; - - $args = array(':project_id' => $project_id, ':cvterm' => $name_value); - $h = chado_query($sql, $args); - - if ($h->rowCount() == 1) { - form_set_error($element['#name'], 'Cannot save column header and unit. ' . ucfirst($name_value) . ' exists in this project.'); - } - - // Test if user is about to save same headers. - if (isset($form_state['values']['all_header_checked'])) { - $h = $form_state['values']['all_header_checked']; - $header_validation = explode(',', $h); - - if (in_array($name_value, $header_validation)) { - form_set_error($element['#name'], 'Cannot save multiple entries of the same column header and unit combination.'); - } - } - } - } -} - - -/** - * Function callback: validate Did you mean? select box - */ -function rawpheno_newheader_didyoumean_validate($element, &$form_state) { - if ($element['#value'] < 0) { - form_set_error($element['#name'], 'Please select an option and try again.'); - } -} - - -/** - * Function load column header information. - */ -function ajax_rawpheno_upload_form_step2_load_header_info($form, $form_state) { - $i = $form_state['triggering_element']['#ajax']['trait_index']; - - return $form['xls_review_fldset']['fldset_' . $i]; -} - - -/* - * Selects the piece of the form we want to use as replacement text and returns it as a form (renderable array). - * - * @return renderable array (the trait description elements) - */ -function ajax_rawpheno_upload_form_step2_expand_trait_callback($form, $form_state) { - // Unique id of each form set. - $i = $form_state['triggering_element']['#ajax']['trait_index']; - - return $form['xls_review_fldset']['fldset_' . $i]; -} - - -/** - * Function callback: Construct form for Stage 03. - * - * Stage 03 form is the final stage that displays a status message - * and a navigation button to direct user after a successful file upload. - */ -function rawpheno_upload_form_stage_save($form, &$form_state) { - $job_id = NULL; - global $user; - - if (isset($form_state['build_info']['args'][0])) { - $job_id = $form_state['build_info']['args'][0]; - - // We only want to run jobs that has phenotypic data in it. Otherwise we tell user - // job is not valid (in case user will hack the url containing the job id). - // Retrieve the tripal job and determine the percent complete. - $job = tripal_get_job($job_id); - - // If job is valid. - if ($job) { - if ($job->uid == $user->uid) { - // Job id belongs to the user. Authorized. - $job_status = trim(strtolower($job->status)); - - // Check if it is a valid job and not a BLAST or other job type. - if ($job->callback == 'rawpheno_load_spreadsheet') { - if ($job_status == 'completed') { - // Is completed some time ago. - $form['notice'] = array( - '#markup' => '
It appears that you are attempting to submit a spreadsheet that has been processed already
' - ); - } - elseif ($job_status == 'error') { - // Has error. - $form['notice'] = array( - '#markup' => '
It appears that you are attempting to submit a spreadsheet that has errors.
' - ); - } - elseif ($job_status == 'cancelled') { - // Is cancelled. - $form['notice'] = array( - '#markup' => '
It appears that you are attempting to submit a spreadsheet that has been cancelled.
' - ); - } - else { - // Not processed yet - show the progress bar. - // A valid job - work on it. - $form['notice'] = array( - '#type' => 'markup', - '#markup' => - '
Your spreadsheet has been successfully submitted and will not be interupted if you choose to leave this page.
' - . '
The progress bar below indicates our progress updating ' . strtoupper($_SERVER['SERVER_NAME']) . '. Your data will not be available until the progress bar below completes.
' - ); - - // Add Progress JS Library. - drupal_add_js('misc/progress.js'); - - // This is the link passed to the JavaScript Progress.js as the parameter to a function - // that monitors a link. The link is a function callback that generates a JSON object - // containing the number of rows save in percent. See file: rawpheno.module. - $form['tripal_job_id'] = array( - '#type' => 'hidden', - '#value' => $GLOBALS['base_url'] . '/phenotypes/raw/upload/job_summary/' . $job->job_id, - '#attributes' => array('id' => 'tripal-job-id'), - ); - - // We make a DIV which the progress bar can occupy. You can see this in use - // in ajax_example_progressbar_callback(). - $form['status'] = array( - '#type' => 'markup', - '#markup' => '
' - ); - } - } - else { - // Job not supported by this module. - $form['notice'] = array( - '#markup' => '
It appears that you are attempting to request a process that is not supported by this module.
' - ); - } - } - else { - // Not authorized. - $form['notice'] = array( - '#markup' => '
It appears that you are attempting to submit a spreadsheet that is not in your account.
' - ); - } - } - else { - // Job is not valid or does not exists. - $form['notice'] = array( - '#markup' => '
The job request to save spreadsheet file does not exist.
' - ); - } - } - - return $form; -} - - -/** - * Implements hook_file_insert(). - * Save file information when file is saved (backup). - * - * @param $file - * Drupal file opbject. - */ -function rawpheno_file_insert($file) { - // Process file only when there is a request to save a file and that request is coming from backup page. - if (isset($file->source)) { - if ($file->source == 'bdnd') { - // User id of the currently logged in user. - $user_id = $GLOBALS['user']->uid; - // The project id field. - $project_id = $_POST['backup_sel_project']; - // The notes field. - $notes = trim(strip_tags($_POST['backup_txt_description'])); - - // Query the record id of the project to user record. - // The result id will be used to map a backup file to user and to project. - $sql = "SELECT project_user_id FROM pheno_project_user WHERE project_id = :project_id AND uid = :user_id LIMIT 1"; - $args = array(':project_id' => $project_id, ':user_id' => $user_id); - $prj_usr_id = db_query($sql, $args) - ->fetchField(); - - // Get the validation result performed to the spreadsheet file and store the result along with the file information. - // The same validation process performed in upload data page is carried out to backup file. However, the result - // is stored as plain text and passed and failed icons are replaced by words passed and failed, respectively. - $status = rawpheno_validate_excel_file($file, $project_id, 'backup'); - - $s = (isset($status['status'])) ? $status['status'] : $status; - - // Express the validation result array into human readable non-html content format. - $validation_result = ''; - // Call the same validator function used in upload data. - $validators = module_invoke_all('rawpheno_validators'); - - // For each status result, convert it to text based and add a unique text indicator to be used - // as key to explode the entire text and create a list using the
tag. - foreach($s as $key => $result) { - $flag = ($result === TRUE) ? 'passed' : 'failed'; - - // The item keyword will be used to create a list of validation entries when displaying validaiton result to user. - $validation_result .= '#item: (' . $flag . ') ' . $validators[$key]['label'] . "\n"; - if ($result !== TRUE) { - $message = call_user_func($validators[$key]['message callback'], $result); - if (!empty($message)) { - $validation_result .= implode("\n", $message); - } - } - } - - // Compute the version number of this file. - $sql = "SELECT MAX(t2.version) + 1 AS version - FROM {pheno_project_user} AS t1 RIGHT JOIN {pheno_backup_file} AS t2 USING(project_user_id) - WHERE t1.project_id = :project_id AND t1.uid = :user_id LIMIT 1"; - - $args = array(':project_id' => $project_id, ':user_id' => $user_id); - $version = db_query($sql, $args) - ->fetchField(); - - // On initial upload the file version is null, in this case - // version is set to 1. Version is incremented by 1 (+1) in - // subsequent uploads. - $version = ($version === null) ? 1 : $version; - - // Insert a record of this file. - // File version, which is a sequential order (integer) is handled by the rdbms as it is set to serial type. - if (isset($status['check_limit'])) { - $validation_result .= "#item: (failed) NOTICE : " . $status['check_limit'] . "\n"; - } - - db_insert('pheno_backup_file') - ->fields(array('fid' => $file->fid, - 'notes' => $notes, - 'version' => $version, - 'project_user_id' => $prj_usr_id, - 'validation_result' => $validation_result)) - ->execute(); - - // Finally, make the file permanent. - rawpheno_upload_make_file_permanent($file->fid); - - // Make the validation result available to frontend. - $_SESSION['rawpheno']['backup_file_validation_result'] = $status; - } - } -} - - -/** - * Upload validators callback: - * Basic compliance test to spreadsheet submitted. - * - * @param $file - * Drupal file opbject. - */ -function rawpheno_file_validate($file) { - // 10 mins (60 * 10). - $max_time = 600; - - if ($file->source == 'dnd') { - // Set processing time to 10 mins. - ini_set('max_execution_time', $max_time); - - // Upload data page. - - // Project id number the spreadsheet and column headers are specific to. - $project_id = (int)$_POST['sel_project']; - - // Validate the file. - // The following function will return an array specifying which of the validation - // steps passed and providing infomration for those that failed. - $status = rawpheno_validate_excel_file($file, $project_id, 'upload'); - - // We want to show the user which steps passed/failed even if all of them passed, - // so lets do that now. We use drupal_set_message() because returning from this function - // creates an error message and halts file upload, whereas, using drupal_set_message() - // allows us to print to the screen regardless of failure/success. - drupal_set_message(theme('rawpheno_upload_validation_report', array('status' => $status)), 'rawpheno-validate-progress'); - - // Now we want to determine if validation passed or failed as a whole. - // To do that we have to look at each step and only if all steps passed - // did the file pass validation and can be uploaded. - $all_passed = TRUE; - foreach ($status as $test_result) { - if ($test_result !== TRUE) { - $all_passed = FALSE; - break; break; - } - } - - // hook_file_validate() expects an array of error messages if validation failed and - // and empty array if there are no errors. We don't want this system to print the errors - // for us since we are using our more friendly theme (see drupal_set_message() above). - // The work-around is to pass FALSE if validation failed. - if ($all_passed) { - drupal_set_message('Your file uploaded successfully. Please click "Next" to continue.'); - return array(); - } - else { - return FALSE; - } - } - elseif ($file->source == 'bdnd') { - // Set processing time to 10 mins. - ini_set('max_execution_time', $max_time); - - // Backup file page. - - // Array to hold the validation result. - $status = array(); - - // Project id number the spreadsheet and column headers are specific to. - $project_id = (int)$_POST['backup_sel_project']; - - // Perform basic compliance test: - // - A project is selected. - // - File is Microsoft Excel Spreadhseet file. - // - Measurement tab exists. - // - Essential column headers defined in the project are present. - $flag_index = array('project_selected', 'is_excel', 'tab_exists', 'column_exists'); - - // Validate file. - $flags = rawpheno_validate_excel_file($file, $project_id, 'backup'); - - $s = (isset($flags['status'])) ? $flags['status'] : $flags; - - // If DND backup, missing measurements, skip all validator but - // save/backup the file anyway. - if ($s['tab_exists'] === FALSE) { - return array(); - } - - // Read only the status from test listed above. - foreach($s as $i => $v) { - if (in_array($i, $flag_index) AND ($v === FALSE || $v === 'todo')) { - $status[$i] = $v; - } - } - - // When any of the mentioned test failed, show them to user. - if (count($status) > 0) { - if (isset($flags['check_limit'])) { - drupal_set_message($flags['check_limit'], 'error'); - } - - drupal_set_message(theme('rawpheno_upload_validation_report', array('status' => $status)), 'rawpheno-validate-progress'); - return FALSE; - } - - // Else, proceed to hook_file_insert(). - } - else { - // File source is one that is not of interest to us. - // Do not return anything or it will trigger validation errors for other modules. - } -} - - -/** - * Make the phenotype excel file permanent on successful upload. - * - * This is an additional process handler used by the form API to generate the form array - * for a given element. Usually it is used to make a custom form element or enhance a - * standard form element. - * - * We are using it to capture the just uploaded file within the AJAX call by checking - * when the element is rendered if it has a fid (ie: has been saved). - */ -function rawpheno_phenotype_upload_file_element_process($element, &$form_state, $form) { - if (isset($element['#value']['fid']) AND !empty($element['#value']['fid'])) { - $file_id = $element['#value']['fid']; - rawpheno_upload_make_file_permanent($file_id); - } - - return $element; -} - - -/** - * Make the file uploaded permanent and make a record indicating that file is used by the module. - * - * @param $file_id - * File id in Drupal file object. - */ -function rawpheno_upload_make_file_permanent($file_id) { - // Get the file object. - $file = file_load($file_id); - - if ($file) { - // Make the file permanent. - $file->status = FILE_STATUS_PERMANENT; - file_save($file); - - // Also, point out that we are using it ;-) - // Note, the file_usage_add() function expects a numerical unique id which we don't have. - // We have gotten around this by using the uid concatenated with the timestamp using - // the assumption that a single user cannot upload more than one phenotype file within a second. - file_usage_add($file, 'rawpheno', 'rawphenotypes-file', $file->uid . $file->timestamp); - } -} - - -/** - * Implements hook_validate(). - */ -function rawpheno_upload_form_master_validate($form, &$form_state) { } - - -/** - * Implements hook_submit(). - * - * Master submit to handle form submit. - */ -function rawpheno_upload_form_master_submit(&$form, &$form_state) { - // Which button triggers a submit action. - $btn_submit = $form_state['triggering_element']['#value']; - - // Save any additional traits and then submit a job to save the spreadsheet. - if ($form_state['stage'] == 'review') { - $job_id = rawpheno_submit_review($form, $form_state); - - // Then we need to add the job_id to the path so the system can keep track of it. - if ($job_id) { - drupal_goto(current_path() . '/' . $job_id); - } - } - - // If we just uploaded the file then we want to save the fid for easy access. - if (isset($form_state['values']['dnd'])) { - $form_state['multistep_values']['fid'] = $form_state['values']['dnd']; - } - - // If the next step button was pressed then iterate to the next step. - if ($btn_submit == 'Next Step') { - // Definitely save the form id. - if(isset($form_state['multistep_values']['form_build_id'])) { - $form_state['values']['form_build_id'] = $form_state['multistep_values']['form_build_id']; - } - - // Save the values from the current step. - $form_state['multistep_values'][$form_state['stage']] = $form_state['values']; - - // Iterate to the next step. - $form_state['new_stage'] = rawpheno_next_page($form, $form_state); - - // Ensure the form state is saved and the form is rebuilt. - $form_state['multistep_values']['form_build_id'] = $form_state['values']['form_build_id']; - $form_state['stage'] = $form_state['new_stage']; - $form_state['rebuild'] = TRUE; - } -} - - -/** - * Save spreadsheet to database. - */ -function rawpheno_submit_review($form, &$form_state) { - // Project id number the spreadsheet and column headers are specific to. - $project_id = $form_state['values']['sel_project']; - - // Save spreadsheet data in the following order. - // 1. New column headers. - // 2. The entire spreadsheet. - - // cvterm id of controlled vocabulary. - if (function_exists('chado_get_cv')) { - $cvid = chado_get_cv(array('name' => 'phenotype_measurement_units')); - } - else { - $cvid = tripal_get_cv(array('name' => 'phenotype_measurement_units')); - } - - $cv_measurements_unit = $cvid->cv_id; - - // 1. Save new headers. - // Read variable that holds new column headers. - $new_header = $form_state['multistep_values']['new_headers']; - - // Create an array of new hearders with flag/status if user wants to save it. - // This array will be passed to rawpheno_load_spreadsheet. - $arr_newheaders = array(); - - // Determine if there is new header. - if (count($new_header) > 0) { - $trait_type = rawpheno_function_trait_types(); - - // Read each column header. - foreach($new_header as $i => $header) { - // For each new header store information provided in the interface. - // Indicates if user has check this header for saving. - $header = trim(str_replace(array("\n", "\r", " "), ' ', $header)); - $header = preg_replace('/\s+/', ' ', $header); - - $arr_newheaders[$header]['flag'] = ($form_state['values']['chk_' . $i] == 1) ? 1 : 0; - - // Determine if the form in review traits has been filled out and checkbox - // has been checked by user. If it has been checked then save the trait. - if ($form_state['values']['chk_' . $i] === 1 && !empty($form_state['values']['txt_header_' . $i])) { - // Before save, we need to tell if the header is present in the database and - // user just wants to reuse them. Otherwise, add a new header. - // Reuse header - set to OPTIONAL. - if ((isset($form_state['values']['sel_header_' . $i]) AND $form_state['values']['sel_header_' . $i] > 0) OR - (isset($form_state['values']['txt_header_cvterm_id_' . $i]))) { - - // User selected from a list of similar headers. - $cvterm_id = (isset($form_state['values']['sel_header_' . $i])) - ? $form_state['values']['sel_header_' . $i] - : $form_state['values']['txt_header_cvterm_id_' . $i]; - - // Map this header to the project. - $sql = "SELECT cvterm_id FROM {pheno_project_cvterm} WHERE project_id = :project_id AND cvterm_id = :cvterm_id LIMIT 1"; - $args = array(':project_id' => $project_id, ':cvterm_id' => $cvterm_id); - - $h = db_query($sql, $args); - if ($h->rowCount() <= 0) { - // Add to project only when it is not in the project. - // Set the trait type to contributed. - db_insert('pheno_project_cvterm') - ->fields(array( - 'project_id' => $project_id, - 'cvterm_id' => $cvterm_id, - 'type' => $trait_type['type2']) - ) - ->execute(); - } - - // When saving this data for this header, use the cvterm_id. - $arr_newheaders[$header]['alt_header'] = $cvterm_id; - continue; - } - - // Check if the trait exists in the database, then it is likely - // that the user is reusing the trait - threfore it is not contributed and just map - // the cvterm id to a project. - - // Add the as contributed - set to CONTRIBUTED. - // Construct the column header name. - $name = trim($form_state['values']['txt_header_' . $i]); - $name = preg_replace('/\s+/', ' ', $name); - - $unit = trim($form_state['values']['txt_unit_' . $i]); - $method = trim($form_state['values']['txtarea_describe_' . $i]); - $def = trim($form_state['values']['txt_def_' . $i]); - - // Format the header. - if (strpbrk($name, '()')) { - // Header has a unit part. - $name = trim(str_replace(array("\n", "\r", " "), ' ', $name)); - } - else { - // Construct header plus the unit. - $name = $name . ' (' . strtolower($unit) . ')'; - } - - // Trait properties - use when inserting the cterm and reference to other property. - $m_cvterm = array( - 'id' => 'rawpheno_tripal:' . $name, - 'name' => $name, - 'definition' => $def, - 'cv_name' => 'phenotype_measurement_types' - ); - - // Search the name in cvterm and decide if trait should be considered optional or contributed. - $sql = "SELECT t2.cvterm_id - FROM {cv} AS t1 INNER JOIN {cvterm} AS t2 USING (cv_id) - WHERE - trim(lower(t2.name)) = trim(lower(:cvterm_name)) - AND t1.name = :cv_name LIMIT 1"; - - $args = array(':cvterm_name' => $m_cvterm['name'], ':cv_name' => $m_cvterm['cv_name']); - $result = chado_query($sql, $args) - ->fetchObject(); - - if ($result) { - // Found use the id. - $m_cvterm_id = $result->cvterm_id; - // Trait is optional. - $type = $trait_type['type2']; - } - else { - // Not found, insert and get the inserted id. - $m = tripal_insert_cvterm($m_cvterm); - $m_cvterm_id = $m->cvterm_id; - // Trait is contributed. - $type = $trait_type['type5']; - } - - // When saving this data for this header, use the cvterm_id. - $arr_newheaders[$header]['alt_header'] = $m_cvterm_id; - db_insert('pheno_project_cvterm') - ->fields(array('project_id' => $project_id, - 'cvterm_id' => $m_cvterm_id, - 'type' => $type)) - ->execute(); - - // Create a R Friendly version. - $r_version = rawpheno_function_make_r_compatible($m_cvterm['name']); - - if (function_exists('chado_get_cv')) { - $cv_rfriendly = chado_get_cv(array('name' => 'phenotype_r_compatible_version')); - } - else { - $cv_rfriendly = tripal_get_cv(array('name' => 'phenotype_r_compatible_version')); - } - - $values = array( - 'cvterm_id' => $m_cvterm_id, - 'type_id' => $cv_rfriendly->cv_id, - 'value' => $r_version, - 'rank' => 0 - ); - - chado_insert_record('cvtermprop', $values); - - // Then save the Unit. - $u_cvterm = array( - 'id' => 'rawpheno_tripal:' . strtolower($unit), - 'name' => strtolower($unit), - 'definition' => $unit, - 'cv_name' => 'phenotype_measurement_units' - ); - - $u_cvterm_id = chado_select_record('cvterm',array('cvterm_id'), - array('name' => $u_cvterm['name'], - 'cv_id' => array('name' => $u_cvterm['cv_name']))); - if (!$u_cvterm_id) { - $u_cvterm_id = tripal_insert_cvterm($u_cvterm); - } - - // Grab just the id. - if (is_array($u_cvterm_id)) { - $u_cvterm_id = $u_cvterm_id[0]->cvterm_id; - } - elseif (is_object($u_cvterm_id)) { - $u_cvterm_id = $u_cvterm_id->cvterm_id; - } - - // Don't forget the method description. - $prop = array( - 'cvterm_id' => $m_cvterm_id, - 'type_id' => $cv_measurements_unit, - 'value' => $method, - 'rank' => 0, - ); - - $prop_id = chado_select_record('cvtermprop', array('cvtermprop_id'), $prop); - if (!$prop_id) { - $prop = chado_insert_record('cvtermprop', $prop); - } - - // Finally relate the measurement and unit. - $rel = array( - 'subject_id' => $u_cvterm_id, - 'type_id' => $cv_measurements_unit, - 'object_id' => $m_cvterm_id, - ); - $rel_id = chado_select_record('cvterm_relationship', array('cvterm_relationship_id'), $rel); - if (!$rel_id) { - chado_insert_record('cvterm_relationship', $rel); - } - } - } - } - - // 2. The entire spreadsheet. - // Get the variable that holds the path to the spreadsheet file in the server. - $file = file_load($form_state['multistep_values']['fid']); - $xls_file = drupal_realpath($file->uri); - - // Array of required traits excluding Name. - $plantprop_headers = rawpheno_project_plantproperty_traits($project_id); - - // Drupal user object. - global $user; - - if (isset($xls_file) && !empty($xls_file)) { - $job_id = tripal_add_job( - "Upload Phenoypic data: " . $xls_file, - 'rawpheno', - 'rawpheno_load_spreadsheet', - array( - $project_id, - serialize($arr_newheaders), - $form_state['multistep_values']['fid'], - serialize($plantprop_headers) - ), - $user->uid - ); - - return $job_id; - } -} + 'markup', + '#markup' => t('Standard Procedure ❯'), + ); + + // If the stage is not set then default to the first stage (i.e. 'check' ) + if (!isset($form_state['stage'])) { + $form_state['stage'] = 'check'; + } + + // If a job_id was provided in the URL then the user wants information + // on a prvious bulk loading job. Thus we should show them the last step. + if (isset($form_state['build_info']['args'][0])) { + $form_state['stage'] = 'save'; + } + + // Add the stage tracker/header. + $form = rawpheno_get_header($form, $form_state); + // Holds the current stage. + $form_stage = $form_state['stage']; + + // Stage indicator for theme function. + $form['current_stage'] = array( + '#type' => 'value', + '#value' => $form_stage, + ); + + // Add the next button for all but the last step. + if ($form_stage != 'save') { + $form['next_step'] = array( + '#type' => 'submit', + '#value' => 'Next Step', + '#weight' => 100 + ); + } + + // Create a select box containing projects available - these are projects + // that have associated column header set and must have at least 1 essential column header. + // The projects are filtered to show only projects assigned to user. + if ($form_stage != 'save') { + $my_project = rawpheno_function_user_project($GLOBALS['user']->uid); + + if (count($my_project) > 0) { + // When there is more than 1 project assigned to user, tell user to select a project + // otherwise default to the only project available. + if (count($my_project) > 1) { + $my_project = array(0 => 'Please select an experiment') + $my_project; + } + + // Default Project in project selector field. + if (isset($_POST['sel_project']) AND $_POST['sel_project'] > 0) { + // HTTP method post has the project id. + $default_value = $_POST['sel_project']; + } + elseif (isset($form_state['values']['sel_project'])) { + // Form state has the project id. + $default_value = $form_state['values']['sel_project']; + } + else { + // Neither has the project id number default to the first project in the project list. + $default_value = 0; + } + + // Determine if the project select box should be enabled. + // Disabled in all stages but stage 01. + $disabled = ($form_stage == 'check') ? FALSE : TRUE; + + $form['sel_project'] = array( + '#type' => 'select', + '#options' => $my_project, + '#default_value' => $default_value, + '#disabled' => $disabled, + '#id' => 'rawpheno-select-project-field', + ); + + // This block is to ensure select project select box is always default to + // "please select a project" or to the first project option in the beginning of the upload process. + // It will also default to this option when user refreshes the page. + if ($form_stage == 'check') { + drupal_add_js('jQuery(document).ready(function() { + jQuery("#rawpheno-select-project-field").val(0); + })', 'inline'); + } + } + else { + // No project is assigned to user. + $form['no_project'] = array( + '#markup' => '
' . t('No experiment is assigned to this account. Please contact the administrator of this website.') . '
', + ); + } + } + + // Note: + // When there is no project defined in the module, the error message is handled by the theme. + + // Get the directory path of rawpheno module. + $path = drupal_get_path('module', 'rawpheno') . '/theme/'; + + // Load corresponding function callback together with JavaScript and CSS. + switch($form_stage) { + case 'check': + // Stage 01 - upload and check spreadsheet. + $form['#attached']['css'] = array($path . 'css/rawpheno.upload.stage01.css'); + $form['#attached']['js'] = array($path . 'js/rawpheno.upload.stage01.js', + $path . 'js/rawpheno.upload.script.js'); + + $form = rawpheno_upload_form_stage_check($form, $form_state); + break; + + case 'review': + // Stage 02 - describe form (only when there is additional trait). + $form['#attached']['css'] = array($path . 'css/rawpheno.upload.stage02.css'); + $form['#attached']['js'] = array($path . 'js/rawpheno.upload.script.js'); + + $form = rawpheno_upload_form_stage_review($form, $form_state); + break; + + case 'save': + // Stage 03 - save to databse and success page. + $form['#attached']['css'] = array($path . 'css/rawpheno.upload.stage03.css'); + $form['#attached']['js'] = array($path . 'js/rawpheno.upload.stage03.js', + $path . 'js/rawpheno.upload.script.js'); + + $form = rawpheno_upload_form_stage_save($form, $form_state); + break; + } + + return $form; +} + + +/** + * Function callback: Construct form for Stage 01. + * + * Stage 01 form allows user to upload data collection spreadsheet and perform basic compliance test. + */ +function rawpheno_upload_form_stage_check($form, &$form_state) { + // Create an instance of DragNDrop Upload. + // SETTINGS: + // #file_upload_max_size: max file size allowed + // #upload_location: destination of file + // #upload_event: manual - show an upload button or auto - uploads after drag drop + // #upload_validators: allowed file extensions + // #upload_button_text: label of upload button + // #droppable_area_text: text in drop area + // #progress_indicator: none, throbber or bar + // #progress_message: message to display while processing + // #allow_replace: allow user to replace file by drag and drop another file + // #standard_upload: show browse button or not + // #upload_button_text: submit button text (not required when auto submit is auto) + + $form['dnd'] = array( + '#type' => 'dragndrop_upload', + '#file_upload_max_size' => '10M', + '#upload_location' => 'public://', + '#upload_event' => 'auto', + // NOTE: Accept the listed file extension and let the spreadsheet reader tell if file is valid to generate an an error message. + // No silent treatment. + '#upload_validators' => array( + 'file_validate_extensions' => array('xlsx xls jpg jpeg gif png txt doc pdf ppt pps odt ods odp csv'), + ), + '#droppable_area_text' => t('Drag your Microsoft Excel Spreadsheet file here'), + '#progress_indicator' => 'throbber', + '#progress_message' => 'Validating your spreadsheet file. Please wait...', + '#allow_replace' => 1, + '#standard_upload' => 1, + '#upload_button_text' => '', + + // We are adding our own element process function so that we can make a successfully + // uploaded/validated file permanent during the AJAX process rather than waiting for + // them to click the "Next" button. + '#process' => array( + 'file_managed_file_process', + 'dragndrop_upload_element_element_process', + 'rawpheno_phenotype_upload_file_element_process', + ), + ); + + return $form; +} + + +/** + * Function callback: Construct form for Stage 02. + * + * Stage 02 form allows user to describe and save a additional trait/s found in the spreadsheet submitted in Stage 01. + * + * Assuming the file uploaded properly, we have access to an excel file and need to + * ensure that all traits have been described. If there are any that haven't then + * we need to ask the the user to define them now. + * + * NOTE: User has the option to skip this stage by not checking any of the new traits found and clicking next step. + */ +function rawpheno_upload_form_stage_review(&$form, &$form_state) { + // Array to hold new headers. + $new_header = array(); + + // The project id number the spreadsheet and column headers are specific to. + $project_id = $form_state['values']['sel_project']; + $project_name = rawpheno_function_getproject($project_id); + + // FIND NEW HEADERS. + // First step, determine which headers/traits need to be described. + if (isset($form_state['multistep_values']['fid'])) { + // Get Drupal file object. + $file = file_load($form_state['multistep_values']['fid']); + + // Ensure that the file exits and project id is selected. + // The form will unset the project id upon page refresh, this will catch the condition + // when no project id is selected, user will be stopped and is requested to retry the process. + if ($file AND !empty($project_id)) { + $new_header = rawpheno_indicate_new_headers($file, $project_id); + + // Calling all modules implementing hook_rawpheno_AGILE_stock_name_alter(): + drupal_alter('rawpheno_ignorecols_newcolumn', $new_header, $project_name); + + $form_state['multistep_values']['new_headers'] = $new_header; + } + else { + drupal_set_message(t('Unable to access your file. Please try uploading again.'), 'error'); + } + } + else { + drupal_set_message(t('We have no record of your uploaded file. Please try uploading it again.'), 'error'); + } + + // If we were unable to access the file or project is not selected then don't let them proceed. + if (!isset($file) OR empty($file) OR empty($project_id)) { + $form['notice'] = array( + '#type' => 'markup', + '#markup' => '
' + . t('Unable to access uploaded file. Please attempt to upload your file again on the previous page. If the problem persists then contact the administrator.', + array('@upload-page' => url('phenotypes/raw/upload'))) + . '
', + ); + + // No submit button as well. + unset($form['next_step']); + + return $form; + } + + + // NO NEW HEADER. + // If there are no new headers then they don't have to do anything. The module will display a summary + // showing the number of parsed column headers in the spreadsheet. + if (empty($new_header)) { + $all_headers = rawpheno_all_headers($file); + + $markup = ' + + '; + + $form['xls_summary_fldset'] = array( + '#type' => 'fieldset', + '#title' => t('Describe new trait'), + ); + + $form['xls_summary_fldset']['information'] = array( + '#type' => 'markup', + '#markup' => $markup, + ); + + $form['notice'] = array( + '#type' => 'markup', + '#markup' => '
No new traits were detected in the spreadsheet. Please click "Next Step".
', + ); + + return $form; + } + + + // NEW HEADERS FOUND. + if (function_exists('chado_get_cv')) { + $cv = chado_get_cv(array('name' => 'phenotype_measurement_types')); + } + else { + $cv = tripal_get_cv(array('name' => 'phenotype_measurement_types')); + } + + $cv_id = $cv->cv_id; + + // Where clause that form part of the SQL below. + $where = array( + 'yes' => "TRIM(LOWER(name)) = :cvterm LIMIT 1", + 'no' => "TRIM(LOWER(SPLIT_PART(name, '(', 1))) LIKE :cvterm" + ); + + $sel = "SELECT * FROM {cvterm} WHERE + cvterm_id NOT IN (SELECT cvterm_id FROM pheno_project_cvterm WHERE project_id = :project_id) + AND cv_id = :cv_id AND %s"; + + // Otherwise, we need a form! + // Main fieldset container for form elements. + $form['xls_review_fldset'] = array( + '#type' => 'fieldset', + '#title' => t('Check the traits that you want to describe and save'), + ); + + $headers_no_format = array_map('rawpheno_function_delformat', $new_header); + + // Array to hold all checked headers. + $arr_checked_header = array(); + + foreach($new_header as $i => $k) { + if (isset($k) AND !empty($k)) { + // To prevent spills of information to other form set, reset this variable that holds + // query result object. + if (isset($cvterm_info)) { + unset($cvterm_info); + } + + // CHECKBOX to let user select a trait to describe and save. If left unchecked, system will not save it. + $form['xls_review_fldset']['chk_' . $i] = array( + '#type' => 'checkbox', + '#title' => t(ucwords($k)), + '#ajax' => array( + 'callback' => 'ajax_rawpheno_upload_form_step2_expand_trait_callback', + 'wrapper' => 'trait-description-' . $i, + 'effect' => 'fade', + 'trait_index' => $i, + ), + ); + + // Container div that holds form elements. + $form['xls_review_fldset']['fldset_' . $i] = array( + '#type' => 'markup', + '#prefix' => '
', + '#suffix' => '
', + ); + + // By default, show the describe form. + $show_form = 'yes'; + + // If the checkbox is checked then show the fields user want to described. + if (isset($form_state['values']['chk_' . $i]) AND ($form_state['values']['chk_' . $i] == TRUE)) { + // TERM NAME/TRAIT/HEADER + $form['xls_review_fldset']['fldset_' . $i]['txt_header_' . $i] = array( + '#type' => 'hidden', + '#value' => $k, + ); + + // Clean up the current header. + $name = trim(strtolower(preg_replace('!\s+!', ' ', $k))); + $arr_checked_header[] = $name; + + // Test if the header has a unit component and set the variable accordingly. + $has_unit = (strpbrk($name, '()')) ? 'yes' : 'no'; + + // Format the name to be used in the following SQL. When the name has a unit component, + // we just feed the name to the SQL using equal operator, otherwise, we use like operator + // to find all similar headers and suggest it. + $cvterm = ($has_unit == 'yes') ? $name : '%' . $name . '%'; + + // Construct the query statement. + $sql = sprintf($sel, $where[$has_unit]); + $args = array(':project_id' => $project_id, ':cv_id' => $cv_id, ':cvterm' => $cvterm); + $h = chado_query($sql, $args); + + if ($h->rowCount() > 0) { + if ($has_unit == 'yes') { + // Load information about the header. + $cvterm_info = $h->fetchObject(); + + // Tell user that the column header exists already. + $form['xls_review_fldset']['fldset_' . $i]['notice_' . $i] = array( + '#markup' => '
The system has detected this column header in the database. + All form fields are disabled to prevent alteration to the original version. + To save this header and data associated to it, please keep the checkbox checked.
', + ); + + $show_form = 'yes'; + } + else { + // Suggest similar header. + // Ensure that the list of headers to be suggested is not in the list of headers detected, + // that way we can avoid duplicate headers. + $header_options = array(); + foreach($h as $m) { + $this_header = trim(strtolower($m->name)); + + if (!in_array($this_header, $headers_no_format)) { + $header_options[$m->cvterm_id] = $m->name; + } + } + + if (count($header_options) > 0) { + $form['xls_review_fldset']['fldset_' . $i]['sel_header_' . $i] = array( + '#type' => 'select', + '#title' => t('Did you mean?'), + '#options' => array('-1' => '---', 0 => 'None of these apply') + $header_options, + '#ajax' => array( + 'callback' => 'ajax_rawpheno_upload_form_step2_load_header_info', + 'wrapper' => 'trait-description-' . $i, + 'effect' => 'fade', + 'trait_index' => $i, + ), + '#element_validate' => array('rawpheno_newheader_didyoumean_validate'), + '#attributes' => array('class' => array('sel-header')), + '#description' => t('The system has detected a similar header in the database. + It is recommended that you select the header from the select box that best describes your data. + If the header is not listed, please select None of these apply option and use the form below to describe this column header.'), + ); + + $show_form = 'no'; + } + else { + $show_form = 'yes'; + } + + if (isset($form_state['values']['sel_header_' . $i])) { + if ($form_state['values']['sel_header_' . $i] > 0) { + $cvterm_id = $form_state['values']['sel_header_' . $i]; + + if (function_exists('chado_get_cvterm')) { + $cvterm_info = chado_get_cvterm(array('cvterm_id' => $cvterm_id)); + } + else { + $cvterm_info = tripal_get_cvterm(array('cvterm_id' => $cvterm_id)); + } + + $show_form = 'yes'; + } + elseif ($form_state['values']['sel_header_' . $i] == 0) { + $show_form = 'yes'; + } + } + } + } + + + if ($show_form == 'yes') { + // TERM DEFINITION + $form['xls_review_fldset']['fldset_' . $i]['txt_def_' . $i] = array( + '#type' => 'textarea', + '#title' => t('Definition'), + '#required' => TRUE, + '#description' => t('A human-readable text definition'), + ); + + if (isset($cvterm_info) && isset($cvterm_info->definition)) { + $form['xls_review_fldset']['fldset_' . $i]['txt_def_' . $i]['#value'] = $cvterm_info->definition; + $form['xls_review_fldset']['fldset_' . $i]['txt_def_' . $i]['#disabled'] = TRUE; + } + + + // UNIT + $form['xls_review_fldset']['fldset_' . $i]['txt_unit_' . $i] = array( + '#type' => 'textfield', + '#title' => t('Unit'), + '#required' => TRUE, + '#maxlength' => 100, + '#element_validate' => array('rawpheno_newheader_unit_validate'), + '#description' => t('Unit of measurement used'), + ); + + if (isset($cvterm_info)) { + $unit_val = strpbrk($cvterm_info->name, '()'); + // Remove any parenthesis making its way to the final value. + $unit_val = str_replace(array('(', ')'), '', $unit_val); + + $form['xls_review_fldset']['fldset_' . $i]['txt_unit_' . $i]['#value'] = trim($unit_val); + $form['xls_review_fldset']['fldset_' . $i]['txt_unit_' . $i]['#disabled'] = TRUE; + } + else { + if ($has_unit == 'yes') { + $u = strpbrk($name, '()'); + // Remove any parenthesis making its way to the final value. + $u = str_replace(array('(', ')'), '', $u); + + $form['xls_review_fldset']['fldset_' . $i]['txt_unit_' . $i]['#value'] = trim($u); + $form['xls_review_fldset']['fldset_' . $i]['txt_unit_' . $i]['#disabled'] = FALSE; + } + } + + + // DESCRIPTION - describe the trait. + $form['xls_review_fldset']['fldset_' . $i]['txtarea_describe_' . $i] = array( + '#type' => 'textarea', + '#title' => t('Describe the method used'), + '#required' => TRUE, + '#description' => t('Describe the method used to collect this data if you used a scale, be specific'), + ); + + if (isset($cvterm_info)) { + $cvterm_describe_unit = rawpheno_function_cvterm_properties($cvterm_info->cvterm_id); + + $form['xls_review_fldset']['fldset_' . $i]['txtarea_describe_' . $i]['#value'] = $cvterm_describe_unit; + $form['xls_review_fldset']['fldset_' . $i]['txtarea_describe_' . $i]['#disabled'] = TRUE; + } + + + // Note fields are required + $form['xls_review_fldset']['fldset_' . $i]['required_' . $i] = array( + '#markup' => '
* means field is required
' + ); + } + } + } + } + + // Hidden field containing all the checked new headers + if (isset($arr_checked_header) AND count($arr_checked_header) > 0) { + $form['all_header_checked'] = array( + '#type' => 'hidden', + '#value' => implode(',', $arr_checked_header), + ); + } + + // Indicator to user of how many of the new traits found has been described. + $form['traits_checked'] = array( + '#type' => 'markup', + '#markup' => '
You have described 0 trait. Please click "Next Step".
' + ); + + return $form; +} + + +/** + * Function validate the unit field when new header is detected in the spreadsheet. + * Validation includes ensuring that user does not use parenthesis ( and ) in the unit. + */ +function rawpheno_newheader_unit_validate($element, &$form_state) { + $unit_value = trim($element['#value']); + $project_id = $form_state['values']['sel_project']; + + // strpbrk() Returns a string starting from the character found, or FALSE if it is not found. + if (strpbrk($unit_value, '()')) { + form_set_error($element['#name'], 'The value in the unit field contains characters "(" and/or ")". Please remove these characters and try again.'); + } + else { + // Test the name plus the unit combination if it is in the project. + $header_field = str_replace('unit', 'header', $element['#name']); + $header_value = $form_state['values'][$header_field]; + + if (!strpbrk($header_value, '()')) { + $name_value = trim(strtolower($header_value . ' (' . strtolower($unit_value) . ')')); + + $sql = "SELECT cvterm_id + FROM {cvterm} INNER JOIN pheno_project_cvterm USING(cvterm_id) + WHERE project_id = :project_id AND TRIM(LOWER(name)) = :cvterm LIMIT 1"; + + $args = array(':project_id' => $project_id, ':cvterm' => $name_value); + $h = chado_query($sql, $args); + + if ($h->rowCount() == 1) { + form_set_error($element['#name'], 'Cannot save column header and unit. ' . ucfirst($name_value) . ' exists in this project.'); + } + + // Test if user is about to save same headers. + if (isset($form_state['values']['all_header_checked'])) { + $h = $form_state['values']['all_header_checked']; + $header_validation = explode(',', $h); + + if (in_array($name_value, $header_validation)) { + form_set_error($element['#name'], 'Cannot save multiple entries of the same column header and unit combination.'); + } + } + } + } +} + + +/** + * Function callback: validate Did you mean? select box + */ +function rawpheno_newheader_didyoumean_validate($element, &$form_state) { + if ($element['#value'] < 0) { + form_set_error($element['#name'], 'Please select an option and try again.'); + } +} + + +/** + * Function load column header information. + */ +function ajax_rawpheno_upload_form_step2_load_header_info($form, $form_state) { + $i = $form_state['triggering_element']['#ajax']['trait_index']; + + return $form['xls_review_fldset']['fldset_' . $i]; +} + + +/* + * Selects the piece of the form we want to use as replacement text and returns it as a form (renderable array). + * + * @return renderable array (the trait description elements) + */ +function ajax_rawpheno_upload_form_step2_expand_trait_callback($form, $form_state) { + // Unique id of each form set. + $i = $form_state['triggering_element']['#ajax']['trait_index']; + + return $form['xls_review_fldset']['fldset_' . $i]; +} + + +/** + * Function callback: Construct form for Stage 03. + * + * Stage 03 form is the final stage that displays a status message + * and a navigation button to direct user after a successful file upload. + */ +function rawpheno_upload_form_stage_save($form, &$form_state) { + $job_id = NULL; + global $user; + + if (isset($form_state['build_info']['args'][0])) { + $job_id = $form_state['build_info']['args'][0]; + + // We only want to run jobs that has phenotypic data in it. Otherwise we tell user + // job is not valid (in case user will hack the url containing the job id). + // Retrieve the tripal job and determine the percent complete. + $job = tripal_get_job($job_id); + + // If job is valid. + if ($job) { + if ($job->uid == $user->uid) { + // Job id belongs to the user. Authorized. + $job_status = trim(strtolower($job->status)); + + // Check if it is a valid job and not a BLAST or other job type. + if ($job->callback == 'rawpheno_load_spreadsheet') { + if ($job_status == 'completed') { + // Is completed some time ago. + $form['notice'] = array( + '#markup' => '
It appears that you are attempting to submit a spreadsheet that has been processed already
' + ); + } + elseif ($job_status == 'error') { + // Has error. + $form['notice'] = array( + '#markup' => '
It appears that you are attempting to submit a spreadsheet that has errors.
' + ); + } + elseif ($job_status == 'cancelled') { + // Is cancelled. + $form['notice'] = array( + '#markup' => '
It appears that you are attempting to submit a spreadsheet that has been cancelled.
' + ); + } + else { + // Not processed yet - show the progress bar. + // A valid job - work on it. + $form['notice'] = array( + '#type' => 'markup', + '#markup' => + '
Your spreadsheet has been successfully submitted and will not be interupted if you choose to leave this page.
' + . '
The progress bar below indicates our progress updating ' . strtoupper($_SERVER['SERVER_NAME']) . '. Your data will not be available until the progress bar below completes.
' + ); + + // Add Progress JS Library. + drupal_add_js('misc/progress.js'); + + // This is the link passed to the JavaScript Progress.js as the parameter to a function + // that monitors a link. The link is a function callback that generates a JSON object + // containing the number of rows save in percent. See file: rawpheno.module. + $form['tripal_job_id'] = array( + '#type' => 'hidden', + '#value' => $GLOBALS['base_url'] . '/phenotypes/raw/upload/job_summary/' . $job->job_id, + '#attributes' => array('id' => 'tripal-job-id'), + ); + + // We make a DIV which the progress bar can occupy. You can see this in use + // in ajax_example_progressbar_callback(). + $form['status'] = array( + '#type' => 'markup', + '#markup' => '
' + ); + } + } + else { + // Job not supported by this module. + $form['notice'] = array( + '#markup' => '
It appears that you are attempting to request a process that is not supported by this module.
' + ); + } + } + else { + // Not authorized. + $form['notice'] = array( + '#markup' => '
It appears that you are attempting to submit a spreadsheet that is not in your account.
' + ); + } + } + else { + // Job is not valid or does not exists. + $form['notice'] = array( + '#markup' => '
The job request to save spreadsheet file does not exist.
' + ); + } + } + + return $form; +} + + +/** + * Implements hook_file_insert(). + * Save file information when file is saved (backup). + * + * @param $file + * Drupal file opbject. + */ +function rawpheno_file_insert($file) { + // Process file only when there is a request to save a file and that request is coming from backup page. + if (isset($file->source)) { + if ($file->source == 'bdnd') { + // User id of the currently logged in user. + $user_id = $GLOBALS['user']->uid; + // The project id field. + $project_id = $_POST['backup_sel_project']; + // The notes field. + $notes = trim(strip_tags($_POST['backup_txt_description'])); + + // Query the record id of the project to user record. + // The result id will be used to map a backup file to user and to project. + $sql = "SELECT project_user_id FROM pheno_project_user WHERE project_id = :project_id AND uid = :user_id LIMIT 1"; + $args = array(':project_id' => $project_id, ':user_id' => $user_id); + $prj_usr_id = db_query($sql, $args) + ->fetchField(); + + // Get the validation result performed to the spreadsheet file and store the result along with the file information. + // The same validation process performed in upload data page is carried out to backup file. However, the result + // is stored as plain text and passed and failed icons are replaced by words passed and failed, respectively. + $status = rawpheno_validate_excel_file($file, $project_id, 'backup'); + + $s = (isset($status['status'])) ? $status['status'] : $status; + + // Express the validation result array into human readable non-html content format. + $validation_result = ''; + // Call the same validator function used in upload data. + $validators = module_invoke_all('rawpheno_validators'); + + // For each status result, convert it to text based and add a unique text indicator to be used + // as key to explode the entire text and create a list using the
tag. + foreach($s as $key => $result) { + $flag = ($result === TRUE) ? 'passed' : 'failed'; + + // The item keyword will be used to create a list of validation entries when displaying validaiton result to user. + $validation_result .= '#item: (' . $flag . ') ' . $validators[$key]['label'] . "\n"; + if ($result !== TRUE) { + $message = call_user_func($validators[$key]['message callback'], $result); + if (!empty($message)) { + $validation_result .= implode("\n", $message); + } + } + } + + // Compute the version number of this file. + $sql = "SELECT MAX(t2.version) + 1 AS version + FROM {pheno_project_user} AS t1 RIGHT JOIN {pheno_backup_file} AS t2 USING(project_user_id) + WHERE t1.project_id = :project_id AND t1.uid = :user_id LIMIT 1"; + + $args = array(':project_id' => $project_id, ':user_id' => $user_id); + $version = db_query($sql, $args) + ->fetchField(); + + // On initial upload the file version is null, in this case + // version is set to 1. Version is incremented by 1 (+1) in + // subsequent uploads. + $version = ($version === null) ? 1 : $version; + + // Insert a record of this file. + // File version, which is a sequential order (integer) is handled by the rdbms as it is set to serial type. + if (isset($status['check_limit'])) { + $validation_result .= "#item: (failed) NOTICE : " . $status['check_limit'] . "\n"; + } + + db_insert('pheno_backup_file') + ->fields(array('fid' => $file->fid, + 'notes' => $notes, + 'version' => $version, + 'project_user_id' => $prj_usr_id, + 'validation_result' => $validation_result)) + ->execute(); + + // Finally, make the file permanent. + rawpheno_upload_make_file_permanent($file->fid); + + // Make the validation result available to frontend. + $_SESSION['rawpheno']['backup_file_validation_result'] = $status; + } + } +} + + +/** + * Upload validators callback: + * Basic compliance test to spreadsheet submitted. + * + * @param $file + * Drupal file opbject. + */ +function rawpheno_file_validate($file) { + // 10 mins (60 * 10). + $max_time = 600; + + if ($file->source == 'dnd') { + // Set processing time to 10 mins. + ini_set('max_execution_time', $max_time); + + // Upload data page. + + // Project id number the spreadsheet and column headers are specific to. + $project_id = (int)$_POST['sel_project']; + + // Validate the file. + // The following function will return an array specifying which of the validation + // steps passed and providing infomration for those that failed. + $status = rawpheno_validate_excel_file($file, $project_id, 'upload'); + + // We want to show the user which steps passed/failed even if all of them passed, + // so lets do that now. We use drupal_set_message() because returning from this function + // creates an error message and halts file upload, whereas, using drupal_set_message() + // allows us to print to the screen regardless of failure/success. + drupal_set_message(theme('rawpheno_upload_validation_report', array('status' => $status)), 'rawpheno-validate-progress'); + + // Now we want to determine if validation passed or failed as a whole. + // To do that we have to look at each step and only if all steps passed + // did the file pass validation and can be uploaded. + $all_passed = TRUE; + foreach ($status as $test_result) { + if ($test_result !== TRUE) { + $all_passed = FALSE; + break; break; + } + } + + // hook_file_validate() expects an array of error messages if validation failed and + // and empty array if there are no errors. We don't want this system to print the errors + // for us since we are using our more friendly theme (see drupal_set_message() above). + // The work-around is to pass FALSE if validation failed. + if ($all_passed) { + drupal_set_message('Your file uploaded successfully. Please click "Next" to continue.'); + return array(); + } + else { + return FALSE; + } + } + elseif ($file->source == 'bdnd') { + // Set processing time to 10 mins. + ini_set('max_execution_time', $max_time); + + // Backup file page. + + // Array to hold the validation result. + $status = array(); + + // Project id number the spreadsheet and column headers are specific to. + $project_id = (int)$_POST['backup_sel_project']; + + // Perform basic compliance test: + // - A project is selected. + // - File is Microsoft Excel Spreadhseet file. + // - Measurement tab exists. + // - Essential column headers defined in the project are present. + $flag_index = array('project_selected', 'is_excel', 'tab_exists', 'column_exists'); + + // Validate file. + $flags = rawpheno_validate_excel_file($file, $project_id, 'backup'); + + $s = (isset($flags['status'])) ? $flags['status'] : $flags; + + // If DND backup, missing measurements, skip all validator but + // save/backup the file anyway. + if ($s['tab_exists'] === FALSE) { + return array(); + } + + // Read only the status from test listed above. + foreach($s as $i => $v) { + if (in_array($i, $flag_index) AND ($v === FALSE || $v === 'todo')) { + $status[$i] = $v; + } + } + + // When any of the mentioned test failed, show them to user. + if (count($status) > 0) { + if (isset($flags['check_limit'])) { + drupal_set_message($flags['check_limit'], 'error'); + } + + drupal_set_message(theme('rawpheno_upload_validation_report', array('status' => $status)), 'rawpheno-validate-progress'); + return FALSE; + } + + // Else, proceed to hook_file_insert(). + } + else { + // File source is one that is not of interest to us. + // Do not return anything or it will trigger validation errors for other modules. + } +} + + +/** + * Make the phenotype excel file permanent on successful upload. + * + * This is an additional process handler used by the form API to generate the form array + * for a given element. Usually it is used to make a custom form element or enhance a + * standard form element. + * + * We are using it to capture the just uploaded file within the AJAX call by checking + * when the element is rendered if it has a fid (ie: has been saved). + */ +function rawpheno_phenotype_upload_file_element_process($element, &$form_state, $form) { + if (isset($element['#value']['fid']) AND !empty($element['#value']['fid'])) { + $file_id = $element['#value']['fid']; + rawpheno_upload_make_file_permanent($file_id); + } + + return $element; +} + + +/** + * Make the file uploaded permanent and make a record indicating that file is used by the module. + * + * @param $file_id + * File id in Drupal file object. + */ +function rawpheno_upload_make_file_permanent($file_id) { + // Get the file object. + $file = file_load($file_id); + + if ($file) { + // Make the file permanent. + $file->status = FILE_STATUS_PERMANENT; + file_save($file); + + // Also, point out that we are using it ;-) + // Note, the file_usage_add() function expects a numerical unique id which we don't have. + // We have gotten around this by using the uid concatenated with the timestamp using + // the assumption that a single user cannot upload more than one phenotype file within a second. + file_usage_add($file, 'rawpheno', 'rawphenotypes-file', $file->uid . $file->timestamp); + } +} + + +/** + * Implements hook_validate(). + */ +function rawpheno_upload_form_master_validate($form, &$form_state) { } + + +/** + * Implements hook_submit(). + * + * Master submit to handle form submit. + */ +function rawpheno_upload_form_master_submit(&$form, &$form_state) { + // Which button triggers a submit action. + $btn_submit = $form_state['triggering_element']['#value']; + + // Save any additional traits and then submit a job to save the spreadsheet. + if ($form_state['stage'] == 'review') { + $job_id = rawpheno_submit_review($form, $form_state); + + // Then we need to add the job_id to the path so the system can keep track of it. + if ($job_id) { + drupal_goto(current_path() . '/' . $job_id); + } + } + + // If we just uploaded the file then we want to save the fid for easy access. + if (isset($form_state['values']['dnd'])) { + $form_state['multistep_values']['fid'] = $form_state['values']['dnd']; + } + + // If the next step button was pressed then iterate to the next step. + if ($btn_submit == 'Next Step') { + // Definitely save the form id. + if(isset($form_state['multistep_values']['form_build_id'])) { + $form_state['values']['form_build_id'] = $form_state['multistep_values']['form_build_id']; + } + + // Save the values from the current step. + $form_state['multistep_values'][$form_state['stage']] = $form_state['values']; + + // Iterate to the next step. + $form_state['new_stage'] = rawpheno_next_page($form, $form_state); + + // Ensure the form state is saved and the form is rebuilt. + $form_state['multistep_values']['form_build_id'] = $form_state['values']['form_build_id']; + $form_state['stage'] = $form_state['new_stage']; + $form_state['rebuild'] = TRUE; + } +} + + +/** + * Save spreadsheet to database. + */ +function rawpheno_submit_review($form, &$form_state) { + // Project id number the spreadsheet and column headers are specific to. + $project_id = $form_state['values']['sel_project']; + + // Save spreadsheet data in the following order. + // 1. New column headers. + // 2. The entire spreadsheet. + + // cvterm id of controlled vocabulary. + if (function_exists('chado_get_cv')) { + $cvid = chado_get_cv(array('name' => 'phenotype_measurement_units')); + } + else { + $cvid = tripal_get_cv(array('name' => 'phenotype_measurement_units')); + } + + $cv_measurements_unit = $cvid->cv_id; + + // 1. Save new headers. + // Read variable that holds new column headers. + $new_header = $form_state['multistep_values']['new_headers']; + + // Create an array of new hearders with flag/status if user wants to save it. + // This array will be passed to rawpheno_load_spreadsheet. + $arr_newheaders = array(); + + // Determine if there is new header. + if (count($new_header) > 0) { + $trait_type = rawpheno_function_trait_types(); + + // Read each column header. + foreach($new_header as $i => $header) { + // For each new header store information provided in the interface. + // Indicates if user has check this header for saving. + $header = trim(str_replace(array("\n", "\r", " "), ' ', $header)); + $header = preg_replace('/\s+/', ' ', $header); + + $arr_newheaders[$header]['flag'] = ($form_state['values']['chk_' . $i] == 1) ? 1 : 0; + + // Determine if the form in review traits has been filled out and checkbox + // has been checked by user. If it has been checked then save the trait. + if ($form_state['values']['chk_' . $i] === 1 && !empty($form_state['values']['txt_header_' . $i])) { + // Before save, we need to tell if the header is present in the database and + // user just wants to reuse them. Otherwise, add a new header. + // Reuse header - set to OPTIONAL. + if ((isset($form_state['values']['sel_header_' . $i]) AND $form_state['values']['sel_header_' . $i] > 0) OR + (isset($form_state['values']['txt_header_cvterm_id_' . $i]))) { + + // User selected from a list of similar headers. + $cvterm_id = (isset($form_state['values']['sel_header_' . $i])) + ? $form_state['values']['sel_header_' . $i] + : $form_state['values']['txt_header_cvterm_id_' . $i]; + + // Map this header to the project. + $sql = "SELECT cvterm_id FROM {pheno_project_cvterm} WHERE project_id = :project_id AND cvterm_id = :cvterm_id LIMIT 1"; + $args = array(':project_id' => $project_id, ':cvterm_id' => $cvterm_id); + + $h = db_query($sql, $args); + if ($h->rowCount() <= 0) { + // Add to project only when it is not in the project. + // Set the trait type to contributed. + db_insert('pheno_project_cvterm') + ->fields(array( + 'project_id' => $project_id, + 'cvterm_id' => $cvterm_id, + 'type' => $trait_type['type2']) + ) + ->execute(); + } + + // When saving this data for this header, use the cvterm_id. + $arr_newheaders[$header]['alt_header'] = $cvterm_id; + continue; + } + + // Check if the trait exists in the database, then it is likely + // that the user is reusing the trait - threfore it is not contributed and just map + // the cvterm id to a project. + + // Add the as contributed - set to CONTRIBUTED. + // Construct the column header name. + $name = trim($form_state['values']['txt_header_' . $i]); + $name = preg_replace('/\s+/', ' ', $name); + + $unit = trim($form_state['values']['txt_unit_' . $i]); + $method = trim($form_state['values']['txtarea_describe_' . $i]); + $def = trim($form_state['values']['txt_def_' . $i]); + + // Format the header. + if (strpbrk($name, '()')) { + // Header has a unit part. + $name = trim(str_replace(array("\n", "\r", " "), ' ', $name)); + } + else { + // Construct header plus the unit. + $name = $name . ' (' . strtolower($unit) . ')'; + } + + // Trait properties - use when inserting the cterm and reference to other property. + $m_cvterm = array( + 'id' => 'rawpheno_tripal:' . $name, + 'name' => $name, + 'definition' => $def, + 'cv_name' => 'phenotype_measurement_types' + ); + + // Search the name in cvterm and decide if trait should be considered optional or contributed. + $sql = "SELECT t2.cvterm_id + FROM {cv} AS t1 INNER JOIN {cvterm} AS t2 USING (cv_id) + WHERE + trim(lower(t2.name)) = trim(lower(:cvterm_name)) + AND t1.name = :cv_name LIMIT 1"; + + $args = array(':cvterm_name' => $m_cvterm['name'], ':cv_name' => $m_cvterm['cv_name']); + $result = chado_query($sql, $args) + ->fetchObject(); + + if ($result) { + // Found use the id. + $m_cvterm_id = $result->cvterm_id; + // Trait is optional. + $type = $trait_type['type2']; + } + else { + // Not found, insert and get the inserted id. + $m = tripal_insert_cvterm($m_cvterm); + $m_cvterm_id = $m->cvterm_id; + // Trait is contributed. + $type = $trait_type['type5']; + } + + // When saving this data for this header, use the cvterm_id. + $arr_newheaders[$header]['alt_header'] = $m_cvterm_id; + db_insert('pheno_project_cvterm') + ->fields(array('project_id' => $project_id, + 'cvterm_id' => $m_cvterm_id, + 'type' => $type)) + ->execute(); + + // Create a R Friendly version. + $r_version = rawpheno_function_make_r_compatible($m_cvterm['name']); + + if (function_exists('chado_get_cv')) { + $cv_rfriendly = chado_get_cv(array('name' => 'phenotype_r_compatible_version')); + } + else { + $cv_rfriendly = tripal_get_cv(array('name' => 'phenotype_r_compatible_version')); + } + + $values = array( + 'cvterm_id' => $m_cvterm_id, + 'type_id' => $cv_rfriendly->cv_id, + 'value' => $r_version, + 'rank' => 0 + ); + + chado_insert_record('cvtermprop', $values); + + // Then save the Unit. + $u_cvterm = array( + 'id' => 'rawpheno_tripal:' . strtolower($unit), + 'name' => strtolower($unit), + 'definition' => $unit, + 'cv_name' => 'phenotype_measurement_units' + ); + + $u_cvterm_id = chado_select_record('cvterm',array('cvterm_id'), + array('name' => $u_cvterm['name'], + 'cv_id' => array('name' => $u_cvterm['cv_name']))); + if (!$u_cvterm_id) { + $u_cvterm_id = tripal_insert_cvterm($u_cvterm); + } + + // Grab just the id. + if (is_array($u_cvterm_id)) { + $u_cvterm_id = $u_cvterm_id[0]->cvterm_id; + } + elseif (is_object($u_cvterm_id)) { + $u_cvterm_id = $u_cvterm_id->cvterm_id; + } + + // Don't forget the method description. + $prop = array( + 'cvterm_id' => $m_cvterm_id, + 'type_id' => $cv_measurements_unit, + 'value' => $method, + 'rank' => 0, + ); + + $prop_id = chado_select_record('cvtermprop', array('cvtermprop_id'), $prop); + if (!$prop_id) { + $prop = chado_insert_record('cvtermprop', $prop); + } + + // Finally relate the measurement and unit. + $rel = array( + 'subject_id' => $u_cvterm_id, + 'type_id' => $cv_measurements_unit, + 'object_id' => $m_cvterm_id, + ); + $rel_id = chado_select_record('cvterm_relationship', array('cvterm_relationship_id'), $rel); + if (!$rel_id) { + chado_insert_record('cvterm_relationship', $rel); + } + } + } + } + + // 2. The entire spreadsheet. + // Get the variable that holds the path to the spreadsheet file in the server. + $file = file_load($form_state['multistep_values']['fid']); + $xls_file = drupal_realpath($file->uri); + + // Array of required traits excluding Name. + $plantprop_headers = rawpheno_project_plantproperty_traits($project_id); + + // Drupal user object. + global $user; + + if (isset($xls_file) && !empty($xls_file)) { + $job_id = tripal_add_job( + "Upload Phenoypic data: " . $xls_file, + 'rawpheno', + 'rawpheno_load_spreadsheet', + array( + $project_id, + serialize($arr_newheaders), + $form_state['multistep_values']['fid'], + serialize($plantprop_headers) + ), + $user->uid + ); + + return $job_id; + } +} diff --git a/include/rawpheno.upload.helpers.inc b/includes/rawpheno.upload.helpers.inc old mode 100755 new mode 100644 similarity index 96% rename from include/rawpheno.upload.helpers.inc rename to includes/rawpheno.upload.helpers.inc index 3bc05be..e86f45e --- a/include/rawpheno.upload.helpers.inc +++ b/includes/rawpheno.upload.helpers.inc @@ -1,69 +1,69 @@ - 1, 'review' => 2, 'save' => 3); - $current_step = (isset($form_stages[$form_state['stage']])) ? $form_stages[$form_state['stage']] : 1; - - // Array of stage indicators. - $stages = array(1 => '1. Validate Spreadsheet', - 2 => '2. Describe New Trait', - 3 => '3. Save Spreadsheet'); - - $markup = ''; - foreach($stages as $k => $v) { - $class = ($k <= $current_step) ? '' : ' progress-stage-todo'; - $markup .= '
-  ' . $v . '  -
'; - } - - // Add header to each stage with corresponding - // stage information defined above. - $form['header_upload'] = array( - '#type' => 'markup', - '#markup' => $markup, - ); - - return $form; -} - - -/** - * Function to calculate the next stage. - * - * @param $form - * @param $form_state - * - * @return - * A string containing the stage name. - */ -function rawpheno_next_page($form, &$form_state) { - // Get the address/name of the next page based on the current stage. - switch($form_state['stage']) { - case 'check': - // In stage check, next is stage 03 or stage 02. - $btn_submit = $form_state['triggering_element']['#value']; - return ($btn_submit == 'Save spreadheet') ? 'save' : 'review'; - break; - - case 'review': - // In stage review, next is stage 03. - return 'save'; - break; - } -} + 1, 'review' => 2, 'save' => 3); + $current_step = (isset($form_stages[$form_state['stage']])) ? $form_stages[$form_state['stage']] : 1; + + // Array of stage indicators. + $stages = array(1 => '1. Validate Spreadsheet', + 2 => '2. Describe New Trait', + 3 => '3. Save Spreadsheet'); + + $markup = ''; + foreach($stages as $k => $v) { + $class = ($k <= $current_step) ? '' : ' progress-stage-todo'; + $markup .= '
+  ' . $v . '  +
'; + } + + // Add header to each stage with corresponding + // stage information defined above. + $form['header_upload'] = array( + '#type' => 'markup', + '#markup' => $markup, + ); + + return $form; +} + + +/** + * Function to calculate the next stage. + * + * @param $form + * @param $form_state + * + * @return + * A string containing the stage name. + */ +function rawpheno_next_page($form, &$form_state) { + // Get the address/name of the next page based on the current stage. + switch($form_state['stage']) { + case 'check': + // In stage check, next is stage 03 or stage 02. + $btn_submit = $form_state['triggering_element']['#value']; + return ($btn_submit == 'Save spreadheet') ? 'save' : 'review'; + break; + + case 'review': + // In stage review, next is stage 03. + return 'save'; + break; + } +} diff --git a/include/rawpheno.validation.inc b/includes/rawpheno.validation.inc similarity index 100% rename from include/rawpheno.validation.inc rename to includes/rawpheno.validation.inc diff --git a/rawpheno.install b/rawpheno.install index 7fe9d26..81a245f 100755 --- a/rawpheno.install +++ b/rawpheno.install @@ -283,7 +283,7 @@ function rawpheno_schema() { } // Include function to manage column headers, cv terms and variable names. -module_load_include('inc', 'rawpheno', 'include/rawpheno.function.measurements'); +module_load_include('inc', 'rawpheno', 'includes/rawpheno.function.measurements'); /** * Function to manage terms used by this module. diff --git a/rawpheno.module b/rawpheno.module index c85d488..aaf2beb 100755 --- a/rawpheno.module +++ b/rawpheno.module @@ -12,13 +12,15 @@ */ // Include function to manage column headers. -module_load_include('inc', 'rawpheno', 'include/rawpheno.function.measurements'); +module_load_include('inc', 'rawpheno', 'includes/rawpheno.function.measurements'); // Include functions required in processing spreadsheet file. -module_load_include('inc', 'rawpheno', 'include/rawpheno.upload.excel'); -module_load_include('inc', 'rawpheno', 'include/rawpheno.validation'); -module_load_include('inc', 'rawpheno', 'include/rawpheno.upload.form'); -module_load_include('inc', 'rawpheno', 'include/rawpheno.tripaldownload'); +module_load_include('inc', 'rawpheno', 'includes/rawpheno.upload.excel'); +module_load_include('inc', 'rawpheno', 'includes/rawpheno.validation'); +module_load_include('inc', 'rawpheno', 'includes/rawpheno.upload.form'); +module_load_include('inc', 'rawpheno', 'includes/rawpheno.tripaldownload'); +// Prepare term used by Germplasm Raw Phenotypes Field. +module_load_include('inc', 'rawpheno', 'includes/TripalFields/rawpheno.fields'); /** * Implements hook_menu(). @@ -37,7 +39,7 @@ function rawpheno_menu() { 'page callback' => 'drupal_get_form', 'page arguments' => array('rawpheno_rawdata'), 'access arguments' => array('access rawpheno'), - 'file' => 'include/rawpheno.rawdata.form.inc', + 'file' => 'includes/rawpheno.rawdata.form.inc', 'type' => MENU_NORMAL_ITEM, ); // Menu callback which generates the summary data in JSON, @@ -74,7 +76,7 @@ function rawpheno_menu() { 'page callback' => 'drupal_get_form', 'page arguments' => array('rawpheno_instructions'), 'access arguments' => array('access rawpheno'), - 'file' => 'include/rawpheno.instructions.form.inc', + 'file' => 'includes/rawpheno.instructions.form.inc', 'type' => MENU_NORMAL_ITEM, 'weight' => 0, ); @@ -84,7 +86,7 @@ function rawpheno_menu() { 'page callback' => 'drupal_get_form', 'page arguments' => array('rawpheno_instructions', 3), 'access arguments' => array('access rawpheno'), - 'file' => 'include/rawpheno.instructions.form.inc', + 'file' => 'includes/rawpheno.instructions.form.inc', 'type' => MENU_CALLBACK, ); // Menu callback which generates a list of headers in JSON, @@ -101,7 +103,7 @@ function rawpheno_menu() { 'page callback' => 'rawpheno_instructions_create_spreadsheet', 'page arguments' => array(4), 'access arguments' => array('access rawpheno'), - 'file' => 'include/rawpheno.instructions.form.inc', + 'file' => 'includes/rawpheno.instructions.form.inc', 'type' => MENU_CALLBACK, ); @@ -114,7 +116,7 @@ function rawpheno_menu() { 'page callback' => 'drupal_get_form', 'page arguments' => array('rawpheno_download'), 'access arguments' => array('download rawpheno'), - 'file' => 'include/rawpheno.download.form.inc', + 'file' => 'includes/rawpheno.download.form.inc', 'type' => MENU_NORMAL_ITEM, 'weight' => 6, ); @@ -134,7 +136,7 @@ function rawpheno_menu() { 'page callback' => 'drupal_get_form', 'page arguments' => array('rawpheno_upload_form_master'), 'access arguments' => array('access rawpheno'), - 'file' => 'include/rawpheno.upload.form.inc', + 'file' => 'includes/rawpheno.upload.form.inc', 'type' => MENU_NORMAL_ITEM, 'weight' => 3, ); @@ -154,7 +156,7 @@ function rawpheno_menu() { 'page callback' => 'drupal_get_form', 'page arguments' => array('rawpheno_backup'), 'access arguments' => array('access rawpheno'), - 'file' => 'include/rawpheno.backup.form.inc', + 'file' => 'includes/rawpheno.backup.form.inc', 'type' => MENU_NORMAL_ITEM, 'weight' => 7, ); @@ -164,7 +166,7 @@ function rawpheno_menu() { 'page callback' => 'drupal_get_form', 'page arguments' => array('rawpheno_backup', 3, 4, 5), 'access arguments' => array('access rawpheno'), - 'file' => 'include/rawpheno.backup.form.inc', + 'file' => 'includes/rawpheno.backup.form.inc', 'type' => MENU_CALLBACK, ); @@ -176,7 +178,7 @@ function rawpheno_menu() { 'page callback' => 'drupal_get_form', 'page arguments' => array('rawpheno_admin_main_page'), 'access arguments' => array('access administration pages'), - 'file' => 'include/rawpheno.admin.form.inc', + 'file' => 'includes/rawpheno.admin.form.inc', 'type' => MENU_NORMAL_ITEM, ); @@ -186,7 +188,7 @@ function rawpheno_menu() { 'page callback' => 'drupal_get_form', 'page arguments' => array('rawpheno_admin_page'), 'access arguments' => array('access administration pages'), - 'file' => 'include/rawpheno.admin.form.inc', + 'file' => 'includes/rawpheno.admin.form.inc', 'type' => MENU_LOCAL_TASK, 'weight' => 1, ); @@ -198,7 +200,7 @@ function rawpheno_menu() { 'page callback' => 'drupal_get_form', 'page arguments' => array('rawpheno_admin_rheaders'), 'access arguments' => array('access administration pages'), - 'file' => 'include/rawpheno.admin.form.inc', + 'file' => 'includes/rawpheno.admin.form.inc', 'type' => MENU_LOCAL_TASK, 'weight' => 2, ); @@ -211,7 +213,7 @@ function rawpheno_menu() { 'page callback' => 'drupal_get_form', 'page arguments' => array('rawpheno_admin_all_projects'), 'access arguments' => array('access administration pages'), - 'file' => 'include/rawpheno.admin.form.inc', + 'file' => 'includes/rawpheno.admin.form.inc', 'type' => MENU_LOCAL_TASK, 'weight' => 3, ); @@ -220,7 +222,7 @@ function rawpheno_menu() { 'page callback' => 'drupal_get_form', 'page arguments' => array('rawpheno_admin_project_management', 5, 6, 7), 'access arguments' => array('access administration pages'), - 'file' => 'include/rawpheno.admin.form.inc', + 'file' => 'includes/rawpheno.admin.form.inc', 'type' => MENU_CALLBACK, ); @@ -485,6 +487,13 @@ function rawpheno_theme($existing, $type, $theme, $path) { ), ); + // Raw data field. + $items['rawpheno_germplasm_field'] = array( + 'template' => 'rawpheno_germplasm_field', + 'path' => $path . '/theme', + 'variables' => array('raw_data' => '') + ); + return $items; } @@ -1176,4 +1185,4 @@ function rawpheno_preprocess_block(&$vars) { } return $vars; -} +} \ No newline at end of file diff --git a/theme/css/rawpheno.germplasmfield.style.css b/theme/css/rawpheno.germplasmfield.style.css new file mode 100644 index 0000000..a0491a5 --- /dev/null +++ b/theme/css/rawpheno.germplasmfield.style.css @@ -0,0 +1,236 @@ +/** + * @file + * Style rawphenotypes in Germplasm field. + */ + +#rawphenotypes-germplasm-raw-phenotypes-field img { + margin: 0 !important; +} + +#rawphenotypes-germplasm-table-wrapper { + -moz-box-shadow: inset 0 -10px 20px -10px #EAEAEA; + -webkit-box-shadow: inset 0 -10px 20px -10px #EAEAEA; + box-shadow: inset 0 -10px 20px -10px #EAEAEA; + z-index: 1000; +} + +#rawphenotypes-germplasm-field-header { + position: relative; + height: 45px; +} + +#rawphenotypes-germplasm-field-header div:first-child { + float: left; + height: inherit; +} + +#rawphenotypes-germplasm-field-header div:nth-child(2) { + float: right; + height: inherit; +} + +#rawphenotypes-germplasm-field-header div:nth-child(2) div { + background-color: #314355; + height: 40px; + width: 270px; + text-align:center; + line-height: 40px; +} + +.rawphenotypes-germplasm-nav a, +.rawphenotypes-germplasm-nav a:link, +.rawphenotypes-germplasm-nav a:active, +.rawphenotypes-germplasm-nav a:visited { + font-family: "Lucida Grande", "Lucida Sans Unicode", Verdana, sans-serif; + color: #FFFFFF; + display: inline-block; + white-space: nowrap; + font-size: 0.9em; + text-decoration: none; +} + +#rawphenotypes-germplasm-field-header div:nth-child(2) div:hover { + color: #FFFFFF; + text-decoration: none; + + -moz-box-shadow: 1px 1px 5px 1px #CCCCCC; + -webkit-box-shadow: 1px 1px 5px 1px #CCCCCC; + box-shadow: 1px 1px 5px 1px #CCCCCC; +} + +#rawphenotypes-define-raw-container { + clear: both; +} + +#rawphenotypes-germplasm-field-header div:last-child { + clear: both; + height: 1px; +} + +#rawphenotypes-germplasm-raw-phenotypes-field h1 span { + color: #3A7F21; +} + +#rawphenotypes-germplasm-field-table { + padding: 0; + margin: 0 !important; +} + +#rawphenotypes-germplasm-field-table td select { + min-width: 250px; + width: 300px; +} + +#rawphenotypes-germplasm-field-table td { font-size: 1.2em; } +#rawphenotypes-germplasm-field-header img, +#rawphenotypes-germplasm-field-table th img, +#rawphenotypes-germplasm-field-table td img { + border: none !important +} + +#rawphenotypes-germplasm-field-table th:last-child, +#rawphenotypes-germplasm-field-table td:last-child { + text-align: center; + width: 10px; +} + +#rawphenotypes-germplasm-field-table td:last-child img { + opacity: 0.5; +} + +#rawphenotypes-germplasm-field-table th:first-child, +#rawphenotypes-germplasm-field-table td:first-child { + text-align: center; + width: 35px; +} + +#rawphenotypes-germplasm-field-table td:nth-child(2) { + width: 100%; +} + +#rawphenotypes-germplasm-export-table { + position: relative; + padding: 0; + margin: 0; + min-width: 450px; + height: 238px; + overflow-y: scroll; + border: none; +} + +#rawphenotypes-germplasm-export-table tr:nth-child(2n+2) { + background: #efefef; + background: rgba(0, 0, 0, 0.063); +} + +#rawphenotypes-germplasm-export-table tbody tr { + cursor: pointer; +} + +#rawphenotypes-germplasm-export-table tbody tr:hover { + background-color: rgba(0, 0, 0, 0.1); + text-decoration: underline; + + -webkit-transition: background-color 0.9s ease-out; + -moz-transition: background-color 0.9s ease-out; + -o-transition: background-color 0.9s ease-out; + transition: background-color 0.9s ease-out; + + border-left: 2px solid #314355; +} + +#rawphenotypes-germplasm-raw-phenotypes-field small { + float: right; + margin: 10px 0 0 0; +} + +#rawphenotypes-define-raw-container { + border-bottom: 1px solid #CCCCCC; + cursor: pointer; + margin: 20px 0; + padding: 0 0 15px 0; + text-align: center; + + -moz-box-shadow: 0 5px 7px -9px #333333; + -webkit-box-shadow: 0 5px 7px -9px #333333; + box-shadow: 0 5px 7px -9px #333333; +} + +#rawphenotypes-germplasm-warning { + margin: 8px 0; +} + +#rawphenotypes-germplasm-controls { + border-bottom-width: 2px; + border-bottom-style: solid; + border-bottom-color: #FFFFFF; + margin: 7px 0 0 0; + padding-bottom: 10px; + position: relative; +} + +#rawphenotypes-germplasm-controls span:first-child a { + padding-left: 17px; + padding-right: 3px; + background: url(../../includes/TripalFields/ncit__raw_data/theme/search.png) no-repeat left center; + background-size: 13px 16px; +} + +#rawphenotypes-germplasm-controls-search-window { + display: none; + background-color: #EAEAEA; + height: 60px; + margin-top: 10px; + padding-top: 10px; + padding-left: 20px; + padding-bottom: 5px; + position: absolute; + left: 45px; + width: 160px; + z-index: 1; + + -moz-box-shadow: 1px 1px 5px 1px #CCCCCC; + -webkit-box-shadow: 1px 1px 5px 1px #CCCCCC; + box-shadow: outset 1px 1px 5px 1px #CCCCCC; +} + +#rawphenotypes-germplasm-controls-search-window::after { + content: " "; + position: absolute; + bottom: 100%; /* At the top of the tooltip */ + left: 50%; + margin-left: -10px; + border-width: 10px; + border-style: solid; + border-color: transparent transparent #EAEAEA transparent; +} + +#rawphenotypes-germplasm-controls-search-window input { + display: block; + font-weight: 300; + font-size: 0.7em; + padding: 2px !important; + margin: 10px 0 5px 0 !important; + width: 80%; +} + +#rawphenotypes-germplasm-controls-search-window a { + font-size: 0.9em; + font-family: Arial, Helvetica, sans-serif; + font-weight: 300; +} + +.ui-autocomplete { + font-family:Arial, Helvetica, sans-serif !important; + font-size: 0.8em !important; +} + +#rawphenotypes-germplasm-controls-selectby { + float: right; +} + +#rawphenotypes-germplasm-controls-selectby span { + background-color: #314355; + color: #FFFFFF; + padding: 1px 3px; +} \ No newline at end of file diff --git a/theme/img/fields/icon-raw.jpg b/theme/img/fields/icon-raw.jpg new file mode 100644 index 0000000..66b3cec Binary files /dev/null and b/theme/img/fields/icon-raw.jpg differ diff --git a/theme/js/rawpheno.germplasmfield.script.js b/theme/js/rawpheno.germplasmfield.script.js new file mode 100644 index 0000000..975e008 --- /dev/null +++ b/theme/js/rawpheno.germplasmfield.script.js @@ -0,0 +1,285 @@ +/** + * Rawphenotypes in Germplasm Field. + */ +(function ($) { + Drupal.behaviors.rawphenotypesGermplasm = { + attach: function (context, settings) { + // Raw phenotype definition. + $('#rawphenotypes-germplasm-field-header div').eq(0).find('a').click(function(e) { + e.preventDefault(); + $('#rawphenotypes-define-raw-container').slideDown(200); + }); + + // Okay - definition. + if ($('#rawphenotypes-define-raw-container')) { + $('#rawphenotypes-define-raw-container a').click(function(e) { + e.preventDefault(); + $('#rawphenotypes-define-raw-container').slideUp(200); + }); + } + + // Default select fields to Location + Experiment filter. + selectOptions('le'); + var imgOpacity = 0.5; + + // Select box event - prepare link to download selection. + var selects = $('#rawphenotypes-germplasm-field-table select'); + var downloadLink = ''; + + selects.change(function(e) { + var selectValue = e.target.value; + var selectId = e.target.id; + downloadLink = ''; + + // Reset other select to allow only one select field + // at a time to export. + selectReset(selectId); + + if (selectValue == '0') { + // None selected - default to select an option. + imgOpacity = '0.5'; + // Detach event created. + } + else { + // Selected, prepare query string for export. + imgOpacity = '1'; + var params = selectValue.split('#'); + // Project id & location & trait. + downloadLink = $.param({t:params[0], p:params[1], l:params[2], g:Drupal.settings.rawpheno.germ}); + } + + $('#' + selectId + '-img').css('opacity', imgOpacity); + }); + + // Listen to images clicked to launch data download. + $('#rawphenotypes-germplasm-field-table td:last-child img').click(function(e) { + var imgOpacity = $(this).css('opacity'); + + if (imgOpacity == 1) { + window.open( + Drupal.settings.rawpheno.exportLink + '?code=' + btoa(downloadLink), + '_blank' + ); + } + }); + + // Image to download ALL for a trait. + $('#rawphenotypes-germplasm-field-table td:first-child img').click(function(e) { + var imgId = e.target.id; + var i = imgId.split('-'); + + // Trait data. + var key = Object.keys(Drupal.settings.rawpheno.germRawdata).filter(function(k) { + return k.indexOf(i[4]) !== -1; + }); + + // All experiment user has access to in a trait. + var row = Drupal.settings.rawpheno.germRawdata[ key ] + .filter(function(e) { return e['phenotype_customfield_terms:user_experiment'] == 1 }); + + // Experiments. + var exp = row.map(e => e['phenotype_customfield_terms:id'].toString()) + .filter((value, index, self) => self.indexOf(value) === index); + + // Locations. + var loc = row.map(e => e['phenotype_customfield_terms:location']) + .filter((value, index, self) => self.indexOf(value) === index); + + if (exp.length > 0) { + // Export all. + downloadLink = $.param({t:i[4], p:exp.join('+'), l:loc.join('+'), g:Drupal.settings.rawpheno.germ}); + + window.open( + Drupal.settings.rawpheno.exportLink + '?code=' + btoa(downloadLink), + '_blank' + ); + } + else { + // None of the experiments in a trait is accessible to current user. + alert('You do not have access to experiments in this trait. Please contact us to request permission.'); + } + }); + + // Listen to controls search, expand and select by. + var tableWindow = $('#rawphenotypes-germplasm-export-table'); + // Create border when scrolling. + tableWindow.scroll(function() { + var c = ($(this).scrollTop() <= 0) ? '#FFFFFF' : '#314355'; + $('#rawphenotypes-germplasm-controls').css('border-bottom-color', c); + }); + + // Expand. + $('#rawphenotypes-germplasm-controls-expand').click(function(){ + var h = ($(this).is(':checked')) ? 460 : 230; + tableWindow.css('height', h); + }); + + // Search. + $('#rawphenotypes-germplasm-controls-search').click(function(e) { + e.preventDefault(); + + $('#rawphenotypes-germplasm-controls-search-window') + .fadeIn('fast') + .find('input').val('').focus(); + + tableWindow.scrollTop(-1); + }); + + // Close search window. + $('#rawphenotypes-germplasm-controls-search-window a').click(function(e) { + e.preventDefault(); + + $(this) + .parent().fadeOut('fast') + .find('input').val(''); + }); + + // Search field when selected/on focus. + $('#rawphenotypes-germplasm-controls-search-window input') + .click(function() { + if ($(this).val()) { + $(this).select(); + } + }); + + // Prepare search items (all traits currently displayed in the table). + var searchTerms = new Array(); + $('#rawphenotypes-germplasm-export-table table td:nth-child(2)').each(function() { + searchTerms.push($(this).text()); + }); + + $('#rawphenotypes-germplasm-controls-search-window input') + .autocomplete({ + select: function(event, ui) { + var foundIndex = searchTerms.indexOf(ui.item.value.trim()); + var foundRow = $('#rawphenotypes-germplasm-export-table table tbody tr').eq(foundIndex); + tableWindow.scrollTop(foundRow.position().top); + + var t = 0; + var timer = setInterval(function() { + if (t < 5) { + var o = (t%2 == 0) ? 0 : 1; + foundRow.css('opacity', o); + } + else { + foundRow.css('opacity', 1); + clearInterval(timer); + } + + t++; + }, 250); + + $(this).parent().fadeOut(); + }, + source: function(request, response) { + var results = $.ui.autocomplete.filter(searchTerms, request.term); + // Fist 5 results. + response(results.slice(0, 5)); + } + }); + + // Select by type. + $('#rawphenotypes-germplasm-controls-selectby a').click(function(e) { + e.preventDefault(); + + var selByStates = ['Experiment', 'Location + Experiment']; + + // If link is experiment - switch it to location + experiment + // and vice versa. + var selByText = ($(this).text() == 'Experiment') ? 1 : 0; + var selByCur = (selByText) ? 0 : 1; + + $(this).text(selByStates[ selByText ]); + $(this).parent().find('span').text(selByStates[ selByCur ]); + + // Match selet field. + selectReset(); + var sel = (selByCur) ? 'le' : 'e'; + selectOptions(sel); + }); + + /** + * Reset select boxes and export link. + * + * @param selectId + * Trait id number used to reference a select field + * If this is specified, exclude this select from reset operation. + */ + function selectReset(selectId = -1) { + var selects = $('#rawphenotypes-germplasm-field-table select'); + + selects.each(function() { + if ($(this).attr('id') != selectId) { + $('#' + $(this).attr('id') + '-img').css('opacity', '0.5'); + this.selectedIndex = 0; + } + }); + } + + /** + * Remove option element from a select field. + * + * @param select + * Object, reference to select element. + */ + function removeOptions(select) { + select.find('option').each(function(i, v) { + if (i > 0) $(this).remove(); + }); + } + + /** + * Create select options + * + * @param set + * String, indicate if options is for location+experiment or experiment. + * Default to le = location + experiment. + */ + function selectOptions(set = 'le') { + var dataset = Drupal.settings.rawpheno; + var germplasm = dataset.germ; + + // Prepare dataset. + if (set == 'le') { + // LOCATION + EXPERIMENT: + $.each(dataset.germRawdata, function(index, value){ + var trait = index.split('_'); + var element = $('#rawphenotypes-germplasm-field-filterby-' + trait[1]); + removeOptions(element); + $.each(value, function(i, v) { + var disabled = v['phenotype_customfield_terms:user_experiment'] == 1 ? '' : 'disabled'; + element.append($('