diff --git a/includes/TripalFields/analyzedphenotypes.fields.inc b/includes/TripalFields/analyzedphenotypes.fields.inc index aecaef9..def29ec 100644 --- a/includes/TripalFields/analyzedphenotypes.fields.inc +++ b/includes/TripalFields/analyzedphenotypes.fields.inc @@ -155,6 +155,25 @@ function analyzedphenotypes_bundle_fields_info($entity_type, $bundle) { ), ); + // Phenotype Ontology. + tripal_insert_cvterm(array( + 'id' => 'NCIT:C52469', + 'name' => 'Synonym', + 'cv_name' => 'NCIT', + 'definition' => 'A word having the same or nearly the same meaning as another word or other words in a language; a word or an expression that serves as a figurative or symbolic substitute for another', + )); + $field_name = 'ncit__synonym'; + $field_type = 'ncit__synonym'; + $fields[$field_name] = array( + 'field_name' => $field_name, + 'type' => $field_type, + 'cardinality' => 1, + 'locked' => FALSE, + 'storage' => array( + 'type' => 'field_chado_storage', + ), + ); + // Phenotype Image tripal_insert_cvterm(array( 'id' => 'NCIT:C48179', @@ -503,6 +522,38 @@ function analyzedphenotypes_bundle_instances_info($entity_type, $bundle) { ), ); + // Phenotype Ontology - Synonym. + $field_name = 'ncit__synonym'; + $field_type = 'ncit__synonym'; + $instances[$field_name] = array( + 'field_name' => $field_name, + 'entity_type' => $entity_type, + 'bundle' => $bundle->name, + 'label' => 'Phenotype Ontology', + 'description' => 'Phenotype Ontology.', + 'required' => FALSE, + 'settings' => array( + 'term_vocabulary' => 'NCIT', + 'term_name' => 'Synonym', + 'term_accession' => 'c52469', + 'auto_attach' => FALSE, + 'chado_table' => $bundle->data_table, + 'chado_column' => $bundle->data_table . '_id', + 'base_table' => $bundle->data_table, + ), + 'widget' => array( + 'type' => 'ncit__synonym_widget', + 'settings' => array(), + ), + 'display' => array( + 'default' => array( + 'label' => 'hidden', + 'type' => 'ncit__synonym_formatter', + 'settings' => array(), + ), + ), + ); + // Phenotype Image. $field_name = 'ncit__image'; $field_type = 'ncit__image'; diff --git a/includes/TripalFields/ncit__synonym/ncit__synonym.inc b/includes/TripalFields/ncit__synonym/ncit__synonym.inc new file mode 100644 index 0000000..fd5580d --- /dev/null +++ b/includes/TripalFields/ncit__synonym/ncit__synonym.inc @@ -0,0 +1,220 @@ + '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' => 'Synonym', + // The unique ID (i.e. accession) of the term. + 'term_accession' => 'c52469', + // 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' => '', + + 'chado_table' => 'cvterm_relationship', + 'chado_column' => 'cvterm_relationship_id', + 'base_table' => 'cvterm_relationship' + + ); + + // 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) { + $record = $entity->chado_record_id; + + if ($record) { + // We need the term configuration for term Related used when creating relationship. + $sysvar = ap_get_variablenames( + array('variablename' => 'related'), + array('set' => 'terms') + ); + $sysvar_related = variable_get($sysvar); + + if ($sysvar_related) { + // Inspect to see if this trait has crop ontology set in chado.cvterm_relationship + // using what is known - type_id of ontology Related (in configuration) and the object_id + // which is the current trait. + + $identifiers = array( + 'column' => array('cvterm_relationship_id', 'subject_id'), + 'value' => array('type_id' => $sysvar_related, 'object_id' => $record) + ); + $rel = chado_select_record('cvterm_relationship', $identifiers['column'], $identifiers['value']); + + $field_name = $this->field['field_name']; + $field_table = 'chado-cvterm_relationship__'; + + if ($rel) { + // Has crop ontology. + + // For chado mechnanism. + $entity->{$field_name}['und'][0] = array( + // Row id #. + $field_table . 'cvterm_relationship_id' => $rel[0]->cvterm_relationship_id, + // Relationship details. + $field_table . 'type_id' => $sysvar_related, + $field_table . 'object_id' => $entity->chado_record_id, + $field_table . 'subject_id' => $rel[0]->subject_id + ); + + // For end user. + // Sending the term, accession and cv name. Last two items are used to constuct + // direct link to vocabulary lookup to a more details about a term. + $termprop = chado_generate_var('cvterm', array( + 'cvterm_id' => $rel[0]->subject_id + )); + + // Term. + $relationship_term = tripal_get_chado_semweb_term('cvterm_relationship', 'subject_id'); + $entity->{$field_name}['und'][0]['value'][ $relationship_term ] = $termprop->name; + + // Accession. + $accession = tripal_get_chado_semweb_term('dbxref', 'accession'); + $entity->{$field_name}['und'][0]['value'][ $accession ] = $termprop->dbxref_id->accession; + + // CV. + $cv_term = tripal_get_chado_semweb_term('cv', 'name'); + $entity->{$field_name}['und'][0]['value'][ $cv_term ] = $termprop->cv_id->name; + } + else { + // Crop ontology not set. + // Nothing here, just forward what we know so far, Related term and the trait id. + $entity->{$field_name}['und'][0] = array( + 'value' => array(), + $field_table . 'object_id' => $entity->chado_record_id, + $field_table . 'type_id' => $sysvar_related, + ); + } + } + } + } + + /** + * 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; + } +} diff --git a/includes/TripalFields/ncit__synonym/ncit__synonym_formatter.inc b/includes/TripalFields/ncit__synonym/ncit__synonym_formatter.inc new file mode 100644 index 0000000..50b6bb7 --- /dev/null +++ b/includes/TripalFields/ncit__synonym/ncit__synonym_formatter.inc @@ -0,0 +1,77 @@ + array('target' => '_blank'))); + + $element[0] = array( + '#type' => 'markup', + '#markup' => '
' . $term . '
' . $cvterm_lookup, + ); + } + + return $element; + } +} diff --git a/includes/TripalFields/ncit__synonym/ncit__synonym_widget.inc b/includes/TripalFields/ncit__synonym/ncit__synonym_widget.inc new file mode 100644 index 0000000..946ff97 --- /dev/null +++ b/includes/TripalFields/ncit__synonym/ncit__synonym_widget.inc @@ -0,0 +1,329 @@ +field['field_name']; + + // Elements of chado.cvterm_relationship table: + $chado_table = 'chado-cvterm_relationship__'; + + + // FOR CHADO MECHNANISM: + // Column - cvterm_relationship_id primary key. + $widget['value'] = array( + '#type' => 'value', + '#value' => $items[$delta][ $chado_table . 'cvterm_relationship_id' ] ?? '', + ); + + // Column - cvterm_relationship_id primary key. + $widget[ $chado_table . 'cvterm_relationship_id' ] = array( + '#type' => 'value', + '#value' => $items[$delta][ $chado_table . 'cvterm_relationship_id' ] ?? '', + ); + + // Column - object_id, the trait id (cvterm_id) # the crop ontology term maps to. + // Has value even when trait has no crop ontology. + $widget[ $chado_table . 'object_id' ] = array( + '#type' => 'value', + '#value' => $items[$delta][ $chado_table . 'object_id' ], + ); + + // Column - subject_id, the crop ontology id (cvterm_id) that maps to a trait. + $widget[ $chado_table . 'subject_id' ] = array( + '#type' => 'value', + '#value' => $items[$delta][ $chado_table . 'subject_id' ] ?? '', + ); + + // Column - type_id, refers to ontology term Related in AP configuration. + // Has value even when trait has no crop ontology. + $widget[ $chado_table . 'type_id' ] = array( + '#type' => 'value', + '#value' => $items[$delta][ $chado_table . 'type_id' ], + ); + + + // CURRENT SETTINGS: + // Style information. + drupal_add_css(drupal_get_path('module', 'analyzedphenotypes') . '/includes/TripalFields/ncit__synonym/theme/style_cp_synonym_field.css'); + + // Current crop ontology. + if (count($items[0]['value']) > 0) { + @list($term, $accession, $cv) = array_values($items[0]['value']); + // Refer to this ID for CSS styling. + $id = 'ap-field-crop_ontology_synonym'; + + $crop_ontology = '
' . $term . '
'; + $cvterm_lookup = l('Vocabulary Details', 'cv/lookup/' . $cv . '/' . $accession, array('attributes' => array('target' => '_blank'))) . ' | '; + } + else { + $crop_ontology = 'Crop ontology not set | '; + $cvterm_lookup = ''; + } + + // FIELDSET - Main fieldset - search ontology terms. + $widget['ap_fieldset'] = array( + '#type' => 'fieldset', + '#title' => t('Crop Ontology'), + ); + + // MARKUP - Show current record if any. + $widget['ap_fieldset']['ap_ontology'] = array( + '#type' => 'markup', + '#markup' => $crop_ontology . $cvterm_lookup . l('Update term', '#', array('attributes' => array('class' => 'ap-link-form-revel'))), + ); + + + // FORM ELEMENTS: + // @credits to Tripal developer - sbo relationship. + $vocs = []; + $vocs = chado_get_cv_select_options(); + $cv_id = isset($form_state['values'][$field_name][$langcode][$delta]['ap_fieldset']['ap_ajax_response']['ap_source_cv']) + ? $form_state['values'][$field_name][$langcode][$delta]['ap_fieldset']['ap_ajax_response']['ap_source_cv'] : 0; + + // ITEM - Describe both CV field and CV Term field. + $widget['ap_fieldset']['ap_describe'] = array( + '#type' => 'item', + '#title' => t('CV and Term'), + '#description' => t('Select CV and Term to change the ontology term for this trait.'), + // + // Wrap. + '#prefix' => '
', + ); + + // MARKUP - AJAX response wrapper. Update CV id filter in autocomplete field below. + $widget['ap_fieldset']['ap_ajax_response'] = array( + '#prefix' => '
', + '#suffix' => '
', + ); + + // FIELD SELECT - Select CV. + $widget['ap_fieldset']['ap_ajax_response']['ap_source_cv'] = array( + '#type' => 'select', + '#options' => $vocs, + '#theme_wrappers' => array(), + '#ajax' => array( + 'callback' => 'analyzedphentypes_synonym_callback', + 'wrapper' => 'ap-ajax-response', + 'method' => 'replace', + ), + ); + + // FIELD AUTOCOMPLETE - Search term in a CV. + $widget['ap_fieldset']['ap_ajax_response']['ap_ontology_term'] = array( + '#type' => 'textfield', + '#size' => 60, + '#autocomplete_path' => 'admin/tripal/storage/chado/auto_name/cvterm/' . $cv_id, + '#disabled' => TRUE, + '#theme_wrappers' => array(), + '#attributes' => array('style' => 'margin: 0 10px;'), + // + // Wrap. + '#suffix' => '
', + ); + + if (isset($cv_id)) { + // Enable field when a cv selected. + $widget['ap_fieldset']['ap_ajax_response']['ap_ontology_term']['#disabled'] = FALSE; + } + + // Listen to link to update ontology term. + drupal_add_js("jQuery('.ap-link-form-revel').click(function(e) { + e.preventDefault(); + jQuery('#ap-crop-ontology-form-fields').fadeIn(); + })", array('type' => 'inline', 'scope' => 'footer')); + } + else { + drupal_set_message('Crop ontology configuration can only be set to a single value. Please set the allowed value to one in mange field.', 'warning', FALSE); + } + } + + /** + * Performs validation of the widgetForm. + * + * Use this validate to ensure that form values are entered correctly. + * The 'value' key of this field must be set in the $form_state['values'] + * array anytime data is entered by the user. It may be the case that there + * are other fields for helping select a value. In the end those helper + * fields must be used to set the 'value' field. + */ + public function validate($element, $form, &$form_state, $langcode, $delta) { + $field_name = $this->field['field_name']; + + if (isset($form_state['values'][$field_name][$langcode][$delta])) { + $form_values = $form_state['values'][$field_name][$langcode][$delta]; + + if (isset($form_values['ap_fieldset']['ap_ajax_response']['ap_source_cv']) + && isset($form_values['ap_fieldset']['ap_ajax_response']['ap_ontology_term'])) { + + $termprop = chado_generate_var('cvterm', array( + 'cv_id' => $form_values['ap_fieldset']['ap_ajax_response']['ap_source_cv'], + 'name' => $form_values['ap_fieldset']['ap_ajax_response']['ap_ontology_term'], + )); + + if ($termprop && $termprop->cvterm_id > 0) { + // Term is valid. + + // From this point on, set the chado_table settings to cvterm_relationship from cvterm. + $this->instance['settings']['chado_table'] = 'cvterm_relationship'; + $field_table = $this->instance['settings']['chado_table']; + + + // Create or Update? + + if (empty($form_values['value'])) { + // Create. + $form_state['values'][$field_name][$langcode][$delta]['chado-' . $field_table . '__object_id'] + = $form_state['values'][$field_name][$langcode][$delta]['chado-' . $field_table . '__object_id']; + + $form_state['values'][$field_name][$langcode][$delta]['chado-' . $field_table . '__type_id'] + = $form_state['values'][$field_name][$langcode][$delta]['chado-' . $field_table . '__type_id']; + + // As entered in the autocomplete field. + $form_state['values'][$field_name][$langcode][$delta]['chado-' . $field_table . '__subject_id'] = $termprop->cvterm_id; + } + else { + // Update. + // As entered in the autocomplete field. + $form_state['values'][$field_name][$langcode][$delta]['chado-' . $field_table . '__subject_id'] = $termprop->cvterm_id; + } + + // After create or update, restore chado_table setting to original value (cvterm). + $this->instance['settings']['chado_table'] = 'cvterm'; + } + } + } + } + + /** + * Performs extra commands when the entity form is submitted. + * + * Drupal typically does not provide a submit hook for fields. The + * TripalField provides one to allow for behind-the-scenes actions to + * occur. This function should never be used for updates, deletes or + * inserts for the Chado table associated with the field. Rather, the + * storage backend should be allowed to handle inserts, updates deletes. + * However, it is permissible to perform inserts, updates or deletions within + * Chado using this function. Those operations can be performed if needed but + * on other tables not directly associated with the field. + * + * An example is the chado.feature_synonym table. The chado_linker__synonym + * field allows the user to provide a brand new synonynm and it must add it + * to the chado.synonym table prior to the record in the + * chado.feature_synonym table. This insert occurs in the widgetFormSubmit + * function. + * + * @param $entity_type + * The type of $entity. + * @param $entity + * The entity for the operation. + * @param $field + * The field structure for the operation. + * @param $instance + * The instance structure for $field on $entity's bundle. + * @param $langcode + * The language associated with $items. + * @param $items + * $entity->{$field['field_name']}[$langcode], or an empty array if unset. + * @param $form + * The submitted form array. + * @param $form_state. + * The form state array. + */ + public function submit($form, &$form_state, $entity_type, $entity, $langcode, $delta) { + } +} + + +/** + * Function callback - AJAX response. + * @credits to Tripal developer - sbo relationship. + */ +function analyzedphentypes_synonym_callback($form, $form_state) { + // Get the triggering element + $form_element_name = $form_state['triggering_element']['#name']; + preg_match('/(.+?)\[(.+?)\]\[(.+?)\]/', $form_element_name, $matches); + $field = $matches[1]; + $lang = $matches[2]; + $delta = $matches[3]; + + // Return the widget that triggered the AJAX call, but only the ontology section. + if (isset($form[$field][$lang][$delta])) { + return $form[$field][$lang][$delta]['ap_fieldset']['ap_ajax_response']; + } +} diff --git a/includes/TripalFields/ncit__synonym/theme/.DS_Store b/includes/TripalFields/ncit__synonym/theme/.DS_Store new file mode 100644 index 0000000..b21d3a4 Binary files /dev/null and b/includes/TripalFields/ncit__synonym/theme/.DS_Store differ diff --git a/includes/TripalFields/ncit__synonym/theme/style_cp_synonym_field.css b/includes/TripalFields/ncit__synonym/theme/style_cp_synonym_field.css new file mode 100644 index 0000000..2cc13bc --- /dev/null +++ b/includes/TripalFields/ncit__synonym/theme/style_cp_synonym_field.css @@ -0,0 +1,28 @@ +/** + * @file + * Style NCIT synonym - crop ontology. + */ + +/* Term wrapper */ +#ap-field-crop_ontology_synonym { + background-color: #3A7F21; + color: #FFFFFF; + display: inline-block; + font-family: sans-serif; + font-weight: 300; + margin-right: 20px; + padding: 5px 7px; + + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + + letter-spacing: 1px; + word-spacing: 1px; +} + +/* Update ontology formset wrapper */ +#ap-crop-ontology-form-fields { + display: none; + margin: 15px 0 0 0; +}