diff --git a/Drupal module Setup.docx b/Drupal module Setup.docx new file mode 100644 index 0000000..a39b22c Binary files /dev/null and b/Drupal module Setup.docx differ diff --git a/ebsco/css/ebsco.css b/ebsco/css/ebsco.css new file mode 100644 index 0000000..022cdc2 --- /dev/null +++ b/ebsco/css/ebsco.css @@ -0,0 +1,431 @@ +/* +* The styles for EBSCO module +*/ +@CHARSET "UTF-8"; + +/** General ***/ + +.floatleft { + float: left; +} + +.floatright { + float: right; +} + +.clear { + clear: both; +} + +.offscreen { + display: none; +} + +.spinner { + width: 16px; + height: 16px; + background: url("../images/ajax_loading.gif") no-repeat left top; + display: none; +} + +.highlight { + font-weight: bold; +} + + +/** Search list ***/ + +.ebsco .result { + width: 650px; +} + +.ebsco .record-number { + margin-right: 10px; + min-width: 10px; +} + +.ebsco .span-2 { + width: auto; + max-width: 70px; + margin-right: 10px; + float: left; +} + +.ebsco .span-9 { + float: none; + overflow: auto; + width: auto; + margin-right: 0; +} + +.ebsco .pagination { + margin-bottom: 0; + padding: 0.2em +} + +.ebsco p.submit { + padding: 10px 0; +} + +.ebsco .jumpMenu { + max-width: 130px; +} + +.ebsco ul.custom-links { + list-style-type: none; + margin: 0; + padding: 0; +} + +.ebsco ul.custom-links li { + display: inline-block; + padding-left: 0; + padding-right: 10px; + margin-left: 10px; + border-right: 1px solid #CCCCCC; +} + +.ebsco ul.custom-links li:first-child { + margin-left: 0; +} + +.ebsco ul.custom-links li:last-child { + padding-right: 0; + border-right: 0 none; +} + + + +/** Side facets **/ + +#block-ebsco-ebsco-facets h2 { + font-weight: bold; +} + +#block-ebsco-ebsco-facets { + padding: 5px !important; +} + +#block-ebsco-ebsco-facets dl { + margin: 5px 0; + padding: 0; +} + +#block-ebsco-ebsco-facets dl dt { + font-size: 115%; + padding: 5px; + background-color: #eeeeee; +} + +#block-ebsco-ebsco-facets dd { + font-weight: normal !important; + margin: 0; + padding: 5px !important; + border-bottom: 1px solid #dddddd; +} + +#block-ebsco-ebsco-facets dd.submit input { + margin: 0; +} + +#block-ebsco-ebsco-facets dl dd:last-child { + border-bottom: none; +} + +#block-ebsco-ebsco-facets dd label { + font-weight: normal; + font-family: Arial; + padding: 0 3px; +} + +#block-ebsco-ebsco-facets dd label { + display: inline; +} + +#block-ebsco-ebsco-facets dl.expandable:hover { + cursor: pointer; +} + +#block-ebsco-ebsco-facets dl.expandable:hover dt span { + color: #444444; +} + +#block-ebsco-ebsco-facets .narrow-list.filters { + background-color: orange; +} + +#block-ebsco-ebsco-facets .narrow-list.filters a { + color: white; +} + + + +/** Detailed view ***/ + +.ebsco-record .toolbar { + border-bottom: 1px solid #EEEEEE; + margin-bottom: 1em; + min-height: 2em; + padding-left: 1em; +} + +.ebsco-record.push-5 { + margin: 0; +} + +.ebsco-record .span-13 { + min-width: 540px; + max-width: 690px; + width: auto; + float: left; +} + +.ebsco-record .span-13 table { + margin-top: 0; +} + +.ebsco-record .span-13 table tr td { + word-break: break-all; +} + +.ebsco-record .span-4 { + max-width: 150px; + min-width: 80px; + width: auto; + margin: 0 0 0 10px; + text-align: center; + float: left; +} + +.ebsco-record .external-links { + border: 1px solid #eeeeee; + list-style-type: none; + margin: 10px 0; + padding: 0; +} + +.ebsco-record .external-links li { + display: inline-block; + padding-left: 0; + padding-right: 10px; + margin-left: 10px; + border-right: 1px solid #CCCCCC; + line-height: 32px; + margin: 5px; +} + +.ebsco-record .external-links li:last-child { + border-right: 0 none; +} + +.external-link img { + vertical-align: middle; + padding-right: 5px; +} + +.top-login-message { + margin-top:10px; + width: 100%; + text-align: center; +} + +.ebsco-record .record { + width: 100%; + padding: 0; +} + +.ebsco-record .book-jacket { + max-width: 150px; +} + +.ebsco-record .html { + background-color: white; + padding:10px 0; + border: 0; +} + + +/** Basic search form ***/ + +#ebsco-basic-search-form .form-item-lookfor, +#ebsco-basic-search-form .form-item-type { + display: inline !important; +} + +#ebsco-basic-search-form #edit-links { + display: inline; + margin: 0; + padding: 0; + border: none 0; + position: static; +} + +#ebsco-basic-search-form #edit-offscreen { + display: none; +} + +#ebsco-basic-search-form .form-item-remember label { + font-weight: bold; +} + +#ebsco-sort-form { + margin : 10px 0; +} + +#ebsco-sort-form > div { + border-bottom: 1px solid #CCCCCC; +} + +#ebsco-sort-form label, +#ebsco-sort-form .form-item { + display: inline-block !important; +} + +#ebsco-sort-form .form-item:first-child { + float: right; +} + +#ebsco-sort-form .form-item:last-child { + float: left; +} + +/** Advanced search form ***/ + +.ebsco-advanced ._advanced-row { + border: 0 none; + padding: 0; + margin: 10px; + top: 5px; +} + +.ebsco-advanced #edit-rows { + margin-bottom: 20px; +} + +.ebsco-advanced ._advanced-row .fieldset-wrapper { + margin: 0 !important; + padding: 0; +} + +.ebsco-advanced .form-item-group0-lookfor label { + display: inline-block; + text-align: right; + width: 100px; +} +.ebsco-advanced .form-type-radio label { + display: inline-block; + text-align: left; + width: 90%; +} + +.ebsco-advanced #edit-add-row { + border: 0 none; + padding: 0; + margin: 10px; + top: 5px; +} + +.ebsco-advanced #edit-add-row .fieldset-wrapper { + margin-top: 5px; +} + +.ebsco-advanced #edit-links { + margin-bottom: 20px; + text-align: center; +} + +.ebsco-advanced #edit-links .fieldset-wrapper { + margin-top: 10px; +} + +.ebsco-advanced #edit-limiters { + width: 99%; +} + +.ebsco-advanced #edit-modes { + width: 49%; + float: left; +} + +.ebsco-advanced #edit-modes { + width: 49%; + float: left; +} + + +.ebsco-advanced #edit-expanders { + width: 49%; + float: right; +} + +.ebsco-advanced #edit-limiters label { + font-weight: normal; +} + +.ebsco-advanced #edit-limiters .form-type-select label { + display: block; +} + +.ebsco-advanced #edit-limiters hr { + margin: 15px 0; + height: 1px; + background-color: #CCCCCC; + color: #CCCCCC; + border: 0 none; +} + +.dateSlider { + width: 150px; + display: inline-block !important; + margin: 0 10px; +} + + +/** Icons ***/ + +.icon { + background: url("../images/sprites_32.png") no-repeat top left; + height: 32px; + line-height: 32px; + display: inline-block; + padding: 0 0 0 36px !important; +} + +.icon.ebook { + background-position: 0 0; +} + +.icon.html { + background-position: 0 -42px; +} + +.icon.pdf { + background-position: 0 -84px; +} + +.icon13 { + background: url("../images/sprites_32.png") no-repeat top left; + padding-left: 18px !important; + width: 13px; + height: 13px; +} + +.icon13.collapsed { + background-position: 0 -126px; +} + +.icon13.expanded { + background-position: 0 -149px; +} + +.icon16 { + background: url("../images/sprites_32.png") no-repeat top left; + padding-left: 21px !important; + width: 16px; + height: 16px; + display: inline-block; +} + +.icon16.tick { + background-position: 0 -171px; +} diff --git a/ebsco/ebsco.info b/ebsco/ebsco.info new file mode 100644 index 0000000..5dd95e0 --- /dev/null +++ b/ebsco/ebsco.info @@ -0,0 +1,8 @@ +name = EBSCO Discovery Service +description = Full-text articles and eBooks from EBSCOhost Discovery Service +core = 7.x + +configure = admin/config/search/ebsco + +stylesheets[screen][] = css/ebsco.css +scripts[] = js/ebsco.js \ No newline at end of file diff --git a/ebsco/ebsco.module b/ebsco/ebsco.module new file mode 100644 index 0000000..cada084 --- /dev/null +++ b/ebsco/ebsco.module @@ -0,0 +1,1036 @@ +'. t("Full-text articles and eBooks from EBSCOhost Discovery Services") .''; + $output .= '

'. t("EBSCO Discovery Service provides users with an easy, yet powerful means of accessing all of + an institution's information resources through a single search.") .'

'; + $output .= '

'. t("This is achieved by harvesting metadata from both internal (library) and external (database vendors) sources, + and creating a pre-indexed service of unprecedented size and speed.") .'

'; + $output .= '

'. t("Although the resulting collection can be massive in size and scope, the fact that it is indexed locally + (on the EBSCOhost® servers) allows for exceptionally fast search response times.") .'

'; + $output .= '

'. t("As no two institutions are the same, EBSCO Discovery Service offers a vast array of customization options with + regard to both the underlying collection of metadata as well as the front-end delivery of search results. + All of this functionality is based upon the powerful EBSCOhost search experience familiar to researchers worldwide.") .'

'; + break; + } + return $output; +} + + +/** + * Implements hook_theme(). + */ +function ebsco_theme() { + $themes = array( + 'ebsco_result' => array( + 'template' => 'templates/ebsco-result' + ), + 'ebsco_results' => array( + 'template' => 'templates/ebsco-results' + ), + 'ebsco_side_facets' => array( + 'template' => 'templates/ebsco-side-facets' + ), + 'ebsco_basic_search' => array( + 'template' => 'templates/ebsco-basic-search' + ), + 'ebsco_advanced_search' => array( + 'template' => 'templates/ebsco-advanced-search' + ) + ); + return $themes; +} + + +/** + * Implements hook_permission(). + * + * Since the access to our new custom pages will be granted based on + * special permissions, we need to define what those permissions are here. + * This ensures that they are available to enable on the user role + * administration pages. + */ +function ebsco_permission() { + return array( + 'administer ebsco' => array( + 'title' => t('Administer EBSCO') + ), + 'use ebsco' => array( + 'title' => t('Use EBSCO') + ) + ); +} + + +/** + * Implements hook_admin(). + * + */ +function ebsco_admin() { + $form = array(); + + $form['ebsco_credentials'] = array( + '#type' => 'fieldset', + '#title' => t('API credentials') + ); + + $form['ebsco_credentials']['ebsco_password'] = array( + '#type' => 'textfield', + '#title' => t('Password'), + '#default_value' => variable_get('ebsco_password'), + '#size' => 50, + '#description' => t("The API password."), + '#required' => TRUE + ); + + $form['ebsco_credentials']['ebsco_user'] = array( + '#type' => 'textfield', + '#title' => t('User Id'), + '#default_value' => variable_get('ebsco_user'), + '#size' => 50, + '#description' => t("The API User Id."), + '#required' => TRUE + ); + + $form['ebsco_credentials']['ebsco_profile'] = array( + '#type' => 'textfield', + '#title' => t('Profile Id'), + '#default_value' => variable_get('ebsco_profile'), + '#size' => 50, + '#description' => t("The API Profile Id."), + '#required' => TRUE + ); + + $form['ebsco_credentials']['ebsco_interface'] = array( + '#type' => 'textfield', + '#title' => t('Interface Id'), + '#size' => 50, + '#description' => t("The API Interface Id."), + '#required' => FALSE + ); + + $form['ebsco_credentials']['ebsco_organization'] = array( + '#type' => 'textfield', + '#title' => t('Organization Id'), + '#size' => 50, + '#description' => t("The API Organization Id."), + '#required' => FALSE + ); + + $form['ebsco_credentials']['ebsco_guest'] = array( + '#type' => 'radios', + '#title' => t('Guest ?'), + '#default_value' => variable_get('ebsco_guest', 0), + '#description' => t("The Guest session."), + '#options' => array(t('No'), t('Yes')), + '#required' => TRUE + ); + + $form['ebsco_general'] = array( + '#type' => 'fieldset', + '#title' => t('General Settings') + ); + + $form['ebsco_general']['ebsco_default_limit'] = array( + '#type' => 'select', + '#title' => t('Default limit'), + '#default_value' => variable_get('ebsco_default_limit', 10), + '#description' => t("Default number of results per page."), + '#options' => EBSCODocument::limit_options(), + '#required' => TRUE + ); + + $form['ebsco_general']['ebsco_default_sort'] = array( + '#type' => 'select', + '#title' => t('Default sort'), + '#default_value' => variable_get('ebsco_default_sort', 'relevance'), + '#description' => t("Default sorting option."), + '#options' => EBSCODocument::sort_options(), + '#required' => TRUE + ); + + $form['ebsco_general']['ebsco_default_amount'] = array( + '#type' => 'select', + '#title' => t('Default detail level'), + '#default_value' => variable_get('ebsco_default_amount', 'detailed'), + '#description' => t("Default level of data detail."), + '#options' => EBSCODocument::amount_options(), + '#required' => TRUE + ); + + $form['ebsco_general']['ebsco_default_mode'] = array( + '#type' => 'select', + '#title' => t('Default search mode'), + '#default_value' => variable_get('ebsco_default_mode', 'all'), + '#description' => t("Default search mode."), + '#options' => EBSCODocument::mode_options(), + '#required' => TRUE + ); + + return system_settings_form($form); +} + + +/** + * Implements hook_menu(). + */ +function ebsco_menu() { + // Ths is a route + $items['ebsco/results'] = array( + 'title' => 'EBSCO Results', + 'page callback' => 'ebsco_results_page', + 'access callback' => true, + 'type' => MENU_CALLBACK + ); + + $items['ebsco/result'] = array( + 'title' => 'EBSCO Record', + 'page callback' => 'ebsco_result_page', + 'access callback' => true, + 'type' => MENU_CALLBACK + ); + + $items['ebsco/pdf'] = array( + 'title' => 'EBSCO Record', + 'page callback' => 'ebsco_pdf_page', + 'access callback' => true, + 'type' => MENU_CALLBACK + ); + + $items['ebsco/fulltext'] = array( + 'title' => 'EBSCO Record', + 'page callback' => 'ebsco_fulltext_page', + 'access callback' => true, + 'type' => MENU_CALLBACK + ); + + $items['ebsco/advanced'] = array( + 'title' => 'EBSCO advanced search', + 'page callback' => 'ebsco_advanced_search_page', + 'access callback' => true, + 'type' => MENU_CALLBACK + ); + + $items['admin/config/search/ebsco'] = array( + 'title' => 'EBSCO settings', + 'description' => 'Configure EBSCO Service.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('ebsco_admin'), + 'access arguments' => array('administer ebsco'), + 'type' => MENU_NORMAL_ITEM + ); + + return $items; +} + + +/** +* Implements hook_block_info(). +*/ +function ebsco_block_info() { + $blocks['ebsco_main'] = array( + 'info' => t('EBSCO Search Form'), + 'cache' => DRUPAL_CACHE_PER_PAGE + ); + + $blocks['ebsco_facets'] = array( + 'info' => t('EBSCO Narrow Search'), + 'cache' => DRUPAL_CACHE_PER_PAGE + ); + + return $blocks; +} + + +/** +* Implements hook_block_view(). +* +* Prepares the content of the block. +*/ +function ebsco_block_view($delta = '') { + $params = $_GET; + switch ($delta) { + case 'ebsco_main': + $reject = isset($params['q']) && strpos('ebsco/advanced', $params['q']) !== false; + return array( + 'content' => $reject ? '' : ebsco_basic_search_block() + ); + break; + + case 'ebsco_facets': + $reject = isset($params['q']) && strpos('ebsco/advanced', $params['q']) !== false; + $reject = $reject || (isset($params['edit']) && strpos('ebsco/results', $params['q']) !== false); + $reject = $reject || (isset($params['q']) && strpos('ebsco/result', $params['q']) !== false); + return array( + 'content' => $reject ? '' : ebsco_side_facets_block() + ); + break; + } +} + + +/****************************************************** + * Page callbacks + ******************************************************/ + + +function ebsco_results_page() { + return theme('ebsco_results'); +} + + +function ebsco_result_page() { + $is_xhr = !empty($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest'; + if ($is_xhr) { + print theme('ebsco_result'); + return true; + } else { + return theme('ebsco_result'); + } +} + + +function ebsco_fulltext_page() { + $is_xhr = !empty($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest'; + if (user_is_logged_in()) { + if ($is_xhr) { + print theme('ebsco_result'); + return true; + } else { + return theme('ebsco_result'); + } + } else { + $_SESSION['EBSCO']['redirect'] = drupal_get_destination(); + if ($is_xhr) { + echo ""; + return; + } else { + drupal_goto('user'); + } + } +} + + +function ebsco_pdf_page() { + global $Document; + $params = $_REQUEST; + + if (user_is_logged_in()) { + if (empty($Document)) { + $Document = new EBSCODocument(); + } + $Document->retrieve(); + $record = $Document->record(); + drupal_goto($record->pdf_link); + } else { + $_SESSION['EBSCO']['redirect'] = drupal_get_destination(); + drupal_goto('user'); + } +} + + +function ebsco_advanced_search_page() { + return theme('ebsco_advanced_search'); +} + + +function ebsco_basic_search_block() { + return theme('ebsco_basic_search'); +} + + +function ebsco_side_facets_block() { + return theme('ebsco_side_facets'); +} + + + +/****************************************************** + * Form builders, form handlers and templates handlers + ******************************************************/ + + +/** + * Form builder; Output a sort form for the search results. + * + * @ingroup forms + */ +function ebsco_sort_form() { + $params = $_REQUEST; + + $form = array( + '#attributes' => array('class' => 'sort-form'), + '#method' => 'get' + ); + + $options = EBSCODocument::amount_options(); + $values = array_values($options); + $default_value = isset($params['amount']) ? $params['amount'] : $values[0]; + $form['mode'] = array( + '#id' => 'ebsco-amount', + '#type' => 'select', + '#title' => t('Page options'), + '#default_value' => $default_value, + '#options' => $options, + '#attributes' => array('class' => array('form-select', '_jump_menu')) + ); + + $options = EBSCODocument::sort_options(); + $values = array_values($options); + $default_value = isset($params['sort']) ? $params['sort'] : $values[0]; + $form['sort'] = array( + '#id' => 'ebsco-sort', + '#type' => 'select', + '#title' => t('Sort'), + '#default_value' => $default_value, + '#options' => $options, + '#attributes' => array('class' => array('form-select', '_jump_menu')) + ); + + return $form; +} + + +/** + * Form builder; Output a search form for the search block's search box. + * + * @ingroup forms + */ +function ebsco_basic_search_form() { + $params = $_REQUEST; + + $form = array( + '#attributes' => array('class' => 'search-form'), + '#action' => url('ebsco/results'), + '#method' => 'get' + ); + + $form['basic']['q'] = array( + '#type' => 'hidden', + '#default_value' => 'ebsco/results' + ); + + $default_value = isset($params['lookfor']) ? $params['lookfor'] : ''; + $form['basic']['lookfor'] = array( + '#id' => 'ebsco-basic-search-lookfor', + '#type' => 'textfield', + '#size' => 40, + '#default_value' => $default_value, + '#attributes' => array('title' => t('Enter the terms you wish to search for.')), + ); + + $default_value = isset($params['type']) ? $params['type'] : ''; + $form['basic']['type'] = array( + '#id' => 'ebsco-basic-search-type', + '#type' => 'select', + '#options' => EBSCODocument::basic_search_type_options(), + '#default_value' => $default_value + ); + + $form['basic']['submit'] = array( + '#id' => 'ebsco-basic-search-submit', + '#type' => 'submit', + '#value' => t('Search') + ); + + $link = url('ebsco/advanced'); + $form['basic']['links'] = array( + '#type' => 'fieldset', + '#value' => "" . t('Advanced search') . "" + ); + + $form['basic']['offscreen'] = array( + '#type' => 'container' + ); + + if (isset($params['filter'])) { + foreach($params['filter'] as $key => $value) { + $form['basic']['offscreen']['group'.$key]['filter[]'] = array( + '#id' => 'ebsco-basic-search-filter' . $key, + '#type' => 'checkbox', + '#default_value' => $value, // doesn't work + '#attributes' => array('checked' => 'checked', 'value' => $value) + + ); + } + + $form['basic']['remember'] = array( + '#type' => 'checkbox', + '#title' => t('Retain my current filters'), + '#prefix' => '
', + '#default_value' => true + ); + } + + return $form; +} + +/** + * Form builder; Output a search form for the search block's search box. + * + * @ingroup forms + */ +function ebsco_advanced_search_form() { + global $Document; + $params = $_REQUEST; + + if (isset($params['edit']) && !empty($params['edit'])) { + $query = $_SESSION['EBSCO']['last-search']['query']; + parse_str($query, $new_params); + $params = $_REQUEST = array_merge($_REQUEST, $new_params); + } + $counter = isset($params['group']) ? count($params['group']) : 3; + + $form = array( + '#attributes' => array('class' => 'search-form'), + '#action' => url('ebsco/advanced') + ); + + $form['advanced'] = array( + '#type' => 'fieldset', + '#title' => t('Advanced Search'), + '#collapsible' => false, + '#collapsed' => false + ); + + $form['advanced']['rows'] = array( + '#type' => 'fieldset', + '#title' => '' + ); + + for ($i = 0; $i < $counter; $i++) { + $form['advanced']['rows']['group' . $i] = array( + '#type' => 'fieldset', + '#tree' => true, + '#attributes' => array('class' => array('_advanced-row')) + ); + + $default_value = isset($params['group'][$i]['bool']) ? $params['group'][$i]['bool'] : ''; + if ($i > 0) { + $form['advanced']['rows']['group' . $i]['bool'] = array( + '#id' => 'ebsco-advanced-search-bool' . $i, + '#type' => 'select', + '#default_value' => $default_value, + '#options' => EBSCODocument::bool_options() + ); + } else { + $form['advanced']['rows']['group' . $i]['bool'] = array( + '#id' => 'ebsco-advanced-search-bool' . $i, + '#type' => 'hidden', + '#default_value' => 'AND' + ); + } + + $title = $i == 0 ? t('Search for :') : ''; + $default_value = isset($params['group'][$i]['lookfor']) ? $params['group'][$i]['lookfor'] : ''; + $form['advanced']['rows']['group' . $i]['lookfor'] = array( + '#id' => 'ebsco-advanced-search-lookfor' . $i, + '#type' => 'textfield', + '#size' => 30, + '#default_value' => $default_value, + '#title' => $title, + '#attributes' => array('title' => t('Enter the terms you wish to search for.')) + ); + + $default_value = isset($params['group'][$i]['type']) ? $params['group'][$i]['type'] : ''; + $form['advanced']['rows']['group' . $i]['type'] = array( + '#id' => 'ebsco-advanced-search-type' . $i, + '#type' => 'select', + '#default_value' => $default_value, + '#title' => t('in'), + '#options' => EBSCODocument::advanced_search_type_options() + ); + + if ($i > 2) { + $form['advanced']['rows']['group' . $i]['remove'] = array( + '#markup' => '' + ); + } + } + + $form['advanced']['rows']['add_row'] = array( + '#type' => 'fieldset', + '#value' => "". t('Add Search Field') . "" + ); + + $items = array( + array( + 'url' => url('ebsco/results'), + 'title' => t('Basic search') + ), + array( + 'url' => url('ebsco/advanced'), + 'title' => t('Advanced search') + ) + ); + $links = array(); + foreach ($items as $item) { + $links[] = "{$item['title']}"; + } + $form['advanced']['links'] = array( + '#type' => 'fieldset', + '#value' => implode(' | ', $links) + ); + + $form['advanced']['modes'] = array( + '#type' => 'fieldset', + '#title' => t('Search modes') + ); + + $form['advanced']['modes']['mode'] = array( + '#type' => 'radios', + '#default_value' => 'all', + '#options' => EBSCODocument::mode_options() + ); + + $expanders = $Document->expanders(); + $limiters = $Document->limiters(); + $options = array('' => true); + foreach($expanders as $key => $expander) { + $options[$expander['Action']] = true; + } + foreach($limiters as $key => $limiter) { + if (!empty($limiter['Values'])) { + $options[$limiter['Id']] = true; + foreach($limiter['Values'] as $value) { + $options[str_replace('value', $value['Value'], $value['Action'])] = true; + } + } else { + $options[str_replace('value', 'y', $limiter['Action'])] = true; + } + } + + $form['advanced']['expanders'] = array( + '#type' => 'fieldset', + '#title' => t('Expand results') + ); + + $form['advanced']['limiters'] = array( + '#type' => 'fieldset', + '#title' => t('Limit results') + ); + + $children = array(); + foreach($expanders as $key => $expander) { + $element = array( + '#type' => 'checkbox', + '#id' => 'ebsco-advanced-search-expander' . $key, + '#title' => $expander['Label'], + '#title_display' => 'after', + '#name' => 'filter[]', + '#attributes' => array('value' => $expander['Action']), + '#checked' => $expander['selected'] + ); + $element['#children'] = ($key != 0) ? '
' : ''; + $element['#children'] .= theme('checkbox', array('element' => $element)); + + $children[] = theme('form_element', array('element' => $element)); + + } + + $form['advanced']['expanders']['filter'] = array( + '#type' => 'checkboxes', + '#validated' => true, + '#options' => $options, + '#children' => implode('', $children) + ); + + $checkboxes = $selects = $dates = array(); + foreach($limiters as $key => $limiter) { + if ($limiter['Type'] == 'text' || $limiter['Type'] == 'select') { + $element = array( + '#type' => 'checkbox', + '#id' => 'ebsco-advanced-search-limiter' . $limiter['Id'], + '#title' => $limiter['Label'], + '#title_display' => 'after', + '#name' => 'filter[]', + '#attributes' => array('value' => str_replace('value', 'y', $limiter['Action'])), + '#checked' => $limiter['selected'] + + ); + $element['#children'] = ($key != 0) ? '
' : ''; + if (!isset($element['#children'])) + {$element['#children']="";} + $element['#children'] .= theme('checkbox', array('element' => $element)); + $checkboxes[] = theme('form_element', array('element' => $element)); + } else if ($limiter['Type'] == 'multiselectvalue') { + $opts = array('' => t('All')); + foreach($limiter['Values'] as $value) { + $opts[$value['Action']] = $value['Value']; + } + $element = array( + '#type' => 'select', + '#multiple' => true, + '#size' => 4, + '#id' => 'ebsco-advanced-search-limiter' . $limiter['Id'], + '#title' => $limiter['Label'], + '#name' => 'filter[]', + '#options' => $opts, + '#default_value' => $limiter['selected'], // empty value means "All" + '#value' => $limiter['selected'], + '#attributes' => array('class' => array('multiselectvalue'), 'multiple' => 'multiple') + ); + $element['#children'] = theme('select', array('element' => $element)); + $selects[] = theme('form_element', array('element' => $element)); + } else if ($limiter['Type'] == 'ymrange') { + $value = $limiter['selected'] ? $limiter['selected'] : ''; + $displayValue = str_replace(array('addlimiter(DT1:', '-1/2013-1)'), array('', ''), $value); + $element = array( + '#type' => 'textfield', + '#id' => $limiter['Id'], + '#title' => $limiter['Label'], + '#value' => $displayValue, + '#name' => $limiter['Id'], + '#autocomplete_path' => null, + '#attributes' => array('size' => 4, 'maxlength' => 4, 'class' => array('yearbox')) + ); + $element['#children'] = theme('textfield', array('element' => $element)); + $element['#children'] .= ''; + $element['#children'] .= '
'; + $dates[] = theme('form_element', array('element' => $element)); + } + } + + $form['advanced']['limiters']['checkboxes'] = array( + '#type' => 'checkboxes', + '#validated' => true, + '#options' => $options, + '#children' => implode('', $checkboxes), + ); + + $form['advanced']['limiters']['dates'] = array( + '#type' => 'container', + '#validated' => true, + '#prefix' => '
', + '#children' => implode('', $dates) + ); + + $form['advanced']['limiters']['selects'] = array( + '#type' => 'container', + '#prefix' => '
', + '#validated' => true, + '#children' => implode('', $selects) + ); + + $form['advanced']['submit'] = array( + '#id' => 'ebsco-advanced-search-submit', + '#type' => 'submit', + '#value' => t('Search') + ); + + $form['advanced']['clear'] = array( + '#markup' => '' + ); + + return $form; +} + + +/** + * Form validation handler for ebsco_basic_search_form(). + * + * @see ebsco_basic_search_form_submit() + */ +function ebsco_basic_search_form_validate($form, &$form_state) { + $params = $form_state['values']; + if (empty($params['lookfor'])) { + form_set_error('lookfor', t('Please enter some keywords.')); + } +} + + +/** + * Form validation handler for ebsco_advanced_search_form(). + * + * @see ebsco_advanced_search_form_submit() + */ +function ebsco_advanced_search_form_validate($form, &$form_state) { + $params = $form_state['values']; + if (empty($params['group0']['lookfor']) && + empty($params['group1']['lookfor']) && + empty($params['group2']['lookfor'])) { + form_set_error('group0][lookfor', t('Please enter some keywords.')); + } +} + + +/** + * Form submission handler for ebsco_basic_search_form(). + * + * @see ebsco_basic_search_form_validate() + */ +function ebsco_basic_search_form_submit($form, &$form_state) { + $params = $form_state['values']; + $allowed_keys = array('lookfor', 'type'); + foreach($params as $key => $value) { + if (!in_array($key, $allowed_keys)) { + unset($params[$key]); + } + } + $form_state['rebuild'] = false; + $form_state['redirect'] = array('ebsco/results', array('query' => $params)); +} + + +/** + * Form submission handler for ebsco_advanced_search_form(). + * + * @see ebsco_advanced_search_form_validate() + */ +function ebsco_advanced_search_form_submit($form, &$form_state) { + $params = $_REQUEST; + $new_params = array(); + $allowed_keys = array('filter', 'mode'); + + foreach($params as $key => $value) { + if (!(in_array($key, $allowed_keys) || strpos($key, 'group') !== false)) { + unset($params[$key]); + } else { + if ($key == 'filter') { + foreach($value as $k => $v) { + if (empty($v)) { + unset($params[$key][$k]); + } + } + } else if (empty($value)) { + unset($params[$key]); + } + } + } + + foreach($params as $key => $value) { + if (strpos($key, 'group') !== false) { + $new_params['group'][] = $value; + unset($params[$key]); + } + } + $params = array_merge($params, $new_params); + + $form_state['rebuild'] = false; + $form_state['redirect'] = array('ebsco/results', array('query' => $params)); +} + + +/** + * Process variables for ebsco-results.tpl.php. + * + * @see ebsco-results.tpl.php + */ +function template_preprocess_ebsco_results(&$variables) { + global $Document; + $params = $_REQUEST; + + $_SESSION['EBSCO']['redirect'] = drupal_get_destination(); + if (empty($Document)) { + $Document = new EBSCODocument(); + } + + $title = !empty($params['lookfor']) ? ' - ' . $params['lookfor'] : ''; + drupal_set_title('Search results' . $title); + + $Document->search(); + + $variables['records'] = $Document->records(); + $variables['record_start'] = $Document->record_start(); + $variables['record_end'] = $Document->record_end(); + $variables['record_count'] = $Document->record_count(); + $variables['search_view'] = $Document->search_view(); + $variables['search_time'] = $Document->search_time(); + $variables['lookfor'] = ''; + if (isset($params['lookfor'])) { + $variables['lookfor'] = $params['lookfor']; + } else if (isset($params['group'])) { + $types = EBSCODocument::basic_search_type_options(); + foreach ($params['group'] as $key => $group) { + if (!empty($group['lookfor'])) { + $pre = $key == 0 ? '' : " {$group['bool']} "; + $variables['lookfor'] .= $pre . $types[$group['type']] . ':' . $group['lookfor']; + } + } + } + $variables['pager'] = $Document->pager(); + + $v1=drupal_get_form('ebsco_sort_form'); + $variables['sort_form'] = drupal_render($v1); + + // Save data needed by scroller in Detailed view page + $Document->search_write(); +} + + +/** + * Process variables for ebsco-basic-search.tpl.php. + * + * @see ebsco-basic-search.tpl.php + */ +function template_preprocess_ebsco_basic_search(&$variables) { + global $Document; + $params = $_REQUEST; + + if (empty($Document)) { + $Document = new EBSCODocument(); + } + + $variables['search_view'] = $Document->search_view(); + $variables['lookfor'] = ''; + if (isset($params['lookfor'])) { + $variables['lookfor'] = $params['lookfor']; + } else if (isset($params['group'])) { + $types = EBSCODocument::basic_search_type_options(); + foreach ($params['group'] as $key => $group) { + if (!empty($group['lookfor'])) { + $pre = $key == 0 ? '' : " {$group['bool']} "; + $variables['lookfor'] .= $pre . $types[$group['type']] . ':' . $group['lookfor']; + } + } + } + $v1=drupal_get_form('ebsco_basic_search_form'); + $variables['search_form'] = drupal_render($v1); +} + + +/** + * Process variables for ebsco-advanced-search.tpl.php. + * + * @see ebsco-advanced-search.tpl.php + */ +function template_preprocess_ebsco_advanced_search(&$variables) { + drupal_add_library('system','ui.slider'); + global $Document; + $params = $_REQUEST; + + if (empty($Document)) { + $Document = new EBSCODocument(); + } + $Document->info(); + + $v1=drupal_get_form('ebsco_advanced_search_form'); + $variables['search_form'] = drupal_render($v1); +} + + +/** + * Process variables for ebsco-result.tpl.php. + * + * @see ebsco-result.tpl.php + */ +function template_preprocess_ebsco_result(&$variables) { + $params = $_REQUEST; + $params['op'] = isset($params['op']) ? $params['op'] : 'Next'; + + $_SESSION['EBSCO']['redirect'] = drupal_get_destination(); + if (empty($Document)) { + $Document = new EBSCODocument(); + } + $Document->retrieve(); + $record = $Document->record(); + + $variables['record'] = $record; + $lastSearch = isset($params['id']) ? $Document->search_read($params['id'], $params['op']) : ''; + $variables['last_search'] = $lastSearch; + + drupal_set_title($record->title); +} + + +/** + * Process variables for ebsco-side-facets.tpl.php. + * + * @see ebsco-side-facets.tpl.php + */ +function template_preprocess_ebsco_side_facets(&$variables) { + global $Document; + + if (empty($Document)) { + $Document = new EBSCODocument(); + } + $Document->info(); + + $variables['record_count'] = $Document->record_count(); + $variables['expanders'] = $Document->expanders(); + $variables['limiters'] = $Document->limiters(); + $variables['facets'] = $Document->facets(); + $variables['filters'] = $Document->filters(); // applied facets, limiters or expanders + $variables['search_params'] = $Document->search_params(); // hidden parameters + $variables['link_search_params'] = $Document->link_search_params(); // hidden parameters +} + + + +/****************************************** + * View Helpers + ******************************************/ + +/** + * Returns an URL without the given filter parameter + * + * @return string + */ +function remove_filter_link($filter) +{ + $params = $_REQUEST; + if (isset($params['filter'])) { + foreach($params['filter'] as $key => $value) { + if ($value == $filter['action']) { + unset($params['filter'][$key]); + } + } + } + return url('ebsco/results', array('query' => $params)); +} + + +/** + * Performs a regex and replaces any url's with links containing themselves as the text + * + * @return string + */ +function auto_link($string) +{ + $linkedString = preg_replace_callback( + "/\b(https?):\/\/([-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|]*)\b/i", + create_function( + '$matches', + 'return "".($matches[0])."";' + ), + $string + ); + return $linkedString; +} \ No newline at end of file diff --git a/ebsco/images/ajax_loading.gif b/ebsco/images/ajax_loading.gif new file mode 100644 index 0000000..d42f72c Binary files /dev/null and b/ebsco/images/ajax_loading.gif differ diff --git a/ebsco/images/sprites_32.png b/ebsco/images/sprites_32.png new file mode 100644 index 0000000..c837d77 Binary files /dev/null and b/ebsco/images/sprites_32.png differ diff --git a/ebsco/js/ebsco.js b/ebsco/js/ebsco.js new file mode 100644 index 0000000..26b37ca --- /dev/null +++ b/ebsco/js/ebsco.js @@ -0,0 +1,186 @@ +/* + * The EBSCO module javascript + **/ +(function ($) { + $(document).ready(function () { + + // + var updatePublishDateSlider = function () { + var from = parseInt($('#DT1').val()); + var min = 1000; + + if (!from || from < min) { + from = min; + } + + // and keep the max at 1 years from now + var max = (new Date()).getFullYear() + 1; + var to = max; + + // update the slider with the new min/max/values + $('#ebsco-advanced-search-sliderDT1').slider('option', { + min: min, max: max, values: [from, to] + }); + }; + + + /* + * Self executing function + **/ + var onLoad = function () { + // EBSCO/Search : Expand limiters + $('._more_limiters').live('click', function (event) { + $("#moreLimiters").hide(); + $("#limitersHidden").removeClass("offscreen"); + }); + + // Search : Collapse limiters + $('._less_limiters').live('click', function (event) { + $("#moreLimiters").show(); + $("#limitersHidden").addClass("offscreen"); + }); + + // EBSCO/Search : Collapse / expand facets + $('.expandable').live('click', function (event) { + var span = $(this).find('dt span'), + id = $(this).attr('id').replace('facet-',''); + if (span.length > 0) { + if (span.hasClass('collapsed')) { + $('#narrowGroupHidden_' + id).show(); + span.removeClass('collapsed'); + span.addClass('expanded'); + } else if (span.hasClass('expanded')) { + $('#narrowGroupHidden_' + id).hide(); + span.removeClass('expanded'); + span.addClass('collapsed'); + } + } else if ($(this).attr('href')) { + var dl = $(this).parents('dl'), + id = dl.attr('id').replace('narrowGroupHidden_', ''), + span = $('#facet-' + id).find('dt span'); + dl.hide(); + span.removeClass('expanded'); + span.addClass('collapsed'); + } + }); + + // EBSCO/Search : Less facets + $('._less_facets').live('click', function (event) { + var id = $(this).attr('id').replace('less-facets-',''); + var dl = $('#facet-' + id); + dl.trigger('click'); + }); + + // Search : Ajax request the Record action + $('._record_link').live('click', function (event) { + var element = $(this); + var position = element.position(); + event.preventDefault(); + $('#spinner').show(); + $("#spinner").offset({left:event.pageX - 18,top:event.pageY - 18}); + + $.get(element.attr('href'), function (data) { + $('#main').html(data); + $('#spinner').hide(); + }); + }); + + // Advanced Search : Add a new search term + $('._add_row').live('click', function (event) { + event.preventDefault(); + var newSearch = $('#advanced-row-template').html(); + var rows = $('._advanced-row'); + if (rows) { + // Find the index of the next row + var index = rows.length - 1; // one row is the template itself, so don't count it + // Replace NN string with the index number + newSearch = newSearch.replace(/NN/g, index); + lastSearch = $('#edit-add-row'); + lastSearch.before(newSearch); + } + }); + + // Advanced Search : Delete an advanced search row + $('._delete_row').live('click', function (event) { + event.preventDefault(); + $(this).parents('._advanced-row').remove(); + }); + + // Advanced Search : Reset the form fields to default values + $('.ebsco-advanced input[name="reset"]').live('click', function (event) { + event.preventDefault(); + $('#ebsco-advanced-search-form').find('input, select').each(function (index) { + var type = this.type; + switch(type) { + case 'text': + $(this).val(''); + break; + case 'checkbox': + $(this).attr('checked', ''); + break; + case 'select-multiple': + $(this).children('option').each(function (index) { + $(this).attr('selected', ''); + }); + break; + case 'select-one': + $(this).children('option').each(function (index) { + $(this).attr('selected', ''); + }); + // for IE + $(this).children('option:first').attr('selected', 'selected'); + break; + case 'radio': + $(this).attr('checked', ''); + $(this).parent().siblings().first().children('input:first').attr('checked', 'checked'); + break; + } + }); + }); + + // Auto submit the seelct boxes with '_jump_menu' class + $('._jump_menu').live('change', function (event) { + var name = $(this).attr('id').replace('ebsco-', ''), + value = $(this).attr('value'), + url = $('#ebsco-sort-form').attr('action'); + url += "&" + name + "=" + value; + window.location.href = url; + }); + + // Retain search filters checkbox functionality + $('#edit-remember').live('click', function (event) { + $("#ebsco-basic-search-form :input[type='checkbox'][name^='filter[']").attr('checked', $(this).attr('checked')); + }); + + // Advanced Search : handle 'Date Published from' limiter + // Create the UI slider (if slider function is defined) + if(typeof $("#ebsco-advanced-search-sliderDT1").slider == 'function') { + + $('#ebsco-advanced-search-sliderDT1').slider({ + range: true, + min: 0, max: 9999, values: [0, 9999], + slide: function (event, ui) { + $('#DT1').val(ui.values[0]); + if(ui.values[0] == 1000) { + $('#ebsco-advanced-search-limiterDT1').val(''); + } else { + $('#ebsco-advanced-search-limiterDT1').val('addlimiter(DT1:' + ui.values[0] + '-1/2013-1)'); + } + } + }); + + // initialize the slider with the original values + // in the text boxes + updatePublishDateSlider(); + + // when user enters values into the boxes + // the slider needs to be updated too + $('#DT1').change(function(){ + updatePublishDateSlider(); + }); + } + }(); + + +}); +})(jQuery); diff --git a/ebsco/lib/EBSCOAPI.php b/ebsco/lib/EBSCOAPI.php new file mode 100644 index 0000000..52bf5d8 --- /dev/null +++ b/ebsco/lib/EBSCOAPI.php @@ -0,0 +1,629 @@ + '', + 'AllFields' => '', + 'Abstract' => 'AB', + 'Author' => 'AU', + 'Source' => 'SO', + 'Subject' => 'SU', + 'Title' => 'TI' + ); + + + /** + * EBSCO sort options + * @global array + */ + private static $sort_options = array( + 'relevance', + 'date', + 'date2', + 'source' + ); + + + /** + * VuFind sort types mapped to EBSCO sort types + * used for urls in Search results / Detailed view + * @global array + */ + private static $mapped_sort_options = array( + '' => 'relevance', + 'relevance' => 'relevance', + 'subject' => 'date', + 'date' => 'date2', + 'date_asc' => 'date2', + 'date_desc' => 'date', + 'callnumber' => 'date', + 'author' => 'author', + 'title' => 'date' + ); + + + /** + * Constructor + * + * + * @param array config + * + * @access public + */ + public function __construct($config) + { + $this->config = $config; + } + + /** + * Setter / Getter for authentication token + * + * @param string The authentication token + * + * @return string or none + * @access public + */ + public function authenticationToken($token = null) + { + if (empty($token)) { + $token = $this->readSession('authenticationToken'); + return !empty($token) ? $token : $this->authenticationToken; + } else { + $this->authenticationToken = $token; + $this->writeSession('authenticationToken', $token); + } + } + + + /** + * Setter / Getter for session token + * + * @param string The session token + * + * @return string or none + * @access public + */ + public function sessionToken($token = null) + { + if (empty($token)) { + $token = $this->readSession('sessionToken'); + return !empty($token) ? $token : $this->sessionToken; + } else { + $this->sessionToken = $token; + $this->writeSession('sessionToken', $token); + } + } + + + /** + * Getter for isGuest + * + * @param string 'y' or 'n' + * + * @return string or none + * @access public + */ + public function isGuest($boolean = null) + { + if (empty($boolean)) { + return $this->readSession('isGuest'); + } else { + $this->writeSession('isGuest', $boolean); + } + } + + + /** + * Create a new EBSCOConnector object or reuse an existing one + * + * @param none + * + * @return EBSCOConnector object + * @access public + */ + public function connector() + { + if (empty($this->connector)) { + $this->connector = new EBSCOConnector($this->config); + } + return $this->connector; + } + + + /** + * Create a new EBSCOResponse object + * + * @param object $response + * + * @return EBSCOResponse object + * @access public + */ + public function response($response) + { + $responseObj = new EBSCOResponse($response); + return $responseObj; + } + + + /** + * Request authentication and session tokens, then send the API request. + * Retry the request if authentication errors occur + * + * @param string $action The EBSCOConnector method name + * @param array $params The parameters of the HTTP request + * @param integer $attempts The number of retries + * + * @return object SimpleXml DOM or PEAR Error + * @access protected + */ + protected function request($action, $params = null, $attempts = 5) + { + $authenticationToken = $this->authenticationToken(); + $sessionToken = $this->sessionToken(); + + // If authentication token is missing then the session token is missing too, so get both tokens + // If session token is missing then the authentication token may be invalid, so get both tokens + if (empty($authenticationToken) || empty($sessionToken)) { + $result = $this->apiAuthenticationAndSessionToken(); + if ($this->isError($result)) { + // Any error should terminate the request immediately + // in order to prevent infinite recursion + return $result; + } + } + + // Any change of the isGuest should request a new session + // (and don't terminate the current request if there was an error during the session request + // since it's not that important) + if ($this->isGuest() != $this->connector()->isGuest()) { + $this->apiSessionToken(); + } + + $headers = array( + 'x-authenticationToken' => $this->authenticationToken(), + 'x-sessionToken' => $this->sessionToken() + ); + + $response = call_user_func_array(array($this->connector(), "request{$action}"), array($params, $headers)); + if ($this->isError($response)) { + // Retry the request if there were authentication errors + $code = $response->getCode(); + switch ($code) { + // If authentication token is invalid then the session token is invalid too, so get both tokens + // If session token is invalid then the authentication token may be invalid too, so get both tokens + case EBSCOConnector::EDS_AUTH_TOKEN_INVALID: + $result = $this->apiAuthenticationToken(); + if ($this->isError($result)) { + // Any error should terminate the request immediately + // in order to prevent infinite recursion + return $result; + } + if ($attempts > 0) { + $result = $this->request($action, $params, --$attempts); + } + break; + case EBSCOConnector::EDS_SESSION_TOKEN_INVALID: + $result = $this->apiAuthenticationAndSessionToken(); + if ($this->isError($result)) { + // Any error should terminate the request immediately + // in order to prevent infinite recursion + return $result; + } + if ($attempts > 0) { + $result = $this->request($action, $params, --$attempts); + } + break; + default: + $result = $this->handleError($response); + break; + } + } else { + $result = $this->response($response)->result(); + } + + return $result; + } + + + /** + * Wrapper for authentication API call + * + * @param none + * + * @access public + */ + public function apiAuthenticationToken() + { + $response = $this->connector()->requestAuthenticationToken(); + + if ($this->isError($response)) { + return $response; + } else { + $result = $this->response($response)->result(); + if (isset($result['authenticationToken'])) { + $this->authenticationToken($result['authenticationToken']); + return $result['authenticationToken']; + } else { + return new EBSCOException("No authentication token was found in the response."); + } + } + } + + + /** + * Wrapper for session API call + * + * @param none + * + * @access public + */ + public function apiSessionToken() + { + // Add authentication tokens to headers + $headers = array( + 'x-authenticationToken' => $this->authenticationToken() + ); + + $response = $this->connector()->requestSessionToken($headers); + + // Raise the exception so that any code running this method should exit immediately + if ($this->isError($response)) { + return $response; + } else { + $result = $this->response($response)->result(); + if (is_string($result)) { + $this->sessionToken($result); + return $result; + } else { + return new EBSCOException("No session token was found in the response."); + } + } + } + + + /** + * Initialize the authentication and session tokens + * + * @param none + * + * @access public + */ + public function apiAuthenticationAndSessionToken() + { + $authenticationToken = $this->apiAuthenticationToken(); + if ($this->isError($authenticationToken)) { + // An authentication error should terminate the request immediately + return $authenticationToken; + } + + $sessionToken = $this->apiSessionToken(); + if ($this->isError($sessionToken)) { + // A session error should terminate the request immediately + return $sessionToken; + } + + // We don't have to return anything, both tokens can be accessed using the getters + return true; + } + + + /** + * Wrapper for search API call + * + * @param array $search The search terms + * @param array $filters The facet filters + * @param string $start The page to start with + * @param string $limit The number of records to return + * @param string $sortBy The value to be used by for sorting + * @param string $amount The amount of data to be returned + * @param string $mode The search mode + * + * @throws object PEAR Error + * @return array An array of query results + * @access public + */ + public function apiSearch($search, $filters, + $start = 1, $limit = 10, $sortBy = 'relevance', $amount = 'detailed', $mode = 'all' + ) { + $query = array(); + + // Basic search + if(!empty($search['lookfor'])) { + $lookfor = $search['lookfor']; + $type = isset($search['index']) && !empty($search['index']) ? $search['index'] : 'AllFields'; + + // escape some characters from lookfor term + $term = str_replace(array(',', ':', '(', ')'), array('\,', '\:', '\(', '\)'), $lookfor); + // replace multiple consecutive empty spaces with one empty space + $term = preg_replace("/\s+/", ' ', $term); + + // search terms + // Complex search term + if (preg_match('/(.*) (AND|OR) (.*)/i', $term)) { + $query['query'] = $term; + } else { + $tag = self::$search_tags[$type]; + $op = 'AND'; + $query_str = implode(',', array($op, $tag)); + $query_str = implode(($tag ? ':' : ''), array($query_str, $term)); + $query['query-1'] = $query_str; + } + + // Advanced search + } else if(!empty($search['group'])) { + + $counter = 1; + foreach ($search['group'] as $group) { + $type = $group['type']; + if (isset($group['lookfor'])) { + $term = $group['lookfor']; + $op = $group['bool']; + $tag = $type && isset(self::$search_tags[$type]) ? self::$search_tags[$type] : ''; + + // escape some characters from lookfor term + $term = str_replace(array(',', ':', '(', ')'), array('\,', '\:', '\(', '\)'), $term); + // replace multiple consecutive empty spaces with one empty space + $term = preg_replace("/\s+/", ' ', $term); + if (!empty($term)) { + $query_str = implode(',', array($op, $tag)); + $query_str = implode(($tag ? ':' : ''), array($query_str, $term)); + $query["query-$counter"] = $query_str; + $counter++; + } + } + } + + // No search term, return an empty array + } else { + $results = array( + 'recordCount' => 0, + 'numFound' => 0, + 'start' => 0, + 'documents' => array(), + 'facets' => array() + ); + return $results; + } + + // Add filters + $limiters = array(); $expanders = array(); $facets = array(); + foreach ($filters as $filter) { + if (preg_match('/addlimiter/', $filter)) { + list($action, $str) = explode('(', $filter, 2); + $field_and_value = substr($str, 0, -1); // e.g. FT:y or GZ:Student Research, Projects and Publications + list($field, $value) = explode(':', $field_and_value, 2); + $limiters[$field][] = $value; + } else if (preg_match('/addexpander/', $filter)) { + list($action, $str) = explode('(', $filter, 2); + $field = substr($str, 0, -1); // expanders don't have value + $expanders[] = $field; + } else if (preg_match('/addfacetfilter/', $filter)) { + list($action, $str) = explode('(', $filter, 2); + $field_and_value = substr($str, 0, -1); // e.g. ZG:FRANCE + list($field, $value) = explode(':', $field_and_value, 2); + $facets[$field][] = $field_and_value; + } + } + if (!empty($limiters)) { + foreach($limiters as $field => $limiter) { + $query['limiter'][] = $field . ':' . implode(',', $limiter); // e.g. LA99:English,French,German + } + } + if (!empty($expanders)) { + $query['expander'] = implode(',', $expanders); // e.g. fulltext, thesaurus + } + if (!empty($facets)) { + $groupId = 1; + foreach($facets as $field => $facet) { + $query['facetfilter'][] = $groupId . ',' . implode(',', $facet); // e.g. 1,DE:Math,DE:History + $groupId += 1; + } + } + + // Add the sort option + $sortBy = in_array($sortBy, self::$sort_options) ? $sortBy : self::$mapped_sort_options[$sortBy]; + + // Add the HTTP query params + $params = array( + // Specifies the sort. Valid options are: + // relevance, date, date2 + // date = Date descending + // date2 = Date ascending + 'sort' => $sortBy, + // Specifies the search mode. Valid options are: + // bool, any, all, smart + 'searchmode' => $mode, + // Specifies the amount of data to return with the response + // Valid options are: + // title: Title only + // brief: Title + Source, Subjects + // detailed: Brief + full abstract + 'view' => $amount, + /// Specifies whether or not to include facets + 'includefacets' => 'y', + 'resultsperpage' => $limit, + 'pagenumber' => $start, + // Specifies whether or not to include highlighting in the search results + 'highlight' => 'y' + ); + + $params = array_merge($params, $query); + + $result = $this->request('Search', $params); + return $result; + } + + + /** + * Wrapper for retrieve API call + * + * @param array $an The accession number + * @param string $start The short database name + * + * @throws object PEAR Error + * @return array An associative array of data + * @access public + */ + public function apiRetrieve($an, $db) + { + // Add the HTTP query params + $params = array( + 'an' => $an, + 'dbid' => $db, + 'highlight' => 'y' + ); + + $result = $this->request('Retrieve', $params); + return $result; + } + + + /** + * Wrapper for info API call + * + * @throws object PEAR Error + * @return array An associative array of data + * @access public + */ + public function apiInfo() + { + if ($result = $this->readSession('info')) { + return $result; + } + + $result = $this->request('Info'); + if(!$this->isError($result)) { + $this->writeSession('info', $result); + } + return $result; + } + + + /** + * Handle a PEAR_Error. Return : + * - if the error is critical : an associative array with the current error message + * - if the error is not critical : the error message + * + * @param Pear_Error $exception + * + * @return array or the Pear_Error exception + * @access protected + */ + private function handleError($error) { + $errorCode = $error->getCode(); + switch($errorCode) { + // This kind of error was generated by user , so display it to user + case EBSCOConnector::EDS_INVALID_ARGUMENT_VALUE: + // Any other errors are system errors, don't display them to user + default: + $errorMessage = 'An error occurred when getting the data.'; + break; + } + $result = array( + 'errors' => $errorMessage, + 'recordCount' => 0, + 'numFound' => 0, + 'start' => 0, + 'documents' => array(), + 'facets' => array() + ); + return $result; + } + + + /** + * Store the given object into session + * + * @param string $key The key used for reading the value + * @param object $value The object stored in session + * + * @return none + * @access protected + */ + protected function writeSession($key, $value) + { + if(!empty($key) && !empty($value)) { + $_SESSION['EBSCO'][$key] = $value; + } + } + + + /** + * Read from session the object having the given key + * + * @param string $key The key used for reading the object + * + * @return object + * @access protected + */ + protected function readSession($key) + { + $value = isset($_SESSION['EBSCO'][$key]) ? $_SESSION['EBSCO'][$key] : ''; + return $value; + } + + + /** + * Check if given object is an EBSCOException object + * + * @param object $object + * + * @return boolean + * @access protected + */ + protected function isError($object) + { + return is_a($object, 'EBSCOException'); + } + +} + + +?> \ No newline at end of file diff --git a/ebsco/lib/EBSCOConnector.php b/ebsco/lib/EBSCOConnector.php new file mode 100644 index 0000000..477b53a --- /dev/null +++ b/ebsco/lib/EBSCOConnector.php @@ -0,0 +1,432 @@ +password = $config['password']; + $this->userId = $config['user']; + $this->interfaceId = $config['interface']; + $this->profileId = $config['profile']; + $this->orgId = $config['organization']; + $this->isGuest = user_is_logged_in() ? 'n' : 'y'; + $this->logAPIRequests = ($config['log'] == 1); + if ($this->logAPIRequests) { + $writer = new Zend_Log_Writer_Stream('php://output'); + $this->logger = new Zend_Log($writer); + } + } + + + /** + * Public getter for private isGuest + * + * @param none + * + * @return string isGuest + * @access public + */ + public function isGuest() + { + return $this->isGuest; + } + + + /** + * Request the authentication token + * + * @param none + * + * @return object SimpleXml or PEAR_Error + * @access public + */ + public function requestAuthenticationToken() + { + $url = self::$authentication_end_point . '/UIDAuth'; + + // Add the body of the request + $params =<< + {$this->userId} + {$this->password} + {$this->interfaceId} + +BODY; + + $response = $this->request($url, $params, array(), 'POST'); + return $response; + } + + + /** + * Request the session token + * + * @param array $headers Authentication token + * + * @return object SimpleXml or PEAR_Error + * @access public + */ + public function requestSessionToken($headers) + { + $url = self::$end_point . '/CreateSession'; + + // Add the HTTP query params + $params = array( + 'profile' => $this->profileId, + 'org' => $this->orgId, + 'guest' => $this->isGuest + ); + + $response = $this->request($url, $params, $headers); + return $response; + } + + + /** + * Request the search records + * + * @param array $params Search specific parameters + * @param array $headers Authentication and session tokens + * + * @return object SimpleXml or PEAR_Error + * @access public + */ + public function requestSearch($params, $headers) + { + $url = self::$end_point . '/Search'; + + $response = $this->request($url, $params, $headers); + return $response; + } + + + /** + * Request a specific record + * + * @param array $params Retrieve specific parameters + * @param array $headers Authentication and session tokens + * + * @return object SimpleXml or PEAR_Error + * @access public + */ + public function requestRetrieve($params, $headers) + { + $url = self::$end_point . '/Retrieve'; + + $response = $this->request($url, $params, $headers); + return $response; + } + + + /** + * Request the info data + * + * @param null $params Not used + * @param array $headers Authentication and session tokens + * + * @return object SimpleXml or PEAR_Error + * @access public + */ + public function requestInfo($params, $headers) + { + $url = self::$end_point . '/Info'; + + $response = $this->request($url, $params, $headers); + return $response; + } + + + /** + * Send an HTTP request and inspect the response + * + * @param string $url The url of the HTTP request + * @param array $params The parameters of the HTTP request + * @param array $headers The headers of the HTTP request + * @param array $body The body of the HTTP request + * @param string $method The HTTP method, default is 'GET' + * + * @return object SimpleXml or PEAR_Error + * @access protected + */ + protected function request($url, $params, $headers = array(), $method = 'GET') + { + $xml = false; + $return = false; + $data = null; + + if (!empty($params)) { + // Arrays of parameters are used only for GET requests + if (is_array($params)) { + $query = http_build_query($params, '', '&'); + $query = preg_replace('/\%5B\d+\%5D/', '', $query); + $url = $url . '?' . $query; + // String parameters are used only for POST requests + } else { + $data = $params; + $headers = array_merge( + array('content-type' => 'text/xml'), + $headers + ); + } + } + + $options = array( + 'headers' => $headers, + 'method' => $method, + 'data' => $data + ); + + // Send the request + try { + $response = drupal_http_request($url, $options); +//print_r($response); + $code = $response->code; + switch ($code) { + case self::HTTP_OK: + $xml_str = $response->data; + try { + $xml = simplexml_load_string($xml_str); + $return = $xml; + } catch(Exception $e) { + $return = new EBSCOException($xml); + } + break; + + case self::HTTP_BAD_REQUEST: + $xml_str = $response->data; + try { + $xml = simplexml_load_string($xml_str); + + // If the response is an API error + $isError = isset($xml->ErrorNumber) || isset($xml->ErrorCode); + if ($isError) { + $error = ''; $code = 0; + if (isset($xml->DetailedErrorDescription) && !empty($xml->DetailedErrorDescription)) { + $error = (string) $xml->DetailedErrorDescription; + } else if (isset($xml->ErrorDescription)) { + $error = (string) $xml->ErrorDescription; + } else if (isset($xml->Reason)) { + $error = (string) $xml->Reason; + } + if (isset($xml->ErrorNumber)) { + $code = (integer) $xml->ErrorNumber; + } else if (isset($xml->ErrorCode)) { + $code = (integer) $xml->ErrorCode; + } + $return = new EBSCOException($error, $code); + } else { + $return = new EBSCOException("HTTP {$code} : The request could not be understood + by the server due to malformed syntax. Modify your search before retrying."); + } + } catch (Exception $e) { + $return = new EBSCOException($xml); + } + break; + + case self::HTTP_NOT_FOUND: + $return = new EBSCOException("HTTP {$code} : The resource you are looking for might + have been removed, had its name changed, or is temporarily unavailable."); + break; + + case self::HTTP_INTERNAL_SERVER_ERROR: + $return = new EBSCOException("HTTP {$code} : The server encountered an unexpected condition + which prevented it from fulfilling the request."); + break; + + default: + $return = new EBSCOException("HTTP {$code} : Unexpected HTTP error."); + break; + } + } catch (Exception $e) { + $return = new EBSCOException($response); + } + + // Log any error + /*if ($this->logAPIRequests) { + // $client = both the HTTP request and response + // $response = only the HTTP response + $message = $this->toString($client); // or $this->toString($response) + $this->logger->log($message, Zend_Log::ERR); + }*/ + + return $return; + } + + + /* + * Capture the output of print_r into a string + * + * @param object Any object + * @access private + */ + private function toString($object) + { + ob_start(); + print_r($object); + return ob_get_clean(); + } + + +} + + +?> \ No newline at end of file diff --git a/ebsco/lib/EBSCODocument.php b/ebsco/lib/EBSCODocument.php new file mode 100644 index 0000000..7165a7f --- /dev/null +++ b/ebsco/lib/EBSCODocument.php @@ -0,0 +1,788 @@ + 10, + 20 => 20, + 30 => 30, + 40 => 40, + 50 => 50 + ); + + /** + * Sort options + * global array + */ + private static $sort_options = array( + 'relevance' => 'Relevance', + 'date_desc' => 'Date Descending', + 'date_asc' => 'Date Ascending' + ); + + /** + * Amount options + * global array + */ + private static $amount_options = array( + 'detailed' => 'Detailed', + 'brief' => 'Brief', + 'title' => 'Title Only' + ); + + /** + * Bool options + * global array + */ + private static $bool_options = array( + 'AND' => 'All terms', + 'OR' => 'Any terms', + 'NOT' => 'No terms' + ); + + /** + * Search mode options + * global array + */ + private static $mode_options = array( + 'all' => 'All search terms', + 'bool' => 'Boolean / Phrase', + 'any' => 'Any search terms', + 'smart' => 'SmartText Searching' + ); + + /** + * Basic search type options + * global array + */ + private static $basic_search_type_options = array( + 'AllFields' => 'All Text', + 'Title' => 'Title', + 'Author' => 'Author', + 'Subject' => 'Subject terms', + 'Source' => 'Source', + 'Abstract' => 'Abstract' + ); + + /** + * Advanced search type options + * global array + */ + private static $advanced_search_type_options = array( + 'AllFields' => 'All Text', + 'Title' => 'Title', + 'Author' => 'Author', + 'Subject' => 'Subject terms' + ); + + + /** + * Constructor. + * + * @param array $data Raw data from the EBSCO search representing the record. + */ + public function __construct($params = null) + { + $this->eds = new EBSCOAPI(array( + 'password' => variable_get('ebsco_password'), + 'user' => variable_get('ebsco_user'), + 'profile' => variable_get('ebsco_profile'), + 'interface' => variable_get('ebsco_interface'), + 'organization' => variable_get('ebsco_organization'), + 'guest' => variable_get('ebsco_guest'), + 'log' => variable_get('ebsco_log') + )); + + $this->params = $params ? $params : $_REQUEST; + $this->limit = variable_get('ebsco_default_limit') ? variable_get('ebsco_default_limit') : $this->limit; + } + + + /** + * Perform the API Info call + * + * @return array + */ + public function info() + { + $this->info = $this->eds->apiInfo(); + return $this->info; + } + + + /** + * Perform the API Retrieve call + * + * @return array + */ + public function retrieve() + { + list($an, $db) = isset($this->params['id']) ? explode('|', $this->params['id'], 2) : array(null, null); + $this->result = $this->eds->apiRetrieve($an, $db); + + return $this->result; + } + + + /** + * Perform the API Search call + * + * @return array + */ + public function search() + { + $search = array(); + + if (isset($this->params['lookfor']) && isset($this->params['type'])) { + $search = array( + 'lookfor' => $this->params['lookfor'], + 'index' => $this->params['type'] + ); + } else if (isset($this->params['group'])) { + $search = $this->params; + } else { + return array(); + } + + $filter = isset($this->params['filter']) ? $this->params['filter'] : array(); + $page = isset($this->params['page']) ? $this->params['page'] + 1 : 1; + $limit = $this->limit; + $sort = isset($this->params['sort']) ? $this->params['sort'] : 'relevance'; + $amount = isset($this->params['amount']) ? $this->params['amount'] : 'detailed'; + $mode = isset($this->params['mode']) ? $this->params['mode'] : 'all'; + + $this->results = $this->eds->apiSearch($search, $filter, $page, $limit, $sort, $amount, $mode); + + if (isset($this->results['start'])) { + $this->results['start'] = $limit * ($page - 1); + } + + return $this->results; + } + + + /** + * Get the EBSCORecord model for the result + * + ** @return array + */ + public function record() + { + if (empty($this->record) && !(empty($this->result))) { + $this->record = new EBSCORecord($this->result); + } + + return $this->record; + } + + + /** + * Get the EBSCORecord models array from results array + * + ** @return array + */ + public function records() + { + if (empty($this->records) && !(empty($this->results))) { + foreach($this->results['documents'] as $result) { + $this->records[] = new EBSCORecord($result); + } + } + + return $this->records; + } + + + /** + * Get the pagination HTML string + * + ** @return HTML string + */ + public function pager() + { + $pager = null; + if ($this->has_records()) { + pager_default_initialize($this->record_count() / $this->limit, 1); + $pager = theme('pager', array('tags' => null, 'quantity' => self::$page_links)); + $pager = preg_replace('/
  • (.*)<\/li>/', '', $pager); + } + return $pager; + } + + + /******************************************************** + * + * Getters (class methods) + * + ********************************************************/ + + + /** + * Getter for sort options + * @return array + */ + public static function limit_options() + { + return self::$limit_options; + } + + + /** + * Getter for sort options + * @return array + */ + public static function sort_options() + { + return self::$sort_options; + } + + + /** + * Getter for amount options + * @return array + */ + public static function amount_options() + { + return self::$amount_options; + } + + + /** + * Getter for boolean options + * @return array + */ + public static function bool_options() + { + return self::$bool_options; + } + + + /** + * Getter for search mode options + * @return array + */ + public static function mode_options() + { + return self::$mode_options; + } + + + /** + * Getter for Basic search type options + * @return array + */ + public static function basic_search_type_options() + { + return self::$basic_search_type_options; + } + + + /** + * Getter for Advanced search type options + * @return array + */ + public static function advanced_search_type_options() + { + return self::$advanced_search_type_options; + } + + + /******************************************************** + * + * Helper methods + * + ********************************************************/ + + + /** + * Get the expanders. + * + * @return array + */ + public function expanders() + { + $actions = array(); + $filters = $this->filters(); + foreach($filters as $filter) { + $actions[] = $filter['action']; + } + + $expanders = isset($this->info['expanders']) ? $this->info['expanders'] : array(); + foreach($expanders as $key => $expander) { + if (in_array($expander['Action'], $actions)) { + $expanders[$key]['selected'] = true; + } + } + + return $expanders; + } + + + /** + * Get the facets. + * + * @return array + */ + public function facets() + { + $actions = array(); + foreach($this->filters as $filter) { + $actions[] = $filter['action']; + } + + $facets = isset($this->results['facets']) ? $this->results['facets'] : array(); + foreach($facets as $key => $cluster) { + foreach($cluster['Values'] as $k => $facet) { + $is_applied = false; + if (in_array($facet['Action'], $actions)) { + $is_applied = true; + } + $facets[$key]['Values'][$k]['applied'] = $is_applied; + } + } + + return $facets; + } + + + /** + * Get the filters. + * + * @return array + */ + public function filters() + { + if (!empty($_REQUEST['filter'])) { + $labels = array(); + foreach($this->info['limiters'] as $limiter) { + $labels[$limiter['Id']] = $limiter['Label']; + } + $this->filters = array(); + foreach($_REQUEST['filter'] as $filter) { + if (!empty($filter)) { + $temp = str_replace(array('addfacetfilter(', 'addlimiter(', 'addexpander('), array('', '', ''), $filter); + if (substr($temp, -1, 1) == ')') { + $temp = substr($temp, 0, -1); + } + // Do not display addfacetfilter, addlimiter or addexpander strings + if (preg_match('/\:/', $filter)) { + list($field, $value) = explode(':', $temp, 2); + $displayField = isset($labels[$field]) ? $labels[$field] : $field; + $displayValue = $value == 'y' ? 'yes' : $value; + } else if (preg_match('/addexpander/', $filter)) { + $field = $temp; + $value = 'y'; + $displayField = isset($labels[$field]) ? $labels[$field] : $field; + $displayValue = 'yes'; + } else { + $field = $value = $displayField = $displayValue = $filter; + } + + $this->filters[] = array( + 'field' => $field, + 'value' => $value, + 'action' => $filter, + 'displayField' => $displayField, + 'displayValue' => $displayValue, + ); + } + } + } + return $this->filters; + } + + + /** + * Get the limiters. + * + * @return array + */ + public function limiters() + { + $actions = array(); $ids = array(); + $filters = $this->filters(); + foreach($filters as $filter) { + $actions[] = $filter['action']; + $ids[] = $filter['field']; + } + + $limiters = isset($this->info['limiters']) ? $this->info['limiters'] : array(); + foreach($limiters as $key => $cluster) { + // multi select limiter + if (!empty($cluster['Values'])) { + foreach($cluster['Values'] as $limiter) { + $action = $limiter['Action']; + if (in_array($action, $actions)) { + $limiters[$key]['selected'][] = $limiter['Action']; + } + } + // date limiter + } else if ($cluster['Type'] == 'ymrange') { + $id = $cluster['Id']; + if (($k = array_search($id, $ids)) !== false) { + $limiters[$key]['selected'] = $filters[$k]['action']; + } + // other limiters + } else { + $action = str_replace('value', 'y', $cluster['Action']); + if (in_array($action, $actions)) { + $limiters[$key]['selected'] = true; + } + } + } + + return $limiters; + } + + + /** + * Get the total number of records. + * + * @return integer + */ + public function record_count() + { + return !empty($this->results) ? $this->results['recordCount'] : 0; + } + + + /** + * Get the number of end record. + * + * @return integer + */ + public function record_end() + { + $count = !empty($this->results) ? count($this->results['documents']) : 0; + $start = !empty($this->results) ? $this->results['start'] : 0; + return $start + $count; + } + + + /** + * Get the number of start record. + * + * @return integer + */ + public function record_start() + { + return !empty($this->results) ? $this->results['start'] + 1 : 0; + } + + + /** + * Get the search time + * + * @return decimal number + */ + public function search_time() + { + return !empty($this->results) && + isset($this->results['searchTime']) ? $this->results['searchTime'] : 0; + } + + + /** + * Get the search view : basic or advanced + * + * @return string + */ + public function search_view() + { + if (isset($_REQUEST['group'])) { + return 'advanced'; + } else { + return 'basic'; + } + } + + + /** + * Hidden params used by UpdateForm + * + * @return array + */ + public function search_params() + { + $params = $this->link_search_params(); + // filter the params that have same values as sidebar checkboxes, otherwise they will produce duplicates + $not_allowed_values = array( + 'addexpander(thesaurus)', + 'addexpander(fulltext)', + 'addlimiter(FT:y)', + 'addlimiter(RV:y)', + 'addlimiter(SO:y)' + ); + + $params = $this->array_filter_recursive($params, function($item) use($not_allowed_values) { + return !($item && in_array($item, $not_allowed_values)); + }); + + return array_filter($params); + } + + + /** + * Hidden params used by UpdateForm + * + * @return array + */ + public function link_search_params() + { + // filter the page parameter + $not_allowed_keys = array('page', 'ui', 'has_js', 'op', 'submit', 'form_id', 'form_build_id'); + + $query=""; + if(isset($_SERVER['QUERY_STRING'])) + {$query = urldecode($_SERVER['QUERY_STRING']);} + parse_str($query, $params); + + $params = $this->array_unset_recursive($params, $not_allowed_keys); + + return $params; + } + + + /** + * Check if there are records in results array + * + ** @return boolean + */ + public function has_records() + { + return !empty($this->results) && !empty($this->results['documents']); + } + + + /** + * Create the last search data + * + * @return void + */ + public function search_create($query = null) + { + $last_search = array(); + if (!empty($this->results)) { + $results_identifiers = array(); + foreach($this->results['documents'] as $result) { + $results_identifiers[] = $result['id']; + } + $last_search['query'] = $query ? $query : $_SERVER['QUERY_STRING']; + $last_search['records'] = serialize($results_identifiers); + $last_search['count'] = $this->record_count(); + } + + return $last_search; + } + + + /** + * Save last search data in session + * + * @return void + */ + public function search_write($query = null) + { + $_SESSION['EBSCO']['last-search'] = $this->search_create($query); + } + + + /** + * Load last search data from session + * + * @return array + */ + public function search_read($id = null, $op = null) + { + $params = array(); + $lastSearch = $_SESSION['EBSCO']['last-search']; + if ($lastSearch) { + $lastSearch['records'] = unserialize($lastSearch['records']); + if ($id) { + parse_str($lastSearch['query'], $params); + $params['page'] = (int) (isset($params['page']) ? $params['page'] : 0); + $index = array_search($id, $lastSearch['records']); + + // if this is not the first scroll and if this is not a page refresh + if (isset($lastSearch['current']) && $lastSearch['current'] != $id) { + // if we change page + if (($op == 'Next' && $index % $this->limit === 0) || + ($op == 'Previous' && $index % $this->limit === 9)) { + $params['page'] = ($op == 'Next') ? $params['page'] + 1 : $params['page'] - 1; + $query = drupal_http_build_query($params); + $lastSearch['query'] = $_SESSION['EBSCO']['last-search']['query'] = $query; + } + } + $start = $params['page']; + + if (count($lastSearch['records']) > 10) { + $records = array_slice($lastSearch['records'], $index - $index % $this->limit, $this->limit); + } else { + $records = $lastSearch['records']; + } + + if (!isset($lastSearch['records'][$index + 1])) { + $params['page'] += 1; + $driver = new EBSCODocument($params); + $driver->search(); + $query = drupal_http_build_query($params); + $newSearch = $driver->search_create($query); + $newSearch['records'] = unserialize($newSearch['records']); + $lastSearch['records'] = array_merge($lastSearch['records'], $newSearch['records']); + $_SESSION['EBSCO']['last-search']['records'] = serialize($lastSearch['records']); + if ($op == 'Next') { + $lastSearch['previous'] = isset($records[8]) ? $records[8] : ''; + } + $lastSearch['next'] = isset($newSearch['records'][0]) ? $newSearch['records'][0] : ''; + } else { + $lastSearch['next'] = $lastSearch['records'][$index + 1]; + } + + if (!isset($lastSearch['records'][$index - 1])) { + if ($params['page'] > 0) { + $params['page'] -= 1; + $driver = new EBSCODocument($params); + $driver->search(); + $query = drupal_http_build_query($params); + $newSearch = $driver->search_create($query); + $newSearch['records'] = unserialize($newSearch['records']); + $lastSearch['records'] = array_merge($lastSearch['records'], $newSearch['records']); + $_SESSION['EBSCO']['last-search']['records'] = serialize($lastSearch['records']); + $lastSearch['previous'] = isset($newSearch['records'][9]) ? $newSearch['records'][9] : ''; + if ($op == 'Previous') { + $lastSearch['next'] = isset($records[1]) ? $records[1] : ''; + } + } else { + $lastSearch['previous'] = ''; + } + } else { + $lastSearch['previous'] = $lastSearch['records'][$index - 1]; + } + + $lastSearch['current_index'] = $start * $this->limit + $index % $this->limit + 1; + $lastSearch['current'] = $id; + } + } + + $_SESSION['EBSCO']['last-search']['current'] = $id; + return $lastSearch; + } + + + /** + * A recursive array_filter + * + * @return array + */ + private function array_filter_recursive($input, $callback = null) + { + foreach ($input as &$value) { + if (is_array($value)) { + $value = $this->array_filter_recursive($value, $callback); + } + } + return array_filter($input, $callback); + } + + + /** + * Recursive filter an array using the given $keys + * + * @return array + */ + private function array_unset_recursive($input, $keys) { + foreach($keys as $key) { + if (isset($input[$key])) { + unset($input[$key]); + } + } + + if (is_array($input)) { + foreach ($input as $key => $value) { + $input[$key] = is_array($value) ? $this->array_unset_recursive($value, $keys) : $value; + } + } + + return array_filter($input); + } +} diff --git a/ebsco/lib/EBSCORecord.php b/ebsco/lib/EBSCORecord.php new file mode 100644 index 0000000..be7c0a4 --- /dev/null +++ b/ebsco/lib/EBSCORecord.php @@ -0,0 +1,398 @@ +data = $data; + $this->record_id = $this->record_id(); + $this->result_id = $this->result_id(); + $this->title = $this->title(); + $this->summary = $this->summary(); + $this->authors = $this->authors(); + $this->subjects = $this->subjects(); + $this->custom_links = $this->custom_links(); + $this->db_label = $this->db_label(); + $this->full_text_availability = $this->full_text_availability(); + $this->full_text = $this->full_text(); + $this->items = $this->items(); + $this->p_link = $this->p_link(); + $this->publication_type = $this->publication_type(); + $this->pdf_availability = $this->pdf_availability(); + $this->pdf_link = $this->pdf_link(); + $this->small_thumb_link = $this->thumb_link(); + $this->medium_thumb_link = $this->thumb_link('medium'); + $this->source = $this->source(); + $this->access_level = $this->access_level(); + } + + + /******************************************************** + * + * Getters + * + ********************************************************/ + + + /** + * Get the summary of the record. + * + * @return string + */ + public function access_level() + { + return isset($this->data['AccessLevel']) ? + $this->data['AccessLevel'] : ''; + } + + + /** + * Get the summary of the record. + * + * @return string + */ + public function summary() + { + return isset($this->data['Items']['Abstract']) ? + $this->data['Items']['Abstract']['Data'] : ''; + } + + + /** + * Get the authors of the record. + * + * @return string + */ + public function authors() + { + return isset($this->data['Items']['Author']) ? + $this->data['Items']['Author']['Data'] : ''; + } + + + /** + * Get the custom links of the record. + * + * @return array + */ + public function custom_links() + { + return isset($this->data['CustomLinks']) ? + $this->data['CustomLinks'] : array(); + } + + + /** + * Get the database label of the record. + * + * @return string + */ + public function db_label() + { + return isset($this->data['DbLabel']) ? + $this->data['DbLabel'] : ''; + } + + + /** + * Get the full text availability of the record. + * + * @return boolean + */ + public function full_text() + { + return isset($this->data['FullText']) && + isset($this->data['FullText']['Value']) ? $this->data['FullText']['Value'] : ''; + } + + + /** + * Get the full text availability of the record. + * + * @return boolean + */ + public function full_text_availability() + { + return isset($this->data['FullText']) && + $this->data['FullText']['Availability']; + } + + + /** + * Get the items of the record. + * + * @return array + */ + public function items() + { + return isset($this->data['Items']) ? $this->data['Items'] : array(); + } + + + /** + * Get the external url of the record. + * + * @return string + */ + public function p_link() + { + return isset($this->data['PLink']) ? $this->data['PLink'] : ''; + } + + + /** + * Get the publication type of the record. + * + * @return string + */ + public function publication_type() + { + return isset($this->data['PubType']) ? $this->data['PubType'] : ''; + } + + + /** + * Get the PDF availability of the record. + * + * @return boolean + */ + public function pdf_availability() + { + return isset($this->data['FullText']) && + isset($this->data['FullText']['Links']) && + isset($this->data['FullText']['Links']['pdflink']) && + $this->data['FullText']['Links']['pdflink']; + } + + + /** + * Get the PDF url of the record. + * + * @return string + */ + public function pdf_link() + { + return isset($this->data['FullText']) && + isset($this->data['FullText']['Links']) && + isset($this->data['FullText']['Links']['pdflink']) ? + $this->data['FullText']['Links']['pdflink'] : + ''; + } + + + /** + * Get the result id of the record. + * + * @return integer + */ + public function result_id() + { + return isset($this->data['ResultId']) ? + $this->data['ResultId'] : ''; + } + + + /** + * Get the subject data of the record. + * + * @return string + */ + public function subjects() + { + return isset($this->data['Items']['Subject']) ? + $this->data['Items']['Subject']['Data'] : ''; + } + + + /** + * Return a URL to a thumbnail preview of the record, if available; false + * otherwise. + * + * @param string $size Size of thumbnail (small, medium or large -- small is + * default). + * + * @return string + */ + public function thumb_link($size = 'small') + { + $imageInfo = isset($this->data['ImageInfo']) ? $this->data['ImageInfo'] : ''; + if ($imageInfo && isset($imageInfo['thumb'])) { + switch ($size) { + case 'large': + case 'medium': + return $imageInfo['medium']; + break; + + case 'small': + default: + return $imageInfo['thumb']; + break; + } + } + return false; + } + + + /** + * Get the title of the record. + * + * @return string + */ + public function title() + { + return isset($this->data['Items']['Title']) ? + $this->data['Items']['Title']['Data'] : ''; + } + + + /** + * Get the source of the record. + * + * @return string + */ + public function source() + { + return isset($this->data['Items']['TitleSource']) ? + $this->data['Items']['TitleSource']['Data'] : ''; + } + + + /** + * Return the identifier of this record within the EBSCO databases + * + * @return string Unique identifier. + */ + public function record_id() + { + return isset($this->data['id']) ? + $this->data['id'] : ''; + } + +} diff --git a/ebsco/lib/EBSCOResponse.php b/ebsco/lib/EBSCOResponse.php new file mode 100644 index 0000000..1eb3413 --- /dev/null +++ b/ebsco/lib/EBSCOResponse.php @@ -0,0 +1,547 @@ +response = $response; + } + + + /** + * Returns the XML as an associative array of data + * + * @param none + * + * @return array An associative array of data + * @access public + */ + public function result() + { + if (!empty($this->response->AuthToken)) { + return $this->buildAuthenticationToken(); + } else if (!empty($this->response->SessionToken)) { + return (string) $this->response->SessionToken; + } else if (!empty($this->response->SearchResult)) { + return $this->buildSearch(); + } else if(!empty($this->response->Record)) { + return $this->buildRetrieve(); + } else if(!empty($this->response->AvailableSearchCriteria)) { + return $this->buildInfo(); + } else { // Should not happen, it may be an exception + return $this->response; + } + } + + + /** + * Parse the SimpleXml object when an AuthenticationToken API call was executed + * + * @param none + * + * @return array An associative array of data + * @access private + */ + private function buildAuthenticationToken() + { + $token = (string) $this->response->AuthToken; + $timeout = (integer) $this->response->AuthTimeout; + + $result = array( + 'authenticationToken' => $token, + 'authenticationTimeout' => $timeout + ); + + return $result; + } + + /** + * Parse a SimpleXml object and + * return it as an associative array + * + * @param none + * + * @return array An associative array of data + * @access private + */ + private function buildSearch() + { + $hits = (integer) $this->response->SearchResult->Statistics->TotalHits; + $searchTime = (integer) $this->response->SearchResult->Statistics->TotalSearchTime / 1000; + $records = array(); + $facets = array(); + if ($hits > 0) { + $records = $this->buildRecords(); + $facets = $this->buildFacets(); + } + + $results = array( + 'recordCount' => $hits, + 'searchTime' => $searchTime, + 'numFound' => $hits, + 'start' => 0, + 'documents' => $records, + 'facets' => $facets + ); + + return $results; + } + + + /** + * Parse a SimpleXml object and + * return it as an associative array + * + * @param none + * + * @return array An associative array of data + * @access private + */ + private function buildRecords() + { + $results = array(); + + $records = $this->response->SearchResult->Data->Records->Record; + foreach ($records as $record) { + $result = array(); + + $result['ResultId'] = $record->ResultId ? (integer) $record->ResultId : ''; + $result['DbId'] = $record->Header->DbId ? (string) $record->Header->DbId : ''; + $result['DbLabel'] = $record->Header->DbLabel ? (string) $record->Header->DbLabel : ''; + $result['An'] = $record->Header->An ? (string) $record->Header->An : ''; + $result['PubType'] = $record->Header->PubType ? (string) $record->Header->PubType : ''; + $result['AccessLevel'] = $record->Header->AccessLevel ? (string) $record->Header->AccessLevel : ''; + $result['id'] = $result['An'] . '|' . $result['DbId']; + $result['PLink'] = $record->PLink ? (string) $record->PLink : ''; + if (!empty($record->ImageInfo->CoverArt)) { + foreach ($record->ImageInfo->CoverArt as $image) { + $size = (string) $image->Size; + $target = (string) $image->Target; + $result['ImageInfo'][$size] = $target; + } + } else { + $result['ImageInfo'] = ''; + } + + if ($record->FullText) { + $availability = (integer) $record->FullText->Text->Availability == 1; + $links = array(); + //RF 2012-12-18 + if (isset($record->FullText->Links)) + { + foreach ($record->FullText->Links->Link as $link) { + $type = (string) $link->Type; + $url = (string) $link->Url; + // If we have an empty url when type is pdflink then just return something so + // that the UI check for empty string will pass. + $url = empty($url) && $type == 'pdflink' ? 'http://content.ebscohost.com' : $url; + $links[$type] = $url; + } + } + $result['FullText'] = array( + 'Availability' => $availability, + 'Links' => $links + ); + } + + if ($record->CustomLinks) { + $result['CustomLinks'] = array(); + foreach ($record->CustomLinks->CustomLink as $customLink) { + $category = $customLink->Category ? (string) $customLink->Category : ''; + $icon = $customLink->Icon ? (string) $customLink->Icon : ''; + $mouseOverText = $customLink->MouseOverText ? (string) $customLink->MouseOverText : ''; + $name = $customLink->Name ? (string) $customLink->Name : ''; + $text = $customLink->Text ? (string) $customLink->Text : ''; + $url = $customLink->Url ? (string) $customLink->Url : ''; + $result['CustomLinks'][] = array( + 'Category' => $category, + 'Icon' => $icon, + 'MouseOverText' => $mouseOverText, + 'Name' => $name, + 'Text' => $text, + 'Url' => $url + ); + } + } + + if($record->Items) { + $result['Items'] = array(); + foreach ($record->Items->Item as $item) { + $name = $item->Name ? (string) $item->Name : ''; + $label = $item->Label ? (string) $item->Label : ''; + $group = $item->Group ? (string) $item->Group : ''; + $data = $item->Data ? (string) $item->Data : ''; + $result['Items'][$name] = array( + 'Name' => $name, + 'Label' => $label, + 'Group' => $group, + 'Data' => $this->toHTML($data, $group) + ); + } + } + + $results[] = $result; + } + + return $results; + } + + + /** + * Parse a SimpleXml object and + * return it as an associative array + * + * @param none + * + * @return array An associative array of data + * @access private + */ + private function buildFacets() + { + $results = array(); + + $facets = $this->response->SearchResult->AvailableFacets->AvailableFacet; + foreach ($facets as $facet) { + $values = array(); + foreach ($facet->AvailableFacetValues->AvailableFacetValue as $value) { + $this_value = (string) $value->Value; + $this_value = str_replace(array('\(','\)'), array('(', ')'), $this_value); + $this_action = (string) $value->AddAction; + $this_action = str_replace(array('\(','\)'), array('(', ')'), $this_action); + $values[] = array( + 'Value' => $this_value, + 'Action' => $this_action, + 'Count' => (string) $value->Count + ); + } + $id = (string) $facet->Id; + $label = (string) $facet->Label; + if (!empty($label)) { + $results[] = array( + 'Id' => $id, + 'Label' => $label, + 'Values' => $values, + 'isApplied' => false + ); + } + } + + return $results; + } + + + /** + * Parse a SimpleXml object and + * return it as an associative array + * + * @param none + * + * @return array An associative array of data + * @access private + */ + private function buildInfo() + { + // Sort options + $elements = $this->response->AvailableSearchCriteria->AvailableSorts->AvailableSort; + $sort = array(); + foreach ($elements as $element) { + $sort[] = array( + 'Id' => (string) $element->Id, + 'Label' => (string) $element->Label, + 'Action' => (string) $element->AddAction + ); + } + + // Search fields + $elements = $this->response->AvailableSearchCriteria->AvailableSearchFields->AvailableSearchField; + $tags = array(); + foreach ($elements as $element) { + $tags[] = array( + 'Label' => (string) $element->Label, + 'Code' => (string) $element->FieldCode + ); + } + + // Expanders + $elements = $this->response->AvailableSearchCriteria->AvailableExpanders->AvailableExpander; + $expanders = array(); + foreach ($elements as $element) { + $expanders[] = array( + 'Id' => (string) $element->Id, + 'Label' => (string) $element->Label, + 'Action' => (string) $element->AddAction, + 'selected' => false // Added because of the checkboxes + ); + } + + // Limiters + $elements = $this->response->AvailableSearchCriteria->AvailableLimiters->AvailableLimiter; + $limiters = array(); + $values = array(); + foreach ($elements as $element) { + if ($element->LimiterValues) { + $items = $element->LimiterValues->LimiterValue; + foreach($items as $item) { + $values[] = array( + 'Value' => (string) $item->Value, + 'Action' => (string) $item->AddAction, + 'selected' => false // Added because of the checkboxes + ); + } + } + $limiters[] = array( + 'Id' => (string) $element->Id, + 'Label' => (string) $element->Label, + 'Action' => (string) $element->AddAction, + 'Type' => (string) $element->Type, + 'Values' => $values, + 'selected' => false + ); + } + + $result = array( + 'sort' => $sort, + 'tags' => $tags, + 'expanders' => $expanders, + 'limiters' => $limiters + ); + + return $result; + } + + + /** + * Parse a SimpleXml object and + * return it as an associative array + * + * @param none + * + * @return array An associative array of data + * @access private + */ + private function buildRetrieve() + { + $record = $this->response->Record; + if ($record) { + $record = $record[0]; // there is only one record + } + + $result = array(); + $result['DbId'] = $record->Header->DbId ? (string) $record->Header->DbId : ''; + $result['DbLabel'] = $record->Header->DbLabel ? (string) $record->Header->DbLabel : ''; + $result['An'] = $record->Header->An ? (string) $record->Header->An : ''; + $result['id'] = $result['An'] . '|' . $result['DbId']; + $result['PubType'] = $record->Header->PubType ? (string) $record->Header->PubType : ''; + $result['AccessLevel'] = $record->Header->AccessLevel ? (string) $record->Header->AccessLevel : ''; + $result['PLink'] = $record->PLink ? (string) $record->PLink : ''; + if (!empty($record->ImageInfo->CoverArt)) { + foreach ($record->ImageInfo->CoverArt as $image) { + $size = (string) $image->Size; + $target = (string) $image->Target; + $result['ImageInfo'][$size] = $target; + } + } else { + $result['ImageInfo'] = ''; + } + if ($record->FullText) { + $availability = (integer) ($record->FullText->Text->Availability) == 1; + $links = array(); + foreach ($record->FullText->Links->Link as $link) { + $type = (string) $link->Type; + $url = (string) $link->Url; + // If we have an empty url when type is pdflink then just return something so + // that the UI check for empty string will pass. + $url = empty($url) && $type == 'pdflink' ? 'http://content.ebscohost.com' : $url; + $links[$type] = $url; + } + $value = $this->toHTML($record->FullText->Text->Value); + $result['FullText'] = array( + 'Availability' => $availability, + 'Links' => $links, + 'Value' => $value + ); + } + + if ($record->CustomLinks) { + $result['CustomLinks'] = array(); + foreach ($record->CustomLinks->CustomLink as $customLink) { + $category = $customLink->Category ? (string) $customLink->Category : ''; + $icon = $customLink->Icon ? (string) $customLink->Icon : ''; + $mouseOverText = $customLink->MouseOverText ? (string) $customLink->MouseOverText : ''; + $name = $customLink->Name ? (string) $customLink->Name : ''; + $text = $customLink->Text ? (string) $customLink->Text : ''; + $url = $customLink->Url ? (string) $customLink->Url : ''; + $result['CustomLinks'][] = array( + 'Category' => $category, + 'Icon' => $icon, + 'MouseOverText' => $mouseOverText, + 'Name' => $name, + 'Text' => $text, + 'Url' => $url + ); + } + } + + if($record->Items) { + $result['Items'] = array(); + foreach ($record->Items->Item as $item) { + $name = $item->Name ? (string) $item->Name : ''; + $label = $item->Label ? (string) $item->Label : ''; + $group = $item->Group ? (string) $item->Group : ''; + $data = $item->Data ? (string) $item->Data : ''; + $result['Items'][$name] = array( + 'Name' => $name, + 'Label' => $label, + 'Group' => $group, + 'Data' => $this->toHTML($data, $group) + ); + } + } + + return $result; + } + + + /** + * Parse a SimpleXml element and + * return it's inner XML as an HTML string + * + * @param SimpleXml $element A SimpleXml DOM + * + * @return string The HTML string + * @access protected + */ + private function toHTML($data, $group = null) + { + // Any group can be added here, but we only use Au (Author) + // Other groups, not present here, won't be transformed to HTML links + $allowed_searchlink_groups = array('au'); + + // Map xml tags to the HTML tags + // This is just a small list, the total number of xml tags is far more greater + $xml_to_html_tags = array( + ' ' ' ' '' => '', // Temporary bug fix + ' '', + ' ' ' ' ' ' ' '

    ' ' ' ' '', + ' ' ' ' ' '

    ' ' ' ' ' '

    ' ' ' ' ' ' ' 'Author', + 'su' => 'Subject' + ); + + // The XML data is XML escaped, let's unescape html entities (e.g. < => <) + $data = html_entity_decode($data); + + // Start parsing the xml data + if (!empty($data)) { + // Replace the XML tags with HTML tags + $search = array_keys($xml_to_html_tags); + $replace = array_values($xml_to_html_tags); + $data = str_replace($search, $replace, $data); + + // Temporary : fix unclosed tags + $data = preg_replace('/<\/highlight/', '', $data); + $data = preg_replace('/<\/span>>/', '', $data); + $data = preg_replace('/<\/searchLink/', '', $data); + $data = preg_replace('/<\/searchLink>>/', '', $data); + + // Parse searchLinks + if (!empty($group)) { + $group = strtolower($group); + if (in_array($group, $allowed_searchlink_groups)) { + $type = $xml_to_search_types[$group]; + $path = url('ebsco/results', array('query' => array('type' => $type))); + $link_xml = '//'; + $link_html = ""; + $data = preg_replace($link_xml, $link_html, $data); + $data = str_replace('', '', $data); + } + } + + // Replace the rest of searchLinks with simple spans + $link_xml = '//'; + $link_html = ''; + $data = preg_replace($link_xml, $link_html, $data); + $data = str_replace('', '', $data); + + // Parse bibliography (anchors and links) + $data = preg_replace('/sanitize($data); + + return $data; + } + + +} + +?> \ No newline at end of file diff --git a/ebsco/lib/sanitizer.class.php b/ebsco/lib/sanitizer.class.php new file mode 100644 index 0000000..d51c597 --- /dev/null +++ b/ebsco/lib/sanitizer.class.php @@ -0,0 +1,444 @@ +. +# All rights reserved. +# +# HTML Sanitizer is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# HTML Sanitizer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with HTML Sanitizer; if not, see . +# +# ***** END LICENSE BLOCK ***** + +/** + * Sanitize HTML contents : + * Remove dangerous tags and attributes that can lead to security issues like + * XSS or HTTP response splitting + * + * @author Frederic Minne + * @copyright Copyright © 2005-2011, Frederic Minne + * @license http://www.gnu.org/licenses/lgpl.txt GNU Lesser General Public License version 3 or later + * @version 1.1 + */ +class HTML_Sanitizer +{ + // Private fields + private $_allowedTags; + private $_allowJavascriptEvents; + private $_allowJavascriptInUrls; + private $_allowObjects; + private $_allowScript; + private $_allowStyle; + private $_additionalTags; + + /** + * Constructor + */ + public function __construct() + { + $this->resetAll(); + } + + /** + * (re)set all options to default value + */ + public function resetAll() + { + $this->_allowDOMEvents = false; + $this->_allowJavascriptInUrls = false; + $this->_allowStyle = false; + $this->_allowScript = false; + $this->_allowObjects = false; + $this->_allowStyle = false; + + $this->_allowedTags = '

    ' + . '
    1. ' + . '

      ' + . '

        ' + . '' + ; + + $this->_additionalTags = ''; + } + + /** + * Add additional tags to allowed tags + * @param string + * @access public + */ + public function addAdditionalTags( $tags ) + { + $this->_additionalTags .= $tags; + } + + /** + * Allow iframes + * @access public + */ + public function allowIframes() + { + $this->addAdditionalTags( '