diff --git a/js/ting.js b/js/ting.js new file mode 100644 index 0000000..964e0a5 --- /dev/null +++ b/js/ting.js @@ -0,0 +1,31 @@ +/** + * @file + * Simple script to help display search overlay when links are clicked. + */ +(function($) { + "use strict"; + + $(document).ready(function() { + $('a.js-search-overlay').live('click', function() { + var link = $(this); + if (link.attr('href').charAt(0) !== '#') { + // Only show overlay for non-local links. + Drupal.TingSearchOverlay(); + } + }); + + // Ensure overlay on collection view links. + $('.ting-collection-wrapper a[href*="/ting/"]').live('click', function() { + if ($(this).not('[target="_blank"]').length) { + Drupal.TingSearchOverlay(); + } + }); + + // Ensure overlay on object view links. + $('.ting-object-wrapper a[href*="/ting/"]').live('click', function() { + if ($(this).not('[target="_blank"]').length) { + Drupal.TingSearchOverlay(); + } + }); + }); +}(jQuery)); diff --git a/plugins/content_types/ting_collection.inc b/plugins/content_types/ting_collection.inc index 3ec8abb..3be45cb 100644 --- a/plugins/content_types/ting_collection.inc +++ b/plugins/content_types/ting_collection.inc @@ -1,4 +1,8 @@ t('Ting collection'), @@ -10,18 +14,20 @@ $plugin = array( 'category' => t('Ting'), ); -function ting_ting_collection_content_type_edit_form($form, &$form_state) { - return $form; -} - +/** + * Implements hook_ID_content_type_render(). + */ function ting_collection_content_type_render($subtype, $conf, $args, $context) { $block = new stdClass(); $object = isset($context->data) ? ($context->data) : NULL; if ($object instanceOf TingCollection) { - if (sizeof($object->entities) < 2) { + if (count($object->entities) < 2) { drupal_goto('ting/object/' . $object->id); } + // Add search overlay trigger. + drupal_add_js(drupal_get_path('module', 'ting') . '/js/ting.js'); + $block->title = check_plain($object->title); $block->content = ting_collection_view($object); } @@ -29,3 +35,9 @@ function ting_collection_content_type_render($subtype, $conf, $args, $context) { return $block; } +/** + * Implements hook_ID_content_type_edit_form(). + */ +function ting_ting_collection_content_type_edit_form($form, &$form_state) { + return $form; +} diff --git a/plugins/content_types/ting_object.inc b/plugins/content_types/ting_object.inc index 5e4945f..2d4b0d2 100644 --- a/plugins/content_types/ting_object.inc +++ b/plugins/content_types/ting_object.inc @@ -1,4 +1,8 @@ t('Ting object'), @@ -10,17 +14,27 @@ $plugin = array( 'category' => t('Ting'), ); -function ting_ting_object_content_type_edit_form($form, &$form_state) { - return $form; -} - +/** + * Implements hook_ID_content_type_render(). + */ function ting_object_content_type_render($subtype, $conf, $args, $context) { $block = new stdClass(); $object = isset($context->data) ? ($context->data) : NULL; if ($object instanceOf TingEntity) { $block->title = check_plain($object->title); $block->content = ting_object_view($object); + + // Add search overlay trigger. + drupal_add_js(drupal_get_path('module', 'ting') . '/js/ting.js'); } return $block; } + +/** + * Implements hook_ID_content_type_edit_form(). + */ +function ting_ting_object_content_type_edit_form($form, &$form_state) { + return $form; +} + diff --git a/ting.admin.inc b/ting.admin.inc index f1446ee..41004b6 100644 --- a/ting.admin.inc +++ b/ting.admin.inc @@ -30,25 +30,17 @@ function ting_admin_ting_settings($form_state) { $form['ting']['ting_search_url'] = array( '#type' => 'textfield', '#title' => t('Search service URL'), - '#description' => t('URL to the Ting search webservice, e.g. http://opensearch.addi.dk/2.1/'), + '#description' => t('URL to the Ting search webservice, e.g. http://opensearch.addi.dk/3.0/'), '#required' => TRUE, '#default_value' => variable_get('ting_search_url', ''), ); - $form['ting']['ting_scan_url'] = array( - '#type' => 'textfield', - '#title' => t('Scan service URL'), - '#description' => t('URL to the Ting scan webservice, e.g. http://openscan.addi.dk/1.7/'), - '#required' => TRUE, - '#default_value' => variable_get('ting_scan_url', ''), - ); - - $form['ting']['ting_spell_url'] = array( - '#type' => 'textfield', - '#title' => t('Spell service URL'), - '#description' => t('URL to the Ting spell webservice, e.g. http://openspell.addi.dk/1.2/'), - '#required' => TRUE, - '#default_value' => variable_get('ting_spell_url', ''), + $form['ting']['ting_filter_by_local_holdings']= array( + '#type' => 'checkbox', + '#title' => t('Filter search queries on holdingsItem'), + '#description' => t('Filter searches by holdingsitem.agencyId. Only activate when opensearch uses data-well 3.5, and the provider is fbs'), + '#required' => FALSE, + '#default_value' => variable_get('ting_filter_by_local_holdings', 0), ); $form['ting']['ting_recommendation_url'] = array( @@ -78,10 +70,8 @@ function ting_admin_ting_settings($form_state) { 259200, 604800, ); - $options = array( - 0 => t('No caching'), - ); + $options = array(); foreach ($intervals as $interval) { $options[$interval] = format_interval($interval, 2); } @@ -130,7 +120,7 @@ function ting_admin_ranking_settings($form, &$form_state) { $form_state['ranking_field_count'] = count($field_data) + 1; } - // Wrapper, so that the AJAX callback have some place to put new elements + // Wrapper, so that the AJAX callback have some place to put new elements. $form['ting_ranking_fields'] = array( '#title' => t('Custom ranking fields'), '#type' => 'fieldset', @@ -164,6 +154,26 @@ function ting_admin_ranking_settings($form, &$form_state) { ), ); + + $ting_sort_value = variable_get('ting_sort_default', 'rank_frequency'); + $form['ting_sort_default'] = array( + '#title' => t('Default sort method (best match)'), + '#type' => 'fieldset', + '#tree' => TRUE, + ); + + $form['ting_sort_default']['fields']['sort_type'] = array( + '#title' => t('Type'), + '#type' => 'select', + '#options' => array( + 'rank_frequency' => t('Best match'), + 'rank_general' => t('General Rank'), + 'rank_none' => t('No rank'), + ), + '#default_value' => array('sort_type' => $ting_sort_value), + ); + + $form['buttons']['save'] = array( '#type' => 'submit', '#value' => t('Save changes'), @@ -202,8 +212,7 @@ function ting_admin_ranking_add_more_js($form, &$form_state) { */ function ting_admin_ranking_form_after_build($form, &$form_state) { $path = drupal_get_path('module', 'ting'); - - drupal_add_css($path .'/css/ting_admin_ranking_form.css'); + drupal_add_css($path . '/css/ting_admin_ranking_form.css'); return $form; } @@ -220,21 +229,26 @@ function ting_admin_ranking_settings_submit($form, &$form_state) { usort($fields, '_ting_ranking_field_sort'); variable_set('ting_ranking_fields', $fields); + + $default_sort = $form_state['values']['ting_sort_default']['fields']['sort_type']; + variable_set('ting_sort_default', $default_sort); + + drupal_set_message(t('Settings has been saved'), 'status'); } /** - * array_filter() callback to remove empty/deleted elements. + * Array_filter() callback to remove empty/deleted elements. */ function _ting_ranking_field_filter($element) { return !empty($element['field_name']); } /** - * usort() callback to remove empty/deleted elements. + * Usort() callback to remove empty/deleted elements. */ function _ting_ranking_field_sort($a, $b) { if ($a['weight'] == $b['weight']) { - return 0; + return 0; } return ($a['weight'] > $b['weight']) ? -1 : 1; } @@ -255,7 +269,7 @@ function ting_admin_boost_settings($form, &$form_state) { $form_state['boost_field_count'] = count($field_data); } - // Wrapper, so that the AJAX callback have some place to put new elements + // Wrapper, so that the AJAX callback have some place to put new elements. $form['ting_boost_fields'] = array( '#title' => t('Custom fields boost values'), '#type' => 'fieldset', @@ -322,7 +336,7 @@ function ting_admin_boost_add_more_js($form, &$form_state) { function ting_admin_boost_form_after_build($form, &$form_state) { $path = drupal_get_path('module', 'ting'); - drupal_add_css($path .'/css/ting_admin_boost_form.css'); + drupal_add_css($path . '/css/ting_admin_boost_form.css'); return $form; } @@ -340,55 +354,22 @@ function ting_admin_boost_settings_submit($form, &$form_state) { } /** - * array_filter() callback to remove empty/deleted elements. + * Array_filter() callback to remove empty/deleted elements. */ function _ting_boost_field_filter($element) { return !(empty($element['field_name']) || empty($element['field_value'])); } /** - * usort() callback to remove empty/deleted elements. + * Callback for usort() to remove empty/deleted elements. */ function _ting_boost_field_sort($a, $b) { if ($a['weight'] == $b['weight']) { - return 0; + return 0; } return ($a['weight'] > $b['weight']) ? -1 : 1; } -/** - * @brief Implementation of hook_menu() - * - * Felter: Forfatter, titel, emneord, - */ -function ting_admin_register_settings() { - // should use phrase.title instead of dc.title but at the moment opensearch does not support the use of phrase.title - $form['ting_admin_register_serie_title'] = array( - '#type' => 'textfield', - '#title' => t('Serie title'), - '#description' => t('Specify the register to be used for searching against serie titles.'), - '#default_value' => variable_get('ting_admin_register_serie_title', 'bib.titleSeries') - ); - - $form['#submit'][] = 'ting_search_extendform_admin_settings_submit'; - return system_settings_form($form); -} - -function ting_admin_register_settings_validate($form, &$form_state) { - $s = ''; - if (isset($form_state['values']['ting_admin_register_serie_title'])) { - $s = $form_state['values']['ting_admin_register_serie_title']; - } - - if ($s == '') { - form_set_error('ting_admin_register_serie_title', t('Please specify a register for serie titles.')); - } -} - -function ting_search_extendform_admin_settings_submit($form, $form_state) { - variable_set('ting_admin_register_serie_title', $form_state['values']['ting_admin_register_serie_title']); -} - /** * Form builder; Configure online resource types and their URL labels. * @@ -403,7 +384,7 @@ function ting_admin_online_types_settings($form_state) { $form['update'] = array( '#type' => 'fieldset', '#title' => t('Update from datawell'), - '#description' => t('Update the list of known types by asking the datawell for all types.') + '#description' => t('Update the list of known types by asking the datawell for all types.'), ); $form['update']['update'] = array( @@ -412,23 +393,23 @@ function ting_admin_online_types_settings($form_state) { '#submit' => array('ting_admin_online_types_settings_update_types'), ); - $types = variable_get('ting_well_types', array()); - + $types = variable_get('ting_well_types', _ting_fetch_well_types()); + $form['ting_online'] = array( '#type' => 'fieldset', '#title' => t('Online types'), '#description' => t('Ting objects defined as found online (not in the library collection).'), - '#tree' => TRUE, + '#tree' => FALSE, '#collapsible' => TRUE, '#collapsed' => TRUE, ); - - $form['ting_online']['types'] = array( + + $form['ting_online']['ting_online_types'] = array( '#type' => 'checkboxes', '#options' => drupal_map_assoc(array_keys($types)), '#default_value' => variable_get('ting_online_types', _ting_default_online_types()), ); - + $settings = variable_get('ting_url_labels', _ting_default_url_labels()); $form['ting_url_labels'] = array( '#type' => 'fieldset', @@ -443,7 +424,7 @@ function ting_admin_online_types_settings($form_state) { '#description' => t('Default label used for types that is not specifically set below.'), ); - if (sizeof($types)) { + if (count($types)) { $form['ting_url_labels']['types'] = array( '#type' => 'fieldset', '#title' => t('Type specific labels'), @@ -470,7 +451,7 @@ function ting_admin_online_types_settings($form_state) { } /** - * Submit handler. Updates the list of known types from the datawell. + * Submit handler. Updates the list of known types from the data well. */ function ting_admin_online_types_settings_update_types($form, &$form_state) { _ting_fetch_well_types(); @@ -489,7 +470,7 @@ function ting_admin_reservable_settings($form_state) { $form['update'] = array( '#type' => 'fieldset', '#title' => t('Update from datawell'), - '#description' => t('Update the lists of known types and sources by asking the datawell for all types and sorces.') + '#description' => t('Update the lists of known types and sources by asking the datawell for all types and sorces.'), ); $form['update']['update'] = array( @@ -498,14 +479,44 @@ function ting_admin_reservable_settings($form_state) { '#submit' => array('ting_admin_reservable_settings_update'), ); - $types = variable_get('ting_well_types', array()); - $sources = variable_get('ting_well_sources', array()); + $form['reservation_settings'] = array( + '#type' => 'fieldset', + '#title' => t('Reservation settings'), + '#tree' => FALSE, + ); + + $options = array(); + $intervals = array( + 86400, + 172800, + 259200, + 345600, + 432000, + 518400, + 604800, + 1209600, + 1814400, + ); + foreach ($intervals as $interval) { + $options[$interval] = format_interval($interval, 2); + } + + $form['reservation_settings']['reservation_expire'] = array( + '#type' => 'select', + '#title' => t('Reservation expire message'), + '#options' => $options, + '#default_value' => variable_get('reservation_expire', 604800), + '#description' => t('Time before an reservation expires that a message should be shown to the user.'), + ); + + $types = variable_get('ting_well_types', _ting_fetch_well_types()); + $sources = variable_get('ting_well_sources', _ting_fetch_well_sources()); $form['ting_reservable'] = array( '#type' => 'fieldset', '#title' => t('Reservation buttons'), '#tree' => FALSE, - '#description' => t("A Ting object will get a reservation button, if it's source and type is both selected ."), + '#description' => t("A Ting object will get a reservation button, if it's source and type is both selected."), ); $form['ting_reservable']['ting_reservable_sources'] = array( @@ -529,7 +540,7 @@ function ting_admin_reservable_settings($form_state) { /** * Submit handler. Updates the list of known types and sources from the - * datawell. + * data well. */ function ting_admin_reservable_settings_update($form, &$form_state) { _ting_fetch_well_types(); diff --git a/ting.api.php b/ting.api.php index 3bd05be..9281c38 100644 --- a/ting.api.php +++ b/ting.api.php @@ -1,22 +1,37 @@ value pairs. Key is the name of the parameter. */ -function hook_ting_online_url_alter(&$url, $entity) { - +function hook_ting_pre_execute($request) { + // In case you need to add additional parameters to request. + return array('includeMarcXchange' => TRUE); } /** - * @} End of "addtogroup hooks". + * Set extra properties to resulting object. + * + * @param object $request + * ting_execute request. + * @param object $response + * ting_execute result. + * @param object $raw_response + * Raw response from ting. + * + * @return array + * Array containing key=>value pairs. Key is the name of the property. */ +function hook_ting_post_execute($request, $response, $raw_response) { + // Add additional property to resulting object. + return array('marcexchange' => array('marcxchange data')); +} diff --git a/ting.client.inc b/ting.client.inc index 90b5370..dd40fe6 100644 --- a/ting.client.inc +++ b/ting.client.inc @@ -4,26 +4,11 @@ * Wrapper functions for Ting client. */ - -function ting_get_object_request($object_id) { - $request = ting_get_request_factory()->getObjectRequest(); - if ($agency = variable_get('ting_agency', FALSE)) { - $request->setAgency($agency); - } - $request->setObjectId($object_id); - $request->setAllRelations(TRUE); - $request->setRelationData('full'); - - // Set search profile, if applicable. - $profile = variable_get('ting_search_profile', ''); - if (!empty($profile) && method_exists($request, 'setProfile')) { - $request->setProfile($profile); - } - - $object = ting_execute_cache($request); - - return $object; -} +/** + * Used to indicate that a given cache entry have return no reply from the data + * well. + */ +define('TING_CACHE_EMPTY_ENTRY', '8e4f3ef1784c020bf7afa5b6dd69b421'); /** * Get an ting object or collection. @@ -32,53 +17,103 @@ function ting_get_object_request($object_id) { * result, and any sub-objects, so fetching objects from a recently * fetched collection won't trigger another backend request. * - * @param $object_id The id to fetch. - * @param $collection Whether to return a collection, if possible, or - * an object. + * @param string $object_id + * The id to fetch. + * @param bool $collection + * Whether to return a collection, if possible, or an object. + * @param bool $with_relations + * Whether to return all relations. Defaults to FALSE. * * @todo Should use getObject, but the ting-client lib doesn't implement that. + * + * @return TingClientObject + * Ting object. */ -function ting_get_object($object_id, $collection = FALSE) { - if (empty($object_id)) { - return FALSE; - } - - // Check the cache first. - $object = ting_cache_get($object_id, $collection); - if (!$object) { - // Put a negative reply in the cache. It will be overwritten by the - // object, or ensure that we won't try to fetch this id again. - ting_cache_set($object_id, NULL); - - if (!$object) { - $request = ting_get_request_factory()->getCollectionRequest(); +function ting_get_object($object_id, $collection = FALSE, $with_relations = FALSE) { + if (!empty($object_id)) { + // Check the cache first. + $type = TING_CACHE_TING_OBJECT; + $cache_key = $object_id; + if ($collection) { + $type = TING_CACHE_COLLECTION; + $cache_key = ting_cache_collection_key($object_id); + } + $object = ting_cache_get($cache_key, $type, $with_relations); + if ($object != TING_CACHE_EMPTY_ENTRY && !$object) { + // Put a negative reply in the cache. It will be overwritten by the + // object, or ensure that we won't try to fetch this id again. + ting_cache_set($object_id, TING_CACHE_EMPTY_ENTRY, $type); + + // Build request request and set object id. + $request = ting_get_request_factory()->getObjectRequest(); + if ($collection) { + // If this is a collection we need to do a collection request, which is + // a search request. + $request = ting_get_request_factory()->getCollectionRequest(); + $request->setAllObjects(FALSE); + } $request->setObjectId($object_id); + + // Set agency from the administration interface. if ($agency = variable_get('ting_agency', FALSE)) { $request->setAgency($agency); } + // Set search profile from the administration interface. $profile = variable_get('ting_search_profile', ''); if (!empty($profile) && method_exists($request, 'setProfile')) { - $request->setProfile($profile); + $request->setProfile($profile); + } + + // Get all relations for the object. + if ($with_relations) { + $request->setAllRelations(TRUE); + $request->setRelationData('full'); } - $request->setAllObjects(false); + // Execute the request. $object = ting_execute_cache($request); + + // If this was a collection request store the collection reply to ensure + // that there is no empty cache entry. + if ($collection) { + if (is_null($object)) { + $object = TING_CACHE_EMPTY_ENTRY; + } + ting_cache_set($object_id, $object, $type); + } } - } - // If not asking for a collection, and the object is, return the - // sub-object with the same id. - if (!$collection && isset($object->objects)) { - foreach ($object->objects as $sub_object) { - if ($sub_object->id == $object_id) { - return $sub_object; + // If not asking for a collection, and the object is, return the + // sub-object with the same id. + if (!$collection && isset($object->objects)) { + foreach ($object->objects as $sub_object) { + if ($sub_object->id == $object_id) { + // If not asking for a collection, and the object is, return the + // sub-object with the same id. + _ting_cache_update_relations_status($sub_object, $with_relations); + return $sub_object; + } } + // No sub-object had the same id. Somethings broken. + return NULL; } - // No sub-object had the same id. Somethings broken. - return NULL; + + // Mark the object in cache as relations have been loaded. + if (!$collection) { + _ting_cache_update_relations_status($object, $with_relations); + } + + // If not asking for a collection, and the object is, return the + // sub-object with the same id. + if ($object == TING_CACHE_EMPTY_ENTRY) { + return NULL; + } + + return $object; } - return $object; + + return NULL; } /** @@ -88,9 +123,9 @@ function ting_get_object($object_id, $collection = FALSE) { */ function ting_get_objects($ids) { $objects = array(); - // Prefill from cache. + // Pre-fill from cache. foreach ($ids as $id) { - $objects[$id] = ting_cache_get($id); + $objects[$id] = ting_cache_get($id, TING_CACHE_TING_OBJECT); if (isset($objects[$id]) && isset($objects[$id]->objects)) { foreach ($objects[$id]->objects as $sub_object) { if ($sub_object->id == $id) { @@ -101,36 +136,53 @@ function ting_get_objects($ids) { // No sub-object had the same id. Somethings broken. $objects[$id] = NULL; } + + if ($objects[$id] == TING_CACHE_EMPTY_ENTRY) { + $objects[$id] = NULL; + } } + // Not all object are searchable, such as relation etc. So to get over this we + // split the request into to groups "own id's" and "others". Where the first + // is ensured to be searchable. + $agency = variable_get('ting_agency', FALSE); $query = array(); foreach ($objects as $id => $object) { - if (!isset($object)) { - $query[] = 'rec.id=' . $id; + if ($object === FALSE) { + // So if the agency match lets search theme as that's faster then fetching + // them one by one. + if (preg_match('/^(890790-basis|' . $agency . '-katalog|' . $agency . ')/', $id)) { + $query[] = 'rec.id=' . $id; + } + else { + // Get objects as it was not local. + $objects[$id] = ting_get_object($id); + } } } - if (sizeof($query)) { + // Open search is limited to 50 results per call, so iterate until all results + // have been fetched. It has a limit on the size of the query (>187 rec.id= + // ORed together seems to break it). + $query_chunks = array_chunk($query, 50); + foreach ($query_chunks as $query_chunk) { $request = ting_get_request_factory()->getSearchRequest(); - - if ($agency = variable_get('ting_agency', FALSE)) { + if ($agency) { $request->setAgency($agency); } - $profile = variable_get('ting_search_profile', ''); if (!empty($profile) && method_exists($request, 'setProfile')) { $request->setProfile($profile); } - - $request->setQuery(join(' OR ', $query)); + $request->setQuery(implode(' OR ', $query_chunk)); $request->setStart(1); - $request->setNumResults(1000); + $request->setNumResults(50); $request->setAllObjects(TRUE); $result = ting_execute_cache($request); if ($result && is_array($result->collections)) { foreach ($result->collections as $collection) { - if (is_array($collection->objects) && sizeof($collection->objects)) { + if (is_array($collection->objects) && count($collection->objects)) { foreach ($collection->objects as $object) { $objects[$object->id] = $object; } @@ -138,33 +190,50 @@ function ting_get_objects($ids) { } } } + return $objects; } /** - * Performs a search agains the well + * Performs a search against the well. * * @param string $query - * The search query + * The search query * @param int $page - * The page number to retrieve search results for + * The page number to retrieve search results for * @param int $results_per_page - * The number of results to include per page + * The number of results to include per page * @param array $options - * Options to pass to the search. Possible options are: - * - facets: Array of facet names for which to return results. Default: facet.subject, facet.creator, facet.type, facet.date, facet.language + * Options to pass to the search. Possible options are: + * - facets: Array of facet names for which to return results. Default: + * facet.subject, facet.creator, facet.type, facet.date, facet.language * - numFacets: The number of terms to include with each facet. Default: 10 - * - enrich: Whether to include additional information and cover images with each object. Default: false - * - sort: The key to sort the results by. Default: "" (corresponds to relevance). The possible values are defined by the sortType type in the XSD. + * - enrich: Whether to include additional information and cover images with + * each object. Default: false + * - sort: The key to sort the results by. Default: "" (corresponds to + * relevance). The possible values are defined by the sortType type + * in the XSD. * - rank: The ranking type, as defined in the XSD. - * - supportingTypes: Whether to include supporting types such as reviews. Default: false + * - supportingTypes: Whether to include supporting types such as reviews. + * Default: false * - reply_only: Don't change the result objects to TingCollection objects. + * - collectionType: The type of results to return. Single + * manifestions(object) or works (collections). Possible values + * manifestion ,work or work-1. Defaults to work. + * * @return TingClientSearchResult - * The search result + * The search result. */ function ting_do_search($query, $page = 1, $results_per_page = 10, $options = array()) { - $request = ting_get_request_factory()->getSearchRequest(); + + $agency = variable_get('ting_agency', FALSE); + if ($agency && variable_get('ting_filter_by_local_holdings', 0)) { + // Limit the search to materials from the local library. From well 3.5 each + // library is no longer isolated. + $query = $query . '(' . $query . ') and holdingsitem.agencyid="' . $agency . '"'; + } + $request->setQuery($query); if ($agency = variable_get('ting_agency', FALSE)) { $request->setAgency($agency); @@ -180,11 +249,28 @@ function ting_do_search($query, $page = 1, $results_per_page = 10, $options = ar } } - $request->setFacets((isset($options['facets'])) ? $options['facets'] : array('facet.subject', 'facet.creator', 'facet.type', 'facet.category', 'facet.language', 'facet.date', 'facet.acSource')); - $request->setNumFacets((isset($options['numFacets'])) ? $options['numFacets'] : ((sizeof($request->getFacets()) == 0) ? 0 : 10)); + $default_facets = array( + 'facet.subject', + 'facet.creator', + 'facet.type', + 'facet.category', + 'facet.language', + 'facet.date', + 'facet.acSource', + ); + $request->setFacets((isset($options['facets'])) ? $options['facets'] : $default_facets); + $request->setNumFacets((isset($options['numFacets'])) ? $options['numFacets'] : ((count($request->getFacets()) == 0) ? 0 : 10)); if (isset($options['sort']) && $options['sort']) { $request->setSort($options['sort']); } + else{ + $sort = variable_get('ting_sort_default', 'rank_frequency'); + $request->setSort($sort); + } + + if (isset($options['collectionType'])) { + $request->setCollectionType($options['collectionType']); + } $request->setAllObjects(isset($options['allObjects']) ? $options['allObjects'] : FALSE); // Set search profile, if applicable. @@ -194,7 +280,7 @@ function ting_do_search($query, $page = 1, $results_per_page = 10, $options = ar } // Apply custom ranking if enabled. - if (variable_get('ting_ranking_custom', FALSE)) { + if (variable_get('ting_ranking_custom', FALSE) && variable_get('ting_ranking_fields', array()) && !isset($options['sort'])) { $fields = array(); foreach (variable_get('ting_ranking_fields', array()) as $field) { $fields[] = array( @@ -207,13 +293,13 @@ function ting_do_search($query, $page = 1, $results_per_page = 10, $options = ar if (!empty($fields)) { // Add the default anyIndex boosts. $fields[] = array( - 'fieldName' => 'cql.anyIndexes', + 'fieldName' => 'term.default', 'fieldType' => 'phrase', - 'weight' => 1, + 'weight' => 2, ); $fields[] = array( - 'fieldName' => 'cql.anyIndexes', + 'fieldName' => 'term.default', 'fieldType' => 'word', 'weight' => 1, ); @@ -221,11 +307,6 @@ function ting_do_search($query, $page = 1, $results_per_page = 10, $options = ar $request->userDefinedRanking = array('tieValue' => 0.1, 'rankField' => $fields); } } - // Otherwise, use the ranking setting. - else { - $request->setRank((isset($options['rank']) && $options['rank']) ? $options['rank'] : 'rank_general'); - } - // Apply custom boosts if any. $boosts = variable_get('ting_boost_fields', array()); @@ -238,7 +319,7 @@ function ting_do_search($query, $page = 1, $results_per_page = 10, $options = ar 'weight' => $boost_field['weight'], ); } - $request->userDefinedBoost = array('boostField' => $uboosts); + $request->userDefinedBoost = $uboosts; } $search_result = ting_execute_cache($request); @@ -263,113 +344,181 @@ function ting_do_search($query, $page = 1, $results_per_page = 10, $options = ar * * Executes the request and caches sub-objects. * - * @param $request the request. + * @param object $request + * The request. + * + * @return object + * The search reply from the data well. */ function ting_execute_cache($request) { + $parms = $request->getRequest()->getParameters(); + + // Handle fulltext vs. dkabm caching of object. + $type = TING_CACHE_TING_OBJECT; + if ($parms['format'] == 'docbook') { + $type = TING_CACHE_TING_OBJECT_FULLTEXT; + } + + // User static cache to store request, used in another function to see if the + // same request is made more than once. $calls = &drupal_static(__FUNCTION__); if (!isset($calls)) { $calls = array(); } - $calls[] = $request->getRequest()->getParameters(); - $reply = ting_execute($request); - - // Cache any sub-objects (mostly true for collections). - if (isset($reply->objects)) { - foreach ($reply->objects as $object) { - ting_cache_set($object->id, $object); - // Cache any relations. - if (isset($object->relations)) { - foreach ($object->relations as $relation) { - if (isset($relation->id)) { - ting_cache_set($relation->id, $relation); + $calls[] = $parms; + + // Check if the reply have been stored in cache. + $reply = ting_cache_get(md5(serialize($parms)), TING_CACHE_REPLY); + + if (!$reply) { + // Reply for the request was not found, so we have to ask the data well. + $reply = ting_execute($request); + + // Cache any sub-objects (mostly true for collections). + if (isset($reply->objects)) { + foreach ($reply->objects as $object) { + ting_cache_set($object->id, $object, TING_CACHE_TING_OBJECT); + // Cache any relations. + if (isset($object->relations)) { + foreach ($object->relations as $relation) { + if (isset($relation->id)) { + ting_cache_set($relation->id, $relation); + } } } } } - // Cache the reply as the first object's id. This is for collections. - if (!isset($reply->id) and isset($reply->objects[0])) { - ting_cache_set($reply->objects[0]->id, $reply); - } - } - // Cache any collections. Done after objects to ensure that collections take - // precedence. - if (isset($reply->collections)) { - foreach ($reply->collections as &$collection) { - if (isset($collection->objects[0])) { - foreach ($collection->objects as $object) { - // Cache any relations. - if (isset($object->relations)) { - foreach ($object->relations as $relation) { - if (isset($relation->id)) { - ting_cache_set($relation->id, $relation); + // Cache any collections. Done after objects to ensure that collections take + // precedence. + if (isset($reply->collections)) { + foreach ($reply->collections as &$collection) { + if (is_array($collection->objects)) { + foreach ($collection->objects as $object) { + // Cache any relations. + if (isset($object->relations)) { + foreach ($object->relations as $relation) { + if (isset($relation->id)) { + ting_cache_set($relation->id, $relation); + } } } + ting_cache_set($object->id, $object, $type); } - ting_cache_set($object->id, $object); + ting_cache_set(ting_cache_collection_key($collection->objects[0]->id), $collection, TING_CACHE_COLLECTION); } + } + } - ting_cache_set($collection->objects[0]->id, $collection); + // Cache any relations. + if (isset($reply->relations)) { + foreach ($reply->relations as $relation) { + ting_cache_set($relation->id, $relation, TING_CACHE_TING_OBJECT); + } + } + + // Cache the object self. + if ($reply instanceof TingClientObject) { + if (!empty($reply->record)) { + ting_cache_set($reply->id, $reply); + } + else { + $reply = TING_CACHE_EMPTY_ENTRY; } } - } - // Cache any relations. - if (isset($reply->relations)) { - foreach ($reply->relations as $object) { - ting_cache_set($object->id, $object); + // Store the reply for the request itself in the cache. + if (is_null($reply)) { + // Handle empty data well replies. + $reply = TING_CACHE_EMPTY_ENTRY; } + ting_cache_set(md5(serialize($parms)), $reply, TING_CACHE_REPLY); } - // Lastly cache the reply itself if it has an id. - if (isset($reply->id)) { - ting_cache_set($reply->id, $reply); + if ($reply == TING_CACHE_EMPTY_ENTRY) { + return NULL; } return $reply; } /** - * Get item from static cache. + * Get cached version of a data well search. + * + * The cache can lookup ting objects, ting collections or even a replay from + * the data well. + * + * To retrieve an reply simple extract the params from the request object, + * serialize them and make a MD5 hash as id. + * + * @see ting_execute_cache() + * + * @param string $id + * Object id or the MD5 hash of the parameters used to execute a search + * against the date well. + * @param string $type + * The type of data to cache, which is used to set the cache id. It should be + * one off: TING_CACHE_TING_OBJECT, TING_CACHE_COLLECTION, + * TING_CACHE_TING_OBJECT_FULLTEXT or TING_CACHE_REPLY. + * @param bool $with_relations + * Is the object we are looking up with relations (addi posts). + * + * @return mixed + * The cached item based on the $type and $id given. If non found in the cache + * NULL is returned. */ -function ting_cache_get($id, $collection = FALSE) { - $cid = 'ting-' . ($collection ? 'collection' : 'object') . ':' . $id; +function ting_cache_get($id, $type = TING_CACHE_TING_OBJECT, $with_relations = FALSE) { + $cid = $type . ':' . $id; + if ($ttl = variable_get('ting_cache_lifetime', TING_DEFAULT_CACHE_LIFETIME)) { - $cache = cache_get($cid); + $cache = cache_get($cid, 'cache_ting'); if ($cache && ($cache->expire > REQUEST_TIME)) { - return $cache->data; - } - return NULL; - } - else { - // Without proper caching, use a request cache. - $cache = &drupal_static('ting_cache_set'); - if (!isset($cache)) { - $cache = array(); - } - // Using array_key_exists, as we might contain NULL values (which is !isset()). - if (array_key_exists($cid, $cache)) { - return $cache[$cid]; + $data = $cache->data; + // Check if cached version has relations, if request. If it's an empty + // array it have not been request by the server yet with relations, so + // return FALSE to trigger a data well request. + if ($with_relations && (isset($data->relations) && is_array($data->relations) && !count($data->relations))) { + return FALSE; + } + + // The data maybe NULL which means that the data well have been asked + // about this object and no where found. + return $data; } - return NULL; + return FALSE; } } /** - * Put item in the static cache. + * Store cached version of a data well search. + * + * The cache can store ting objects, ting collections or even a replay from + * the data well. + * + * To store an reply simple extract the params from the request object, + * serialize them and make a MD5 hash as id. + * + * @see ting_execute_cache() + * + * @param string $id + * Id that the item was cached under. + * @param mixed $value + * The value to store in the cache. + * @param string $type + * The type of data to cache, which is used to set the cache id. It should be + * one off: TING_CACHE_TING_OBJECT, TING_CACHE_COLLECTION, + * TING_CACHE_TING_OBJECT_FULLTEXT or TING_CACHE_REPLY. */ -function ting_cache_set($id, $value) { - $cid = 'ting-object'; - if ($value instanceof TingClientObjectCollection) { - $cid = 'ting-collection'; - } - $cid .= ':' . $id; +function ting_cache_set($id, $value, $type = TING_CACHE_TING_OBJECT) { + // Define the cache id. + $cid = $type . ':' . $id; if ($ttl = variable_get('ting_cache_lifetime', TING_DEFAULT_CACHE_LIFETIME)) { - $cache = cache_set($cid, $value, 'cache', REQUEST_TIME + $ttl); + cache_set($cid, $value, 'cache_ting', REQUEST_TIME + $ttl); } else { - // Without proper caching, use a request cache. + // Without proper caching, use a static cache that only works on pr. + // request. $cache = &drupal_static(__FUNCTION__); if (!isset($cache)) { $cache = array(); @@ -378,27 +527,85 @@ function ting_cache_set($id, $value) { } } +/** + * Generates a cache id (cid) for ting collection cache. + * + * Collections have to be indexed in cache based on the facets selected as the + * collections changes content based on facets. This is all due to the fact that + * collections don't have unique id's. In fact we use the first object's id in + * the collection to id the collection. + * + * @param string $object_id + * Ting object ID also known as PID. + * + * @return string + * Cache key to retrieve and set data in the cache, + */ +function ting_cache_collection_key($object_id) { + $cache_key = $object_id; + if (!empty($_GET['facets'])) { + $cache_key .= ':' . md5(serialize($_GET['facets'])); + } + + return $cache_key; +} + +/** + * Mark the object in the cache as having no relations in the data well. + * + * This is need as object may have been cached without relations in a search + * request, but a get_object request may ask for the same object from cache with + * relations. So this FALSE value is used to ensure that the data well is only + * asked once for a object with relations even, if it do not have relations. + * + * Default value from the data well is an empty array, so if the array is empty + * the ting_cache_set, function will not return the cached if relations are + * requested. + * + * @param StdClass $object + * Ting data well object. + * @param bool $with_relations + * If TRUE relations will be marked. + */ +function _ting_cache_update_relations_status($object, $with_relations = FALSE) { + if ($with_relations && $object instanceof TingClientObject) { + if (empty($object->relations)) { + // Mark this object as having no relations. + $object->relations = FALSE; + } + + // Update cache with the object. + ting_cache_set($object->id, $object); + } +} + /** * Get recommendations for a given ISBN. * * @param string $isbn - * ISBN number to get recommendations from. - * @param $numResults - * The number of results to return. + * ISBN number to get recommendations from. + * @param int $num_results + * The number of results to return. + * * @return array - * An array of TingClientObjectRecommendation objects. + * An array of TingClientObjectRecommendation objects. */ -function ting_get_object_recommendations($isbn, $numResults = 10) { +function ting_get_object_recommendations($isbn, $num_results = 10) { $request = ting_get_request_factory()->getObjectRecommendationRequest(); $request->setIsbn($isbn); - $request->setNumResults($numResults); + $request->setNumResults($num_results); return ting_execute($request); } /** * Retrieves an initialized Ting client request factory. * + * @throws TingClientException + * If there is no end-point url defined in the configuration this exception is + * thrown. + * * @return TingClientRequestFactory + * TingClientRequestFactory object. */ function ting_get_request_factory() { static $request_factory; @@ -406,10 +613,8 @@ function ting_get_request_factory() { if (!isset($request_factory)) { $url_variables = array( 'search' => 'ting_search_url', - 'scan' => 'ting_scan_url', 'object' => 'ting_search_url', 'collection' => 'ting_search_url', - 'spell' => 'ting_spell_url', 'recommendation' => 'ting_recommendation_url', ); @@ -432,6 +637,17 @@ function ting_get_request_factory() { return $request_factory; } +/** + * Add relation type to a search request object. + * + * @param TingClientSearchRequest $request + * The search request to add the relation to. + * @param string $type + * The type of relation add to the request. + * + * @return TingClientSearchRequest + * The request added the relation. + */ function ting_add_relations($request, $type = 'full') { $request->setAllRelations(TRUE); $request->setRelationData($type); @@ -439,18 +655,51 @@ function ting_add_relations($request, $type = 'full') { } /** - * Perform a request against Ting and perform error handling if necessary + * Perform a request against Ting and perform error handling if necessary. * - * @param $request The request - * @return mixed Result of the request or false if an error occurs + * @param object $request + * The request. + * + * @return mixed + * Result of the request or false if an error occurs. */ function ting_execute($request) { + // Get additional parameters from other modules. + $params = module_invoke_all('ting_pre_execute', $request); + if (!empty($params)) { + $request->setParameters($params); + } + try { timer_start('ting'); $res = ting_get_client()->execute($request); timer_stop('ting'); - return $res; - } catch (TingClientException $e) { + + // When the request is for fulltext (doc-book) the result is XML but the + // next part expect JSON only formatted input. So this hack simply return + // the XML for now as later on we have to work with open format and XML + // parsing. So for now simply return the result to fulltext. + if ($request instanceof TingClientObjectRequest && $request->getOutputType() == 'xml' && $request->getFormat() == 'docbook') { + return $res; + } + + $response = $request->parseResponse($res); + + // Pass parsed results to other modules. + // @todo Check if it works for collection of items. + $props = module_invoke_all('ting_post_execute', $request, $response, $res); + if (!empty($props)) { + foreach ($props as $property => $value) { + $response->{$property} = $value; + } + } + + return $response; + } + catch (TingClientException $e) { + if (isset($e->user_message)) { + drupal_set_message($e->user_message, 'warning'); + } timer_stop('ting'); watchdog('ting client', 'Error performing request: ' . $e->getMessage(), NULL, WATCHDOG_ERROR, 'http://' . $_SERVER["HTTP_HOST"] . request_uri()); return FALSE; @@ -458,9 +707,12 @@ function ting_execute($request) { } /** - * Retrieves an initialized Ting client with appropriate request adapter and logger + * Retrieves an initialized Ting client. + * + * The client returned is with appropriate request adapter and logger. * * @return TingClient + * The ting client object that can be used to communicate with the data well. */ function ting_get_client() { static $client; @@ -472,46 +724,3 @@ function ting_get_client() { return $client; } - -/** - * Use OpenScan to search for keyword, check - * http://oss.dbc.dk/twiki/bin/view/Databroend/OpenSearchDocIndexes - * for which phrase index to search, default is 'anyIndexes' - * - * @param string $query The prefix to scan for - * @param string $phrase Which phrase index to search - * @param int $num_results The numver of results to return - * @return TingClientScanResult - */ -function ting_do_scan($query, $phrase = 'anyIndexes', $num_results = 10) { - $request = ting_get_request_factory()->getScanRequest(); - $request->setField('phrase.' . $phrase); - $request->setLower($query); - $request = ting_add_agency($request); - $request->setNumResults($num_results); - return ting_execute($request); -} - -/** - * @param object $request - The TingClient object - * @return TingClientScanRequest - */ -function ting_add_agency(TingClientScanRequest $request) { - if ($agency = variable_get('ting_agency', FALSE)) { - $request->setAgency($agency); - } - return $request; -} - -/** - * @param string $word The word to get spell suggestions for - * @param $num_results The number of results to return - * @return array An array of TingClientSpellSuggestion objects - */ -function ting_get_spell_suggestions($word, $num_results = 10) { - $request = ting_get_request_factory()->getSpellRequest(); - $request->setWord($word); - $request->setNumResults($num_results); - return ting_execute($request); -} - diff --git a/ting.controllers.inc b/ting.controllers.inc index e286375..55ce8fb 100644 --- a/ting.controllers.inc +++ b/ting.controllers.inc @@ -94,14 +94,14 @@ class TingObjectController extends DrupalDefaultEntityController { $get_ids[] = $qe->ding_entity_id; } if ($get_ids && $load_ids = array_diff($get_ids, $cached_entity_ids)) { - $objects = ting_get_objects($load_ids); + $objects = ting_get_objects(array_unique($load_ids)); } // Not known locally. Create a proxy if it exists in the well. - if (sizeof($unknown)) { + if (count($unknown)) { $new_ids = array(); foreach ($unknown as $ding_entity_id) { - if (isset($objects[$ding_entity_id])){ + if (isset($objects[$ding_entity_id])) { // Insert a new local proxy row. $ting_object = array( 'ding_entity_id' => $ding_entity_id, @@ -138,6 +138,7 @@ class TingObjectController extends DrupalDefaultEntityController { $entities += $queried_entities; } + // If entity supports cache. if ($this->cache) { // Add entities to the cache if we are not loading a revision. if (!empty($queried_entities) && !$revision_id) { @@ -157,6 +158,7 @@ class TingObjectController extends DrupalDefaultEntityController { } return $entities; } + /** * Gets entities from the static cache. * @@ -179,15 +181,16 @@ class TingObjectController extends DrupalDefaultEntityController { // Exclude any entities loaded from cache if they don't match $conditions. // This ensures the same behavior whether loading from memory or database. if ($conditions) { - foreach ($entities as $entity) { - $entity_values = (array) $entity; - $diffs = array_diff_assoc($conditions, $entity_values); - if ($diffs) { - if (array_keys($diffs) != array('ding_entity_id') || !in_array($entity_values['ding_entity_id'], $diffs['ding_entity_id'])) { + // Check that conditions has ding_entity_id key. + if (!empty($conditions['ding_entity_id'])) { + foreach ($entities as $entity) { + if (!in_array($entity->ding_entity_id, $conditions['ding_entity_id'])) { unset($entities[$entity->{$this->idKey}]); - }} + } + } } } + return $entities; } } diff --git a/ting.drush.inc b/ting.drush.inc new file mode 100644 index 0000000..9d644a9 --- /dev/null +++ b/ting.drush.inc @@ -0,0 +1,55 @@ +properties['object']; } + + public function getURI() { + return $this->properties['uri']; + } + + public function getType() { + return $this->properties['type']; + } } /** @@ -33,7 +40,9 @@ class TingRelation extends DingEntityBase { */ class TingEntity extends DingEntity { public $type = DingEntityBase::NULL; + public $serieNumber = DingEntityBase::NULL; public $serieTitle = DingEntityBase::NULL; + public $serieDescription = DingEntityBase::NULL; public $record = DingEntityBase::NULL; public $relations = DingEntityBase::NULL; public $localId = DingEntityBase::NULL; @@ -49,20 +58,33 @@ class TingEntity extends DingEntity { public $isPartOf = DingEntityBase::NULL; public $extent = DingEntityBase::NULL; public $classification = DingEntityBase::NULL; + public $isbn = DingEntityBase::NULL; public function getExtent() { return !empty($this->reply->record['dcterms:extent'][''][0]) ? $this->reply->record['dcterms:extent'][''][0] : FALSE; } function getClassification() { - $ret = (isset($this->reply->record['dc:subject']['dkdcplus:DK5'][0]) ? $this->reply->record['dc:subject']['dkdcplus:DK5'][0] : '' . - (isset($this->reply->record['dc:subject']['dkdcplus:DK5-Text']) ? ' (' . $this->reply->record['dc:subject']['dkdcplus:DK5-Text'][0] . ')' : '')); - if( strlen($ret) > 0 ) { - return $ret; + $dk5 = ''; + if (!empty($this->reply->record['dc:subject']['dkdcplus:DK5'][0])) { + $dk5 = $this->reply->record['dc:subject']['dkdcplus:DK5'][0]; + + if ($dk5 == 'sk') { + return ''; + } + + return $dk5; } + return FALSE; } + function getClassificationText() { + $dk5_text = !empty($this->reply->record['dc:subject']['dkdcplus:DK5-Text'][0]) ? $this->reply->record['dc:subject']['dkdcplus:DK5-Text'][0] : ''; + + return $dk5_text; + } + function getIsPartOf() { $this->isPartOf = array(); if( !empty($this->reply->record['dcterms:isPartOf']) ) { @@ -115,16 +137,67 @@ class TingEntity extends DingEntity { return $title; } - function getSerieTitle() { - $serie_title = array(); - $serie = !empty($this->reply->record['dc:title']['dkdcplus:series'][0]) ? $this->reply->record['dc:title']['dkdcplus:series'][0] : ''; - if (preg_match('/^([^;]+);/', $serie, $serie_title)) { - return isset($serie_title[1]) ? trim($serie_title[1]) : FALSE; + private function splitSerie() { + $series = !empty($this->reply->record['dc:title']['dkdcplus:series']) ? $this->reply->record['dc:title']['dkdcplus:series'] : array(); + $serie_titles = array(); + foreach ($series as $serie) { + $serie_titles[] = explode(';', $serie); } - else { - return FALSE; + return $serie_titles; + } + + function getSerieTitle() { + return $this->splitSerie(); + } + + function getSerieDescription() { + $serie = !empty($this->reply->record['dc:description']['dkdcplus:series'][0]) ? $this->reply->record['dc:description']['dkdcplus:series'][0] : ''; + return $this->process_series_description($serie); + } + + /** + * Process series information. + * + * This could be handled more elegantly if we had better structured data. + * For now we have to work with what we got to convert titles to links + * Series information appear in the following formats: + * - Samhørende: [title 1] ; [title 2] ; [title 3] + * - [volumne number]. del af: [title] + */ + private function process_series_description($series) { + $result = ''; + $parts = explode(':', $series); + + if (is_array($parts) && count($parts) >= 2) { + $prefix = $parts[0] . ': '; + + if (stripos($prefix, 'del af:') !== FALSE) { + $title = trim($parts[1]); + $path = str_replace('@serietitle', drupal_strtolower($title), variable_get('ting_search_register_serie_title', 'phrase.titleSeries="@serietitle"')); + $link = l($title, 'search/ting/' . $path, array('attributes' => array('class' => array('series')))); + $result = $prefix . $link; + } else if (stripos($prefix, 'Samhørende:') !== FALSE) { + $titles = $parts[1]; + // Multiple titles are separated by ' ; '. Explode to iterate over them + $titles = explode(' ; ', $titles); + foreach ($titles as &$title) { + $title = trim($title); + // Some title elements are actually volumne numbers. Do not convert these to links + if (!preg_match('/(nr.)? \d+/i', $title)) { + $title = l($title, 'search/ting/' . '"' . $title . '"'); + } + } + // Reassemple titles + $titles = implode(', ', $titles); + $result = $prefix . ' ' . $titles; + } + else { + return $series; + } } + + return $result; } function getAbstract() { @@ -137,9 +210,9 @@ class TingEntity extends DingEntity { function getRelations() { // If relations are not set; do another request to get relations - if( !isset($this->reply->relationsData) ) { - $tingClientObject = ting_get_object_request($this->ding_entity_id); - if( isset($tingClientObject->relationsData) ) { + if (!isset($this->reply->relationsData)) { + $tingClientObject = ting_get_object($this->ding_entity_id, FALSE, TRUE); + if (isset($tingClientObject->relationsData)) { $this->reply->relationsData = $tingClientObject->relationsData; } } @@ -198,12 +271,50 @@ class TingEntity extends DingEntity { return !empty($this->reply->record['dc:date']) ? $this->reply->record['dc:date'][''][0] : FALSE; } - function getOnline_url() { - if (isset($this->reply->record['dc:identifier']['dcterms:URI'])) { + /** + * Try to find the online url. + * + * @param bool $get_relations + * If TRUE relations will be fetched from the data well else "dc:identifier" + * will be checked. + * + * @return mixed + * URL to the online resource or the empty string if not found. + */ + function getOnline_url($get_relations = TRUE) { + $url = ''; + if ($get_relations) { + // Try to find the online url from relation data, which requires us to get + // relations. First check if relations are set; if not do another request + // to get relations + if (!isset($this->reply->relationsData)) { + $tingClientObject = ting_get_object($this->ding_entity_id, FALSE, TRUE); + if (isset($tingClientObject->relationsData)) { + $this->reply->relationsData = $tingClientObject->relationsData; + } + } + if (isset($this->reply->relationsData)) { + foreach ($this->reply->relationsData as $data) { + if ($data->relationType == 'dbcaddi:hasOnlineAccess') { + $url = preg_replace('/^\[URL\]/', '', $data->relationUri); + // Check for correct url or leading token - some uri is only an id. + if (stripos($url, 'http') === 0 || strpos($url, '[') === 0) { + // Give other modules a chance to rewrite the url. + drupal_alter('ting_online_url', $url, $this); + } + } + } + } + } + + // No hasOnlineAccess found so fallback to dc:identifier. + if (empty($url) && isset($this->reply->record['dc:identifier']['dcterms:URI'])) { $url = $this->reply->record['dc:identifier']['dcterms:URI'][0]; + // Give ting_proxy a change to rewrite the url. drupal_alter('ting_online_url', $url, $this); - return $url; } + + return $url; } function getAc_source() { @@ -213,6 +324,29 @@ class TingEntity extends DingEntity { function getDescription() { return !empty($this->reply->record['dc:description'][''][0]) ? $this->reply->record['dc:description'][''][0] : FALSE; } + + /** + * Get ISBN numbers of the object. + * + * @return array + */ + function getIsbn() { + $isbn = array(); + + // Nothing to do. + if (empty($this->reply->record['dc:identifier']['dkdcplus:ISBN'])) { + return $isbn; + } + + // Get ISBN numbers. + $isbn = $this->reply->record['dc:identifier']['dkdcplus:ISBN']; + foreach ($isbn as $k => $number) { + $isbn[$k] = str_replace(array(' ', '-'), '', $number); + } + rsort($isbn); + return $isbn; + } + } /** @@ -231,25 +365,17 @@ class TingCollection extends DingEntityCollection { function getTitle() { foreach ($this->reply->objects as &$object) { - // Find the title of the object that was used to fetch this collection. + // Find the title of the object that was used to fetch this collection. We + // don't look at dkdcplus:full as it might be too specific ("1Q84. Book 1" + // for instance). if ($object->id == $this->ding_entity_id && !empty($object->record['dc:title'])) { - if (isset($object->record['dc:title']['dkdcplus:full'])) { - return $object->record['dc:title']['dkdcplus:full'][0]; - } - else { - return $object->record['dc:title'][''][0]; - } + return $object->record['dc:title'][''][0]; } } // If we couldn't find the object, use the title of the first object. $title = FALSE; if (isset($this->reply->objects[0]->record['dc:title'])) { - if (isset($this->reply->objects[0]->record['dc:title']['dkdcplus:full'])) { - $title = $this->reply->objects[0]->record['dc:title']['dkdcplus:full'][''][0]; - } - else { - $title = $this->reply->objects[0]->record['dc:title'][''][0]; - } + $title = $this->reply->objects[0]->record['dc:title'][''][0]; } return $title; } @@ -310,4 +436,3 @@ class TingCollection extends DingEntityCollection { } } } - diff --git a/ting.field.inc b/ting.field.inc index 007150b..ac54720 100644 --- a/ting.field.inc +++ b/ting.field.inc @@ -131,6 +131,7 @@ function ting_field_formatter_info() { ), 'settings' => array( 'link_type' => 'none', + 'prefix_type' => 'no', ), ), 'ting_type_default' => array( @@ -211,7 +212,18 @@ function ting_field_formatter_settings_form($field, $instance, $view_mode, $form ), '#default_value' => $settings['link_type'], ); + + $element['prefix_type'] = array( + '#type' => 'radios', + '#title' => t('Prefix with "ting type"'), + '#options' => array( + 'no' => t('No'), + 'yes' => t('Yes'), + ), + '#default_value' => $settings['prefix_type'], + ); break; + case 'ting_entities': $element['hide_primary'] = array( '#type' => 'checkbox', @@ -219,6 +231,7 @@ function ting_field_formatter_settings_form($field, $instance, $view_mode, $form '#description' => t("Don't show the primary object as part of the list."), '#default_value' => $settings['hide_primary'], ); + case 'ting_primary_object': $entity_info = entity_get_info('ting_object'); $view_modes = array(); @@ -232,7 +245,7 @@ function ting_field_formatter_settings_form($field, $instance, $view_mode, $form '#default_value' => $settings['view_mode'], '#options' => $view_modes, ); - break; + break; } return $element; @@ -247,10 +260,14 @@ function ting_field_formatter_settings_summary($field, $instance, $view_mode) { $summary = ''; switch ($field['type']) { case 'ting_title': - $summary .= t('Link type: @type', array('@type' => $settings['link_type'])); + $summary .= t('Link type: @type', array('@type' => $settings['link_type'])); + $summary .= ''; + $summary .= t('Prefix with type: @type', array('@type' => $settings['prefix_type'])); break; + case 'ting_entities': $summary .= $settings['hide_primary'] ? t("Don't show primary. ") : t("Include primary. "); + case 'ting_primary_object': $view_mode = $settings['view_mode']; $entity_info = entity_get_info('ting_object'); @@ -265,7 +282,6 @@ function ting_field_formatter_settings_summary($field, $instance, $view_mode) { return $summary; } - /** * Implements hook_field_formatter_view(). */ @@ -277,7 +293,7 @@ function ting_field_formatter_view($entity_type, $entity, $field, $instance, $la $entities = $entity->entities; // Skip the first. if ($display['settings']['hide_primary']) { - $primary = array_shift($entities); + array_shift($entities); } foreach ($entity->types as $type) { @@ -303,25 +319,54 @@ function ting_field_formatter_view($entity_type, $entity, $field, $instance, $la */ $type = 'ting_object'; if ($display['settings']['link_type'] == 'collection') { - // // Check if the cache contains an collection for this id. - // $x = ting_cache_get($entity->id); - // if ($x instanceof TingClientObjectCollection) { - $type = 'ting_collection'; - // } + $type = 'ting_collection'; } - if ($display['settings']['link_type'] != 'none') { - $url = entity_uri($type, $entity); - $title = l($entity->title, $url['path'], $url['options']); + + $link = FALSE; + $title = $entity->title; + // Only create the link if the object is available in the data well. + if ($display['settings']['link_type'] != 'none' && !isset($entity->is_available)) { + $link = TRUE; + } + + // Check if element should be prefixed with type. + if ($display['settings']['prefix_type'] == 'yes') { + $collection = ting_collection_load($entity->id); + $ting_type = $entity->getType(); + if (count($collection->types) > 1) { + $ting_type = t('Material collection'); + // Replace title with the more generic collection title. + $title = $collection->title; + } + + if ($link) { + $url = entity_uri($type, $entity); + $title = l($title, $url['path'], $url['options']); + } + else { + $title = check_plain($title); + } + + $element[$delta] = array( + '#prefix' => '