'field_tripal_ws_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. 'searchable_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' => 'schema', // The name of the term. 'term_name' => 'Thing', // The unique ID (i.e. accession) of the term. 'term_accession' => 'property', // 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, // Settings to allow the site admin to set the remote data source info. 'data_info' => array( 'query' => '', 'remote_site' => '', 'description' => '', 'rd_field_name' => '', 'site_logo' => '', ), ); // 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 search field that appears on the page that redirects // the user but otherwise provides no data. public static $no_data = TRUE; // Holds an object describing the remote site that tihs field connects to. private $remote_site = NULL; // Set to TRUE if this field is being loaded via web services. WE don't // want remote fields loaded when a web-service call is made. private $loaded_via_ws = FALSE; public function __construct($field, $instance) { parent::__construct($field, $instance); // This field should not do anything if it is loaded via web-services. // We don't want remote content to be available in web services. There // is an if statement to not show this field in the web services but the // entity_load function doesn't know this field shouldn't be loaded so // we need to short-circuit that. if (preg_match('/web-services/', $_SERVER['REQUEST_URI'])) { $this->loaded_via_ws = TRUE; return; } // Get the site url from the tripal_sites table. if (array_key_exists('data_info', $instance['settings'])) { $site_id_ws = $instance['settings']['data_info']['remote_site']; if ($site_id_ws) { $this->remote_site = db_select('tripal_sites', 'ts') ->fields('ts') ->condition('ts.id', $site_id_ws) ->execute() ->fetchObject(); } } } /** * @see WebServicesField::load() */ public function load($entity) { // If this field is being loaded via web services then just return. if ($this->loaded_via_ws == TRUE) { return; } $field_name = $this->field['field_name']; $field_type = $this->field['type']; // Set some defaults for the empty record. $entity->{$field_name}['und'][0] = array( 'value' => '', 'remote_entity' => NULL, 'error' => FALSE, 'warning' => FALSE, 'admin_message' => '', 'query_str' => '', ); // Get the query set by the admin for this field and replace any tokens $query_str = $this->instance['settings']['data_info']['query']; $bundle = tripal_load_bundle_entity(array('name' => $entity->bundle)); $query_str = tripal_replace_entity_tokens($query_str, $entity, $bundle); // Make the request. $data = $this->makeRemoteRequest($query_str); if(!$data){ $entity->{$field_name}['und'][0]['value'] = 'ERROR: there was a problem retrieving content for this field.'; $entity->{$field_name}['und'][0]['admin_message'] = "The remote service returned no data."; $entity->{$field_name}['und'][0]['remote_entity'] = NULL; $entity->{$field_name}['und'][0]['error'] = TRUE; $entity->{$field_name}['und'][0]['warning'] = FALSE; $entity->{$field_name}['und'][0]['query_str'] = $this->buildRemoteURL($this->remote_site, $query_str); return; } // Make sure we didn't have a problem if (array_key_exists('error', $data)) { $entity->{$field_name}['und'][0]['value'] = 'ERROR: there was a problem retrieving content for this field.'; $entity->{$field_name}['und'][0]['admin_message'] = "The content is currently not available because the " . "remote service reported the following error: " . $data['error'] . "."; $entity->{$field_name}['und'][0]['remote_entity'] = NULL; $entity->{$field_name}['und'][0]['error'] = TRUE; $entity->{$field_name}['und'][0]['warning'] = FALSE; $entity->{$field_name}['und'][0]['query_str'] = $this->buildRemoteURL($this->remote_site, $query_str); return; } $num_items = count($data['member']); if ($num_items == 0) { $entity->{$field_name}['und'][0]['value'] = 'Content is unavailable on the remote service.'; $entity->{$field_name}['und'][0]['admin_message'] = "The query to the remote service returned an empty result set. If you " . "think this is an error, please check the query string and the remote service to verify. "; $entity->{$field_name}['und'][0]['warning'] = TRUE; $entity->{$field_name}['und'][0]['error'] = FALSE; $entity->{$field_name}['und'][0]['remote_entity'] = NULL; $entity->{$field_name}['und'][0]['query_str'] = $this->buildRemoteURL($this->remote_site, $query_str); return; } // Iterate through the members returned and save those for the field. for ($i = 0; $i < $num_items; $i++) { $member = $data['member'][$i]; // Get the cotent type and remote entity id $content_type = $member['@type']; $remote_entity_id = $member['@id']; $remote_entity_id = preg_replace('/^.*\/(\d+)/', '$1', $remote_entity_id); // Separate the query_field if it has subfields. $rd_field_name = $this->instance['settings']['data_info']['rd_field_name']; $subfields = explode(',', $rd_field_name); $query_field = $subfields[0]; // Next get the the details about this member. $query_field_url = $content_type . '/' . $remote_entity_id . '/' . $query_field; $field_data = $this->makeRemoteRequest($query_field_url); // If we encounter any type of error, we'll reset the field and return. if ($field_data && array_key_exists('error', $field_data)) { $entity->{$field_name} = []; $entity->{$field_name}['und'][0]['value'] = 'ERROR: there was a problem retrieving secific content for this field.'; $entity->{$field_name}['und'][0]['admin_message'] = "While iterating through the list of results, the " . "remote service reported the following error: " . $field_data['error'] . ". " ; $entity->{$field_name}['und'][0]['remote_entity'] = NULL; $entity->{$field_name}['und'][0]['error'] = TRUE; $entity->{$field_name}['und'][0]['warning'] = FALSE; $entity->{$field_name}['und'][0]['query_str'] = $this->buildRemoteURL($this->remote_site, $query_field_url); return; } // Set the field data as the value. $field_data_type = $field_data['@type']; $entity->{$field_name}['und'][$i]['value'] = $field_data; $entity->{$field_name}['und'][$i]['remote_entity'] = $member; $entity->{$field_name}['und'][$i]['error'] = FALSE; $entity->{$field_name}['und'][$i]['warning'] = FALSE; $entity->{$field_name}['und'][$i]['admin_message'] = ''; $entity->{$field_name}['und'][$i]['query_str'] = $this->buildRemoteURL($this->remote_site, $query_field_url);; } } /** * Used to build the full URL for the query. */ private function buildRemoteURL($remote_site, $query) { $path = $query; $q = ''; if (preg_match('/\?/', $query)) { list($path, $q) = explode('?', $query); } if(empty($remote_site)) { tripal_report_error('tripal_ws', TRIPAL_ERROR, 'Unable to find remote_site in remote__data field while attempting to build the remote URL.'); return null; } return tripal_build_remote_content_url($remote_site, $path, $q); } /** * Makes a request to a remote Tripal web services site. * * @param $query * The query string. This string is added to the URL for the remote * website. * @return array on success or null if request fails. */ private function makeRemoteRequest($query) { $path = $query; $q = ''; if (preg_match('/\?/', $query)) { list($path, $q) = explode('?', $query); } if(empty($this->remote_site)) { tripal_report_error('tripal_ws', TRIPAL_ERROR, 'Unable to find remote_site while attempting to make the request.'); return null; } try { $data = tripal_get_remote_content($this->remote_site->id, $path, $q); } catch (Exception $exception) { tripal_report_error('tripal_ws', TRIPAL_ERROR, $exception->getMessage()); return null; } return $data; } /** * * @see TripalField::settingsForm() */ public function instanceSettingsForm() { $element = parent::instanceSettingsForm(); // Get the setting for the option for how this widget. $instance = $this->instance; $settings = ''; $site_list = ''; $tokens = array(); // Get the form info from the bundle about to be saved. $bundle = tripal_load_bundle_entity(array('name' => $instance['bundle'])); // Retrieve all available tokens. $tokens = tripal_get_entity_tokens($bundle); $element['data_info'] = array( '#type' => 'fieldset', '#title' => 'Remote Data Settings', '#description' => 'These settings allow you to provide a Tripal web services query to identify content on another Tripal site and display that here within this field. You must specify the query to execute and the field to display.', '#collapsible' => TRUE, '#collapsed' => FALSE, '#prefix' => "