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' => '

', + '#suffix' => '

', + 'title' => array( + // @todo: this could be moved into a theme function to be more + // generic. + '#prefix' => '' . $ting_type . ':', + '#markup' => $title, + ), + ); } else { - $title = check_plain($entity->title); + $element[$delta] = array( + '#prefix' => '

', + '#suffix' => '

', + '#markup' => $title, + ); } - $element[$delta] = array( - '#prefix' => '

', - '#suffix' => '

', - '#markup' => $title, - ); break; + case 'ting_type_default': $element[$delta] = array( '#theme' => 'item_list', @@ -329,36 +374,52 @@ function ting_field_formatter_view($entity_type, $entity, $field, $instance, $la array( 'data' => $entity->type, 'class' => array(drupal_html_class($entity->type)), - ) + ), ), ); break; + case 'ting_series_default': if ($entity->serieTitle) { + // The search string used is configurable because the data well is not + // fully implemented regarding series. As default we search both in + // the series title register and the description field because many + // series only have data in the description field. This will probably + // change with new versions of the data well. + foreach($entity->serieTitle as $title) { + $search_term = str_replace('@serietitle', drupal_strtolower($title[0]), variable_get('ting_search_register_serie_title', 'phrase.titleSeries="@serietitle"')); + $series_number = isset($title[1]) ? ' : ' . check_plain($title[1]) . ' ; ' : ' ; '; + $element[$delta][] = array( + '#markup' => l(trim($title[0]), 'search/ting/' . $search_term, array('attributes' => array('class' => array('series')))) . $series_number, + ); + } + } + elseif ($entity->serieDescription) { $element[$delta] = array( - // should use phrase.title instead of dc.title but at the moment opensearch does not support the use of phrase.title - '#markup' => l(check_plain($entity->serieTitle), 'search/ting/' . variable_get('ting_admin_register_serie_title', 'dc.title') . '=' . drupal_strtolower($entity->serieTitle), array('attributes' => array('class' => array('series')))), + '#markup' => $entity->serieDescription, ); } break; + case 'ting_abstract_default': $element[$delta] = array( '#markup' => check_plain($entity->abstract), ); break; + case 'ting_author_default': $creators = array(); foreach ($entity->creators as $i => $creator) { - $creators[] = l($creator, 'search/ting/dc.creator=' . $creator, array('attributes' => array('class' => array('author')))); + $creators[] = l($creator, 'search/ting/phrase.creator="' . $creator . '"', array('attributes' => array('class' => array('author')))); } $markup_string = ''; if (count($creators)) { if ($entity->date != '') { $markup_string = t('By !author_link (@year)', array( '!author_link' => implode(', ', $creators), - '@year' => $entity->date, // So wrong, but appears to - // be the way the data is. - )); + // So wrong, but appears to be the way the data is. + '@year' => $entity->date, + )); } else { $markup_string = t('By !author_link', array( @@ -366,27 +427,30 @@ function ting_field_formatter_view($entity_type, $entity, $field, $instance, $la )); } } - elseif($entity->date != '') { - $markup_string = t('(@year)', array('@year'=>$entity->date)); - } + elseif ($entity->date != '') { + $markup_string = t('(@year)', array('@year' => $entity->date)); + } $element[$delta] = array( '#markup' => $markup_string, ); break; + case 'ting_subjects_default': if (count($entity->subjects) == TRUE) { $subjects = array(); foreach ($entity->subjects as $subject) { - $subjects[] = l($subject, 'search/ting/dc.subject=' . $subject, array('attributes' => array('class' => array('subject')))); + $subjects[] = l($subject, 'search/ting/dkcclterm.em="' . $subject . '"', array('attributes' => array('class' => array('subject')))); } $element[$delta] = array( '#markup' => implode(' ', $subjects), ); } break; + case 'ting_primary_object_default': $element[$delta] = ting_object_view($entity->primary_object, $display['settings']['view_mode']); break; + case 'ting_collection_types_default': $types = array(); foreach ($entity->types as $type) { diff --git a/ting.info b/ting.info index 4aeebe0..ae4a8be 100644 --- a/ting.info +++ b/ting.info @@ -11,4 +11,4 @@ configure = admin/config/ting stylesheets[all][] = ting.css dependencies[] = ding_entity dependencies[] = nanosoap -dependencies[] = blackhole +dependencies[] = virtual_field diff --git a/ting.install b/ting.install index 5bc981b..e958ce4 100644 --- a/ting.install +++ b/ting.install @@ -9,6 +9,8 @@ * Implements hook_schema(). */ function ting_schema() { + $schema = array(); + $schema['ting_object'] = array( 'description' => 'Local proxy table for ting objects.', 'fields' => array( @@ -19,16 +21,16 @@ function ting_schema() { 'not null' => TRUE, ), 'vid' => array( - 'description' => 'The current {ting_object_revision}.vid version identifier.', + 'description' => 'The current {ting_object}.vid version identifier.', 'type' => 'int', 'unsigned' => TRUE, - 'not null' => TRUE, - 'default' => 0, + 'not null' => FALSE, + 'default' => NULL, ), 'ding_entity_id' => array( 'description' => 'The ting object id.', 'type' => 'varchar', - 'length' => 256, + 'length' => 255, 'not null' => TRUE, 'default' => '', ), @@ -83,6 +85,9 @@ function ting_schema() { ), ); + // Cache table used by ting_execute_cache. + $schema['cache_ting'] = drupal_get_schema_unprocessed('system', 'cache'); + return $schema; } @@ -95,7 +100,7 @@ function ting_ding_entity_fields() { 'field' => array( 'locked' => TRUE, 'storage' => array( - 'type' => 'blackhole', + 'type' => 'virtual_field', ), ), 'instance' => array( @@ -106,7 +111,7 @@ function ting_ding_entity_fields() { 'field' => array( 'locked' => TRUE, 'storage' => array( - 'type' => 'blackhole', + 'type' => 'virtual_field', ), ), 'instance' => array( @@ -117,7 +122,7 @@ function ting_ding_entity_fields() { 'field' => array( 'locked' => TRUE, 'storage' => array( - 'type' => 'blackhole', + 'type' => 'virtual_field', ), ), 'instance' => array( @@ -128,7 +133,7 @@ function ting_ding_entity_fields() { 'field' => array( 'locked' => TRUE, 'storage' => array( - 'type' => 'blackhole', + 'type' => 'virtual_field', ), ), 'instance' => array( @@ -139,7 +144,7 @@ function ting_ding_entity_fields() { 'field' => array( 'locked' => TRUE, 'storage' => array( - 'type' => 'blackhole', + 'type' => 'virtual_field', ), ), 'instance' => array( @@ -150,7 +155,7 @@ function ting_ding_entity_fields() { 'field' => array( 'locked' => TRUE, 'storage' => array( - 'type' => 'blackhole', + 'type' => 'virtual_field', ), ), 'instance' => array( @@ -162,7 +167,7 @@ function ting_ding_entity_fields() { 'field' => array( 'locked' => TRUE, 'storage' => array( - 'type' => 'blackhole', + 'type' => 'virtual_field', ), ), 'instance' => array( @@ -174,7 +179,7 @@ function ting_ding_entity_fields() { 'field' => array( 'locked' => TRUE, 'storage' => array( - 'type' => 'blackhole', + 'type' => 'virtual_field', ), ), 'instance' => array( @@ -186,7 +191,7 @@ function ting_ding_entity_fields() { 'field' => array( 'locked' => TRUE, 'storage' => array( - 'type' => 'blackhole', + 'type' => 'virtual_field', ), ), 'instance' => array( @@ -196,3 +201,156 @@ function ting_ding_entity_fields() { ), ); } + +/** + * Convert blackhole field storage to virtual field. + */ +function ting_update_7000() { + return db_update('field_config') + ->fields(array( + 'storage_type' => 'virtual_field', + 'storage_module' => 'virtual_field', + )) + ->condition('module', 'ting') + ->execute(); +} + +/** + * Add cache table to use with ting_execute_cache(). + */ +function ting_update_7001() { + $schema = ting_schema(); + $ret = array(); + $ret[] = db_create_table('cache_ting', $schema['cache_ting']); + return $ret; +} + +/** + * Update own agency ding_entity_id's to match new data well format (basis and katalog). + */ +function ting_update_7002() { + // Convert "katalog" PID's first. + $agency = variable_get('ting_agency', ''); + db_query("UPDATE ting_object + SET ding_entity_id = CONCAT('" . $agency . "-katalog:', SUBSTRING_INDEX(ding_entity_id, ':', -1)) + WHERE ding_entity_id LIKE '" . $agency . ":9%'") + ->execute(); + + // Convert basic PID's + db_query("UPDATE ting_object + SET ding_entity_id = CONCAT('870970-basis:', SUBSTRING_INDEX(ding_entity_id, ':', -1)) + WHERE ding_entity_id LIKE '" . $agency . ":%'") + ->execute(); +} + +/** + * Update other agencies ding_entity_id's to match new data well format (basis and katalog). + */ +function ting_update_7003() { + db_query("UPDATE ting_object + SET ding_entity_id = CONCAT(SUBSTRING_INDEX(ding_entity_id, ':', 1), '-katalog:', SUBSTRING_INDEX(ding_entity_id, ':', -1)) + WHERE ding_entity_id LIKE '7%:9%' + AND ding_entity_id NOT LIKE '%-basis:%' + AND ding_entity_id NOT LIKE '%-katalog:%'") + ->execute(); + + // Convert basic PID's + db_query("UPDATE ting_object + SET ding_entity_id = CONCAT('870970-basis:', SUBSTRING_INDEX(ding_entity_id, ':', -1)) + WHERE ding_entity_id LIKE '7%:%' + AND ding_entity_id NOT LIKE '%-basis:%' + AND ding_entity_id NOT LIKE '%-katalog:%'") + ->execute(); +} + +/** + * Update all other ding_entity_id's where possible to data well version 3. + */ +function ting_update_7004() { + $translation = array( + '150021' => '150021-bibliotek', + '150027' => '150021-fjern', + '150030' => '870970-spilmedier', + '870973' => '870973-anmeld', + '150039' => '150015-forlag', + '870976' => '870976-anmeld', + '150048' => '870970-basis', + '150028' => '870970-basis', + '150015' => '870970-basis', + '150033' => '150033-dandyr', + '150040' => '150040-verdyr', + '150018' => '150018-danhist', + '150032' => '150018-samfund', + '150034' => '150018-religion', + '150054' => '150018-biologi', + '150055' => '150018-fysikkemi', + '150056' => '150056-geografi', + '150008' => '150008-academic', + '150043' => '150043-atlas', + '150023' => '150023-sicref', + '150025' => '150008-public', + '150052' => '870970-basis', + '159002' => '159002-lokalbibl', + '150012' => '150012-leksikon', + ); + + foreach ($translation as $id => $new_id) { + db_query("UPDATE ting_object + SET ding_entity_id = CONCAT('" . $new_id . ":', SUBSTRING_INDEX(ding_entity_id, ':', -1)) + WHERE ding_entity_id LIKE '" . $id . ":%'") + ->execute(); + } +} + +/** + * Update default ac.source from 'bibliotekets materialer' to 'bibliotekskatalog'. + */ +function ting_update_7005() { + variable_set('ting_reservable_sources', _ting_default_reservable_sources()); +} + +/** + * Ensure default cache expire for the data-well is set correctly. + */ +function ting_update_7006() { + $ttl = variable_get('ting_cache_lifetime', TING_DEFAULT_CACHE_LIFETIME); + if ($ttl <= 0) { + variable_set('ting_cache_lifetime', 900); + } +} + +/** + * Update series title search registry setting. + */ +function ting_update_7007() { + // Only update if set else default value will be used. + if (variable_get('ting_search_register_serie_title', FALSE)) { + variable_set('ting_search_register_serie_title', 'phrase.titleSeries = "@serietitle"'); + } +} + +/** + * Update series title search registry setting. + */ +function ting_update_7008() { + // Only update if set else default value will be used. + if (variable_get('ting_search_register_serie_title', FALSE)) { + variable_set('ting_search_register_serie_title', 'phrase.titleSeries="@serietitle"'); + } +} + +/** + * Update ting_object table - set foreign key (vid) to ting_object_revision to be null by default thus allowing + * multiple NULL values in table + */ +function ting_update_7009() { + db_drop_unique_key('ting_object', 'vid'); + db_change_field('ting_object', 'vid', 'vid', array( + 'description' => 'The current {ting_object}.vid version identifier.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => FALSE, + 'default' => NULL, + )); + db_add_unique_key('ting_object', 'vid', array('vid')); +} diff --git a/ting.make b/ting.make index d620fb7..4f950d3 100644 --- a/ting.make +++ b/ting.make @@ -3,23 +3,24 @@ core = 7.x ; Contrib -projects[nanosoap][subdir] = contrib +// The patch allows extra CURL parameters. +projects[nanosoap][subdir] = "contrib" projects[nanosoap][version] = "1.0" +projects[nanosoap][patch][] = "http://drupal.org/files/nanosoap-curloptions-1943732.patch" -projects[blackhole][subdir] = contrib -projects[blackhole][version] = "1.1" -;projects[virtual_field][subdir] = contrib -;projects[virtual_field][version] = "1.0" +projects[virtual_field][subdir] = "contrib" +projects[virtual_field][version] = "1.2" ; Libraries libraries[ting-client][download][type] = "git" -libraries[ting-client][download][url] = "git@github.com:ding2tal/ting-client.git" +libraries[ting-client][download][url] = "git@github.com:ding2/ting-client.git" +libraries[ting-client][download][branch] = "master" libraries[ting-client][destination] = "modules/ting/lib" ; Ding 2 modules projects[ding_entity][type] = "module" projects[ding_entity][download][type] = "git" -projects[ding_entity][download][url] = "git@github.com:ding2tal/ding_entity.git" -projects[ding_entity][download][branch] = "development" +projects[ding_entity][download][url] = "git@github.com:ding2/ding_entity.git" +projects[ding_entity][download][branch] = "master" diff --git a/ting.module b/ting.module index 7e7bcbe..6966848 100644 --- a/ting.module +++ b/ting.module @@ -4,13 +4,19 @@ * Enables integration with Ting. */ -define('TING_DEFAULT_CACHE_LIFETIME', 0); +// Define the different types of data that we cache in ting_set_cache and the +// default TTL. +define('TING_DEFAULT_CACHE_LIFETIME', 900); +define('TING_CACHE_TING_OBJECT', 'ting-object'); +define('TING_CACHE_TING_OBJECT_FULLTEXT', 'ting-object-fulltext'); +define('TING_CACHE_COLLECTION', 'ting-collection'); +define('TING_CACHE_REPLY', 'ting-reply'); // Load Field module hooks. module_load_include('inc', 'ting', 'ting.field'); /** - * Implementation of hook_ctools_plugin_api(). + * Implements hook_ctools_plugin_api(). */ function ting_ctools_plugin_api($module, $api) { if ($module == 'page_manager' && $api == 'pages_default') { @@ -20,16 +26,19 @@ function ting_ctools_plugin_api($module, $api) { /** - * Implements hook_ctools_plugin_directory() to let the system know - * where our task and task_handler plugins are. + * Implements hook_ctools_plugin_directory(). + * + * Lets the system know where our task and task_handler plugins are. */ function ting_ctools_plugin_directory($owner, $plugin_type) { - return 'plugins/' . $plugin_type; + return 'plugins/' . $plugin_type; } /** - * Implements hook_ctools_plugin_type() to inform the plugin system that Page - * Manager owns task, task_handler, and page_wizard plugin types. + * Implements hook_ctools_plugin_type(). + * + * Informs the plugin system that Page Manager owns task, task_handler, and + * page_wizard plugin types. * * All of these are empty because the defaults all work. */ @@ -43,6 +52,7 @@ function ting_ctools_plugin_type() { 'contexts' => array(), ); } + /** * Implements hook_menu(). */ @@ -84,7 +94,7 @@ function ting_menu() { 'page callback' => 'drupal_get_form', 'page arguments' => array('ting_admin_ting_settings'), 'access arguments' => array('administer ting settings'), - 'file' => 'ting.admin.inc' + 'file' => 'ting.admin.inc', ); $items['admin/config/ting/ranking'] = array( @@ -105,22 +115,13 @@ function ting_menu() { 'file' => 'ting.admin.inc', ); - $items['admin/config/ting/registers'] = array( - 'title' => 'Open search register settings', - 'description' => 'Configure registers to use for specific open search searches.', - 'page callback' => 'drupal_get_form', - 'page arguments' => array('ting_admin_register_settings'), - 'access arguments' => array('administer ting settings'), - 'file' => 'ting.admin.inc' - ); - $items['admin/config/ting/online_types'] = array( 'title' => 'Online types and URL labels', 'description' => 'Define online resource types and their corresponding URL labels.', 'page callback' => 'drupal_get_form', 'page arguments' => array('ting_admin_online_types_settings'), 'access arguments' => array('administer ting settings'), - 'file' => 'ting.admin.inc' + 'file' => 'ting.admin.inc', ); $items['admin/config/ting/reservable'] = array( @@ -129,7 +130,7 @@ function ting_menu() { 'page callback' => 'drupal_get_form', 'page arguments' => array('ting_admin_reservable_settings'), 'access arguments' => array('administer ting settings'), - 'file' => 'ting.admin.inc' + 'file' => 'ting.admin.inc', ); return $items; @@ -220,19 +221,40 @@ function ting_block_info() { } /** - * helper function - get relations for an ting_entity as an array ([$type] => array($relations)) + * Implements hook_cron(). + * + * Ensures that all expired entries are delete form the cache on cron runes. + */ +function ting_cron() { + cache_clear_all(NULL, 'cache_ting'); +} + +/** + * Implements hook_flush_caches(). + */ +function ting_flush_caches() { + return array('cache_ting'); +} + +/** + * Get relations for an ting_entity as an array ([$type] => array($relations)). */ function ting_get_relations($ting_entity) { - foreach( $ting_entity->relations as $relation ) { + $relations = array(); + + foreach ($ting_entity->relations as $relation) { + if ($relation->type == 'dbcaddi:hasOpenUrl' || $relation->type == 'dbcaddi:hasOnlineAccess') { + continue; + } $relations[$relation->type][] = $relation; } - // get references from ting_reference - if( module_exists('ting_reference') ) { - $refs = module_invoke('ting_reference','get_relations','ting_object',$ting_entity); - if( !empty($refs) ) { - foreach( $refs as $ref ) { - $relations[$ref->relation_type][] = $ref; + // Get references from ting_reference. + if (module_exists('ting_reference')) { + $refs = module_invoke('ting_reference', 'get_relations', 'ting_object', $ting_entity); + if (!empty($refs)) { + foreach ($refs as $ref) { + $relations[$ref->relation_type][] = $ref; } } } @@ -243,70 +265,66 @@ function ting_get_relations($ting_entity) { * Implements hook_block_view(). */ function ting_block_view($delta = '') { - $block = array(); + $block = new stdClass(); + switch ($delta) { case 'ting_relation_anchors': $items = array(); - if( $object = menu_get_object('ting_object',2) ) { + if ($object = menu_get_object('ting_object', 2)) { $relations = ting_get_relations($object); - $relation_types = module_invoke_all('anchor_info'); - $block['subject'] = array( - '#markup' => t('About the material'), - '#prefix' => '
', - '#suffix' => '
', - ); - $block['subject'] = render($block['subject']); - - foreach( $relations as $key => $relation ) { - if( isset($relation_types[$key]) ) { - $items[] = l($relation_types[$key].' ('.count($relation).') ','#'.$key,array('external'=>TRUE)); + if (!empty($relations)) { + $relation_types = module_invoke_all('ding_anchor_info'); + $block->subject = t('About the material'); + + foreach ($relations as $key => $relation) { + if (isset($relation_types[$key])) { + $items[] = l($relation_types[$key] . ' (' . count($relation) . ') ', '#' . $key, array('external' => TRUE)); + } } - } - $block['content'] = - array( - '#theme' => 'item_list', - '#items' => $items, + $block->content = array( + '#theme' => 'item_list', + '#items' => $items, ); + } } break; + case 'ting_collection_types': if ($collection = menu_get_object('ting_collection', 2)) { - $block['subject'] = array( - '#markup' => t('Types'), - '#prefix' => '
', - '#suffix' => '
', - ); - $block['subject'] = render($block['subject']); + $block->subject = t('Other materialtypes'); $items = array(); foreach ($collection->types as $type) { - $items[] = l($type . ' (' . $collection->types_count[$type] . ')', '#' . $type, array('external' => TRUE )); + $items[] = l($type . ' (' . $collection->types_count[$type] . ')', '#' . $type, array('external' => TRUE)); } - $block['content'] = array( + $block->content = array( '#theme' => 'item_list', '#items' => $items, ); } break; + case 'ting_object_types': - if (($object = menu_get_object('ting_object', 2)) && - ($collection = ting_collection_load($object->id))) { - $block['subject'] = array( - '#markup' => t('Other materialtypes'), - '#prefix' => '
', - '#suffix' => '
', - ); - $block['subject'] = render($block['subject']); + $object = menu_get_object('ting_object', 2); + if ($object && $collection = ting_collection_load($object->id)) { $items = array(); foreach ($collection->types as $type) { $uri = entity_uri('ting_collection', $collection); - $uri['options']['fragment'] = $type; + $uri['options']['fragment'] = _ting_anchor_name($type); + $uri['options']['attributes'] = array('class' => array('js-search-overlay')); $items[] = l($type . ' (' . $collection->types_count[$type] . ')', $uri['path'], $uri['options']); } - $block['content'] = array( - '#theme' => 'item_list', - '#items' => $items, - ); + // Only display block if there are more than on item. + if ($items > 1) { + $block->subject = t('Other materialtypes'); + $block->content = array( + '#theme' => 'item_list', + '#items' => $items, + ); + + // Add search overlay trigger. + drupal_add_js(drupal_get_path('module', 'ting') . '/js/ting.js'); + } } break; } @@ -335,6 +353,7 @@ function ting_element_info() { ); } + /** * Implements hook_theme(). */ @@ -363,7 +382,7 @@ function ting_ding_devel_timers() { 'ting_net' => array( 'title' => 'Ting net time was @time ms.', 'include in total' => FALSE, - ) + ), ); } @@ -494,7 +513,7 @@ function ting_ding_entity_is($object, $class) { } /** - * Implements hook_ding_entity_buttons() + * Implements hook_ding_entity_buttons(). */ function ting_ding_entity_buttons($type, $entity) { if ($entity instanceof TingEntity && $entity->online_url) { @@ -502,11 +521,12 @@ function ting_ding_entity_buttons($type, $entity) { $type = drupal_strtolower($entity->type); $label = isset($settings[$type]) && $settings[$type] ? $settings[$type] : $settings['_default']; return array( + // The link can't use l() as it will encode the url from ting_proxy. Use + // get online as the dc:identifier in $entity->online is not always the + // correct url. array( - '#prefix' => '

', - '#suffix' => '

', '#type' => 'markup', - '#markup' => l($label, $entity->online_url, array('attributes'=>array('target'=>'_new'))) + '#markup' => '' . $label . '', ), ); } @@ -516,18 +536,20 @@ function ting_ding_entity_buttons($type, $entity) { * Implements hook_page_alter(). * * Log, and display if the devel module is active and the user has - * permissions, a warning if the datawell was queried more than once. + * permissions, a warning if the data well was queried more than once. */ function ting_page_alter(&$page) { $calls = &drupal_static('ting_execute_cache'); - if (sizeof($calls) > 1) { + if (count($calls) > 5) { $calls_str = array(); foreach ($calls as $call) { $calls_str[] = print_r($calls, TRUE); } - watchdog('ting', 'Warning, ting_execute called @x times:
"@queries"', array('@x' => sizeof($calls), '@queries' => join('" "', $calls_str)), WATCHDOG_WARNING); + + watchdog('ting', 'Warning, ting_execute called @x times:
"@queries"', array('@x' => count($calls), '@queries' => implode('" "', $calls_str)), WATCHDOG_WARNING); + if (function_exists('dpm') && user_access('access devel information')) { - drupal_set_message(t('Warning, ting_execute called @x times.', array('@x' => sizeof($calls))), 'error'); + drupal_set_message(t('Warning, ting_execute called @x times.', array('@x' => count($calls))), 'error'); dpm($calls, 'ting_execute queries:'); } } @@ -544,7 +566,7 @@ function ting_object_page_view($object) { * Page callback: Display a ting collection. */ function ting_collection_page_view($object) { - if (sizeof($object->entities) < 2) { + if (count($object->entities) < 2) { drupal_goto('ting/object/' . $object->id); } return ting_collection_view($object); @@ -552,9 +574,11 @@ function ting_collection_page_view($object) { /** * Page title callback. + * + * Strips chars '<' and '>' in order to avoid HTML injections. */ function ting_page_title($object) { - return check_plain($object->title); + return str_replace('&', '&', htmlspecialchars($object->title, ENT_NOQUOTES, 'UTF-8')); } /** @@ -586,12 +610,53 @@ function ting_ting_object_load($objects) { /** * Load a ting object. * - * Don't use this, use ding_entity_load(). + * Don't use this, use ding_entity_load(). Default menu callback load of ting + * object. + * + * Handles redirect from data well version 2 to version 3. */ function ting_object_load($id) { + $agency = variable_get('ting_agency', FALSE); + if ($agency && preg_match('/^(\d{6}):(\w+)$/', $id, $matches)) { + // Matched to data well version 2, to redirect to version 3. + $new_id = ding_provider_build_entity_id($matches[2], $matches[1]); + if ($new_id === FALSE) { + $id = ting_lookup_and_translate($id); + } + else { + $id = $new_id; + } + drupal_goto('ting/object/' . $id, array(), 301); + } + return ding_entity_load($id, 'ting_object'); } + +/** + * Try to lookup well3 pid via a search on the old pid. + * Search query is constructed from old pid eg. 870971:72966643 -> '870971 and 72966643' + * + * This is NOT foolproof - if more than one result is found the first is returned, and that might not + * be the correct one + * + * @param $old_pid ( eg 870971:72966643) + * @return bool|mixed; new pid (eg 870971-tsart:72966643 ) if found; else FALSE + */ +function ting_lookup_and_translate($old_pid) { + $query_elements = explode(':', $old_pid); + $query = implode(' and ', $query_elements); + + module_load_include('client.inc', 'ting'); + $result = ting_do_search($query, 1, 1, array('facets' => array())); + if (!empty($result->collections)) { + $id = key($result->collections); + return $id; + } + return FALSE; +} + + /** * Load multiple ting objects. * @@ -604,9 +669,25 @@ function ting_object_load_multiple($ids) { /** * Load a ting collection. * - * Don't use this, use ding_collection_load(). + * Don't use this, use ding_collection_load(). Default menu callback load of + * ting collections. + * + * Handles redirect from data well version 2 to version 3. */ function ting_collection_load($id) { + $agency = variable_get('ting_agency', FALSE); + if ($agency && preg_match('/^(\d{6}):(\w+)$/', $id, $matches)) { + // Matched to data well version 2, to redirect to version 3. + $new_id = ding_provider_build_entity_id($matches[2], $matches[1]); + if ($new_id === FALSE) { + $id = ting_lookup_and_translate($id); + } + else { + $id = $new_id; + } + drupal_goto('ting/collection/' . $id, array(), 301); + } + return ding_entity_load($id, 'ting_collection'); } @@ -677,12 +758,13 @@ function ting_collection_view($object, $view_mode = 'full', $langcode = NULL) { } /** - * Sorts the objects according to type and language, but maintains the order - * of types and languages in the original array. + * Sorts the objects according to type and language. + * + * But maintains the order of types and languages in the original array. */ function _ting_type_lang_sort($objects, &$return_types) { $types = array(); - $language = array(); + $languages = array(); $sorted = array(); // Sort the objects into type buckets containing language buckets. foreach ($objects as $object) { @@ -735,12 +817,12 @@ function ting_boost_field_element_process($element, $form_state) { '#type' => 'select', '#options' => array( '' => '- ' . t('Choose') . ' -', - 'ac.source' => t('Source'), - 'dc.creator' => t('Author'), - 'dc.date' => t('Year of publish'), - 'dc.language' => t('Language'), - 'dc.type' => t('Material type'), - 'dc.identifier' => t('ISBN number'), + 'term.acSource' => t('Source'), + 'term.creator' => t('Author'), + 'term.date' => t('Year of publish'), + 'term.language' => t('Language'), + 'term.type' => t('Material type'), + 'term.identifier' => t('ISBN number'), ), '#default_value' => (isset($element['#value']['field_name'])) ? $element['#value']['field_name'] : NULL, '#attributes' => array('class' => array('field-name')), @@ -764,10 +846,13 @@ function ting_boost_field_element_process($element, $form_state) { return $element; } +/** + * Validate the boost form. + */ function boost_weight_validate($element, &$form_state) { - if ( !empty($element['#value']) && ($element['#value'] != (string)(int)$element['#value']) ) { - form_error($element, t('Boost weight is an integer.')); - } + if (!empty($element['#value']) && (is_int($element['#value']))) { + form_error($element, t('Boost weight is an integer.')); + } } /** @@ -793,12 +878,12 @@ function ting_ranking_field_element_process($element, $form_state) { '#type' => 'select', '#options' => array( '' => '- ' . t('Choose') . ' -', - 'ac.source' => t('Source'), - 'dc.title' => t('Title'), - 'dc.creator' => t('Author'), - 'dc.subject' => t('Subject'), - 'dc.date' => t('Year of publish'), - 'dc.type' => t('Material type'), + 'term.acSource' => t('Source'), + 'term.title' => t('Title'), + 'term.creator' => t('Author'), + 'term.subject' => t('Subject'), + 'term.date' => t('Year of publish'), + 'term.type' => t('Material type'), ), '#default_value' => (isset($element['#value']['field_name'])) ? $element['#value']['field_name'] : NULL, '#attributes' => array('class' => array('field-name')), @@ -837,6 +922,7 @@ function _ting_default_online_types() { 'periodikum (net)', 'pc-spil (net)', 'avis (net)', + 'e-node', ); } @@ -847,7 +933,7 @@ function _ting_default_online_types() { */ function _ting_default_reservable_sources() { return array( - 'bibliotekets materialer', + 'bibliotekskatalog', ); } @@ -858,34 +944,48 @@ function _ting_default_reservable_sources() { */ function _ting_default_reservable_types() { return array( - 'billedbog', - 'blu-ray disc', 'bog', + 'node', + 'dvd', + 'billedbog', + 'cd (musik)', + 'lydbog (cd)', + 'lydbog (bånd)', + 'tegneserie', + 'lydbog (cd-mp3)', + 'sammensat materiale', 'cd', + 'bog stor skrift', + 'video', + 'blu-ray', 'cd-rom', - 'diskette', - 'dvd', + 'pc-spil', + 'playstation 3', + 'xbox 360', + 'wii', + 'playstation 2', + 'playstation 4', + 'graphic novel', + 'nintendo ds', 'dvd-rom', - 'film', + 'kort', + 'xbox', + 'gameboy advance', + 'wii u', 'grammofonplade', - 'kassettelydbånd', - 'lydbog (cd)', - 'lydbog (cd-mp3)', - 'playstation2-spil', - 'playstation3-spil', - 'playstation-spil', - 'puslespil', + 'playstation', + 'lydbog', 'spil', - 'tegneserie', - 'video', - 'wii-spil', - 'xbox-spil', - 'årbog', + 'puslespil', + 'diskette', ); } /** * Fetch known types from the datawell. + * + * @return array + * Array with data well material types. */ function _ting_fetch_well_types() { // Get a list of types by executing a null query and look at the facets @@ -897,7 +997,7 @@ function _ting_fetch_well_types() { 'sort' => 'random', ); module_load_include('client.inc', 'ting'); - $result = ting_do_search("*=*", 0, 0, $options); + $result = ting_do_search("*", 0, 0, $options); $types = array(); foreach ($result->facets['facet.type']->terms as $term => $count) { @@ -908,10 +1008,15 @@ function _ting_fetch_well_types() { if (!empty($types)) { variable_set('ting_well_types', $types); } + + return $types; } /** * Fetch known sources from the datawell. + * + * @return array + * Array with the sources. */ function _ting_fetch_well_sources() { // Get a list of sources by executing a null query and look at the facets @@ -923,7 +1028,7 @@ function _ting_fetch_well_sources() { 'sort' => 'random', ); module_load_include('client.inc', 'ting'); - $result = ting_do_search("*=*", 0, 0, $options); + $result = ting_do_search("*", 0, 0, $options); $sources = array(); foreach ($result->facets['facet.acSource']->terms as $term => $count) { @@ -934,4 +1039,23 @@ function _ting_fetch_well_sources() { if (!empty($sources)) { variable_set('ting_well_sources', $sources); } + + return $sources; +} + +/** + * Get anchor name from collection type. + * + * @param string $type + * Name of the anchor. + * + * @return string + * HTML safe name. + */ +function _ting_anchor_name($type) { + $name = str_replace(array('(', ')'), '', $type); + $name = str_replace(' ', '-', $name); + $name = str_replace(':', '', $name); + $name = strtolower($name); + return $name; } diff --git a/ting.test b/ting.test index 0f90065..fc6c77a 100644 --- a/ting.test +++ b/ting.test @@ -18,8 +18,6 @@ class TingEntityTestCase extends DrupalWebTestCase { parent::setUp(array('ding_entity', 'ting', 'nanosoap')); variable_set('ting_agency', '100200'); variable_set('ting_search_url', 'http://opensearch.addi.dk/next_2.0/'); - variable_set('ting_scan_url', 'http://openscan.addi.dk/1.5/'); - variable_set('ting_spell_url', 'http://openspell.addi.dk/1.2/'); variable_set('ting_recommendation_url', 'http://openadhl.addi.dk/1.1/'); variable_set('ting_search_profile', 'opac'); } diff --git a/ting.theme.inc b/ting.theme.inc index 6be71cf..aa6e51a 100644 --- a/ting.theme.inc +++ b/ting.theme.inc @@ -1,5 +1,4 @@ $val ) { + if (isset($variables['content']['entities'])) { + foreach ($variables['content']['entities'] as $key => $val) { $variables['content']['overview']['types'][] = $key; } } + + // Modify 'alt' attribute for list items. + if (!empty($variables['content']['ting_cover']) && !empty($variables['content']['group_collection_list'])) { + $variables['content']['ting_cover'][0]['#alt'] = implode(' ', array( + $variables['object']->title, + $variables['object']->type, + $variables['object']->date, + )); + } } /** diff --git a/ting_object.tpl.php b/ting_object.tpl.php index 1af702e..a063d58 100644 --- a/ting_object.tpl.php +++ b/ting_object.tpl.php @@ -1,8 +1,6 @@