diff --git a/code/aspen_app/src/components/Action/Holds/HoldNotificationPreferences.js b/code/aspen_app/src/components/Action/Holds/HoldNotificationPreferences.js index 141613f71b..0e08aa25d2 100644 --- a/code/aspen_app/src/components/Action/Holds/HoldNotificationPreferences.js +++ b/code/aspen_app/src/components/Action/Holds/HoldNotificationPreferences.js @@ -1,5 +1,5 @@ import React from 'react'; -import { FormControl, FormControlLabel, FormControlLabelText, FormControlHelper, Select, SelectTrigger, SelectInput, SelectIcon, SelectPortal, SelectBackdrop, SelectContent, SelectDragIndicatorWrapper, SelectDragIndicator, SelectItem, Icon, ChevronDownIcon, Input, InputField, Checkbox, CheckboxLabel, Text, CheckIcon, CheckboxIndicator, CheckboxIcon, FormControlHelperText } from '@gluestack-ui/themed'; +import { FormControl, FormControlLabel, FormControlLabelText, FormControlHelper, Select, SelectTrigger, SelectInput, SelectIcon, SelectPortal, SelectBackdrop, SelectContent, SelectDragIndicatorWrapper, SelectDragIndicator, SelectItem, Icon, ChevronDownIcon, Input, InputField, Checkbox, CheckboxLabel, Text, CheckIcon, CheckboxIndicator, CheckboxIcon, FormControlHelperText, SelectScrollView } from '@gluestack-ui/themed'; import _ from 'lodash'; import { getTermFromDictionary, getTranslationsWithValues } from '../../../translations/TranslationService'; @@ -32,6 +32,7 @@ export const HoldNotificationPreferences = (props) => { { @@ -116,12 +117,14 @@ export const HoldNotificationPreferences = (props) => { - {_.map(smsCarriers, function (carrier, index, array) { - if (index === smsCarrier) { - return ; - } - return ; - })} + + {_.map(smsCarriers, function (carrier, index, array) { + if (index === smsCarrier) { + return ; + } + return ; + })} + diff --git a/code/aspen_app/src/components/Action/Holds/HoldPrompt.js b/code/aspen_app/src/components/Action/Holds/HoldPrompt.js index 312ce5734b..194dc70c04 100644 --- a/code/aspen_app/src/components/Action/Holds/HoldPrompt.js +++ b/code/aspen_app/src/components/Action/Holds/HoldPrompt.js @@ -1,6 +1,6 @@ import { useQuery, useQueryClient } from '@tanstack/react-query'; import _ from 'lodash'; -import { CheckIcon, CloseIcon, Modal, ModalBackdrop, ModalContent, ModalHeader, ModalCloseButton, ModalBody, ModalFooter, FormControl, FormControlLabel, FormControlLabelText, Heading, Select, Button, ButtonGroup, ButtonText, SelectTrigger, SelectInput, SelectIcon, SelectPortal, SelectBackdrop, SelectContent, SelectDragIndicatorWrapper, SelectDragIndicator, SelectItem, Icon, ChevronDownIcon, ButtonSpinner } from '@gluestack-ui/themed'; +import { CheckIcon, CloseIcon, Modal, ModalBackdrop, ModalContent, ModalHeader, ModalCloseButton, ModalBody, ModalFooter, FormControl, FormControlLabel, FormControlLabelText, Heading, Select, Button, ButtonGroup, ButtonText, SelectTrigger, SelectInput, SelectIcon, SelectPortal, SelectBackdrop, SelectContent, SelectDragIndicatorWrapper, SelectDragIndicator, SelectItem, Icon, ChevronDownIcon, ButtonSpinner, SelectScrollView } from '@gluestack-ui/themed'; import React from 'react'; import { Platform } from 'react-native'; import { HoldsContext, LibrarySystemContext, ThemeContext, UserContext } from '../../../context/initialContext'; @@ -177,7 +177,7 @@ export const HoldPrompt = (props) => { /> ) : null} {!isFetching && (holdTypeForFormat === 'either' || holdTypeForFormat === 'item') ? : null} - {promptForHoldType || holdType === 'volume' ? : null} + {promptForHoldType || holdType === 'volume' ? : null} {_.isArray(locations) && _.size(locations) > 1 && !isEContent ? ( @@ -185,7 +185,7 @@ export const HoldPrompt = (props) => { {getTermFromDictionary(language, 'select_pickup_location')} - setLocation(itemValue)}> {locations.map((selectedLocation, index) => { if (selectedLocation.code === location) { @@ -200,12 +200,14 @@ export const HoldPrompt = (props) => { - {locations.map((availableLocations, index) => { - if (availableLocations.code === location) { - return ; - } - return ; - })} + + {locations.map((availableLocations, index) => { + if (availableLocations.code === location) { + return ; + } + return ; + })} + @@ -216,7 +218,7 @@ export const HoldPrompt = (props) => { {isPlacingHold ? getTermFromDictionary('en', 'linked_place_hold_for_account') : getTermFromDictionary('en', 'linked_checkout_to_account')} - setActiveAccount(itemValue)}> @@ -227,10 +229,12 @@ export const HoldPrompt = (props) => { - - {accounts.map((item, index) => { - return ; - })} + + + {accounts.map((item, index) => { + return ; + })} + diff --git a/code/aspen_app/src/components/Action/Holds/SelectItem.js b/code/aspen_app/src/components/Action/Holds/SelectItem.js index 60e18b6ac3..403875c399 100644 --- a/code/aspen_app/src/components/Action/Holds/SelectItem.js +++ b/code/aspen_app/src/components/Action/Holds/SelectItem.js @@ -1,8 +1,7 @@ -import { Icon, ChevronDownIcon, FormControl, FormControlLabel, FormControlLabelText, Select, SelectTrigger, SelectInput, SelectIcon, SelectPortal, SelectBackdrop, SelectContent, SelectDragIndicatorWrapper, SelectDragIndicator, SelectItem, CheckIcon, Radio, RadioGroup, RadioIndicator, RadioIcon, RadioLabel, CircleIcon } from '@gluestack-ui/themed'; +import { Icon, ChevronDownIcon, FormControl, SelectScrollView, FormControlLabel, FormControlLabelText, Select, SelectTrigger, SelectInput, SelectIcon, SelectPortal, SelectBackdrop, SelectContent, SelectDragIndicatorWrapper, SelectDragIndicator, SelectItem, CheckIcon, Radio, RadioGroup, RadioIndicator, RadioIcon, RadioLabel, CircleIcon } from '@gluestack-ui/themed'; import React from 'react'; import { Platform } from 'react-native'; import _ from 'lodash'; -import { ThemeContext } from '../../../context/initialContext'; import { getTermFromDictionary } from '../../../translations/TranslationService'; export const SelectItemHold = (props) => { @@ -54,7 +53,6 @@ export const SelectItemHold = (props) => { {getTermFromDictionary(language, 'select_item')} diff --git a/code/aspen_app/src/components/Action/Holds/SelectVolume.js b/code/aspen_app/src/components/Action/Holds/SelectVolume.js index 2b9264910a..0f9ac51aac 100644 --- a/code/aspen_app/src/components/Action/Holds/SelectVolume.js +++ b/code/aspen_app/src/components/Action/Holds/SelectVolume.js @@ -1,4 +1,4 @@ -import { FormControl, Select, CheckIcon, Radio } from 'native-base'; +import { Icon, ChevronDownIcon, FormControl, FormControlLabel, FormControlLabelText, SelectScrollView, Select, SelectTrigger, SelectInput, SelectIcon, SelectPortal, SelectBackdrop, SelectContent, SelectDragIndicatorWrapper, SelectDragIndicator, SelectItem, CheckIcon, Radio, RadioGroup, RadioIndicator, RadioIcon, RadioLabel, CircleIcon } from '@gluestack-ui/themed'; import React from 'react'; import { Platform } from 'react-native'; import { useQuery } from '@tanstack/react-query'; @@ -9,7 +9,7 @@ import _ from 'lodash'; import { getTermFromDictionary } from '../../../translations/TranslationService'; export const SelectVolume = (props) => { - const { id, volume, setVolume, showModal, promptForHoldType, holdType, setHoldType, language, url } = props; + const { id, volume, setVolume, showModal, promptForHoldType, holdType, setHoldType, language, url, textColor, theme } = props; const { status, data, error, isFetching } = useQuery({ queryKey: ['volumes', id, url], @@ -33,29 +33,35 @@ export const SelectVolume = (props) => { <> {promptForHoldType ? ( - { setHoldType(nextValue); setVolume(''); - }} - accessibilityLabel=""> - - {getTermFromDictionary(language, 'first_available')} + }}> + + + + + {getTermFromDictionary(language, 'first_available')} - - {getTermFromDictionary(language, 'specific_volume')} + + + + + {getTermFromDictionary(language, 'specific_volume')} - + ) : null} {holdType === 'volume' ? ( - {getTermFromDictionary(language, 'select_volume')} + + {getTermFromDictionary(language, 'select_volume')} + ) : null} diff --git a/code/aspen_app/src/components/loadError.js b/code/aspen_app/src/components/loadError.js index bb75741c99..c115240928 100644 --- a/code/aspen_app/src/components/loadError.js +++ b/code/aspen_app/src/components/loadError.js @@ -1,5 +1,5 @@ import React, { useCallback } from 'react'; -import { Alert, Button, Center, Heading, HStack, Icon, ScrollView, Text, Toast, VStack, Box, AlertDialog, IconButton, CloseIcon } from 'native-base'; +import { Alert, Button, Center, Heading, HStack, Icon, Text, Toast, VStack, Box } from '@gluestack-ui/themed-native-base'; import { MaterialIcons } from '@expo/vector-icons'; import _ from 'lodash'; @@ -167,4 +167,4 @@ export function popAlert(title, description, status) { }) ); } -*/ +*/ \ No newline at end of file diff --git a/code/aspen_app/src/components/loadingSpinner.js b/code/aspen_app/src/components/loadingSpinner.js index 95caca662a..4867fcd6f7 100644 --- a/code/aspen_app/src/components/loadingSpinner.js +++ b/code/aspen_app/src/components/loadingSpinner.js @@ -1,29 +1,27 @@ -import React from "react"; -import {Center, Heading, HStack, VStack, Spinner} from "native-base"; +import React from 'react'; +import { Center, Heading, HStack, VStack, Spinner } from '@gluestack-ui/themed-native-base'; /* TODO: Translate the accessibility labels */ -export function loadingSpinner(message = "") { - if (message !== "") { - return ( -
- - - - {message} - - -
- ); - } +export function loadingSpinner(message = '') { + if (message !== '') { + return ( +
+ + + {message} + +
+ ); + } - return ( -
- - - -
- ); + return ( +
+ + + +
+ ); } \ No newline at end of file diff --git a/code/aspen_app/src/screens/BrowseCategory/Home.js b/code/aspen_app/src/screens/BrowseCategory/Home.js index 159683f5c1..599adafdd7 100644 --- a/code/aspen_app/src/screens/BrowseCategory/Home.js +++ b/code/aspen_app/src/screens/BrowseCategory/Home.js @@ -335,10 +335,10 @@ export const DiscoverHomeScreen = () => { $dark-color="$textLight50" sx={{ '@base': { - fontSize: '$16', + fontSize: 16, }, '@lg': { - fontSize: '$22', + fontSize: 22, }, }}> {title} diff --git a/code/aspen_app/src/screens/GroupedWork/Variations.js b/code/aspen_app/src/screens/GroupedWork/Variations.js index a6d828fd5b..21fb1c845e 100644 --- a/code/aspen_app/src/screens/GroupedWork/Variations.js +++ b/code/aspen_app/src/screens/GroupedWork/Variations.js @@ -1,4 +1,4 @@ -import { AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogBody, AlertDialogFooter, AlertDialogBackdrop, Badge, BadgeText, FlatList, Heading, Select, VStack, Button, ButtonGroup, ButtonIcon, ButtonText, Box, Center, HStack, Text, SafeAreaView, ScrollView, SelectTrigger, SelectInput, SelectIcon, SelectPortal, SelectBackdrop, SelectContent, SelectDragIndicatorWrapper, SelectDragIndicator, SelectItem, Icon } from '@gluestack-ui/themed'; +import { AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogBody, AlertDialogFooter, AlertDialogBackdrop, Badge, BadgeText, FlatList, Heading, Select, VStack, Button, ButtonGroup, ButtonIcon, ButtonText, Box, Center, HStack, Text, SafeAreaView, ScrollView, SelectTrigger, SelectInput, SelectIcon, SelectPortal, SelectBackdrop, SelectContent, SelectDragIndicatorWrapper, SelectDragIndicator, SelectItem, Icon, SelectScrollView } from '@gluestack-ui/themed'; import { MapPinIcon } from 'lucide-react-native'; import { useRoute } from '@react-navigation/native'; import { useQuery, useQueryClient } from '@tanstack/react-query'; @@ -219,9 +219,11 @@ export const Variations = (props) => { - {_.map(holdSelectItemResponse.items, function (item, index, array) { - return ; - })} + + {_.map(holdSelectItemResponse.items, function (item, index, array) { + return ; + })} + diff --git a/code/aspen_app/src/screens/MyAccount/Events/Events.js b/code/aspen_app/src/screens/MyAccount/Events/Events.js index ec942e6e09..d6ce2dab6b 100644 --- a/code/aspen_app/src/screens/MyAccount/Events/Events.js +++ b/code/aspen_app/src/screens/MyAccount/Events/Events.js @@ -32,7 +32,7 @@ export const MyEvents = () => { const systemMessagesForScreen = []; const [filterBy, setFilterBy] = React.useState('upcoming'); - + const [paginationLabel, setPaginationLabel] = React.useState('Page 1 of 1'); const [events, updateEvents] = React.useState([]); React.useLayoutEffect(() => { @@ -57,16 +57,17 @@ export const MyEvents = () => { staleTime: 1000, onSuccess: (data) => { updateSavedEvents(data.events); + if (data.totalPages) { + let tmp = getTermFromDictionary(language, 'page_of_page'); + tmp = tmp.replace('%1%', page); + tmp = tmp.replace('%2%', data.totalPages); + console.log(tmp); + setPaginationLabel(tmp); + } }, onSettle: (data) => setLoading(false), }); - const { data: paginationLabel, isFetching: translationIsFetching } = useQuery({ - queryKey: ['totalPages', url, page, language], - queryFn: () => getTranslationsWithValues('page_of_page', [page, data.totalPages], language, library.baseUrl), - enabled: !!data, - }); - const getActionButtons = () => { return ( { {_.size(systemMessagesForScreen) > 0 ? {showSystemMessage()} : null} {getActionButtons()} - {status === 'loading' || isFetching || translationIsFetching ? ( + {status === 'loading' || isFetching ? ( loadingSpinner() ) : status === 'error' ? ( loadError('Error', '') diff --git a/code/aspen_app/src/screens/MyAccount/Lists/MyList.js b/code/aspen_app/src/screens/MyAccount/Lists/MyList.js index 9606825eb7..76fb467543 100644 --- a/code/aspen_app/src/screens/MyAccount/Lists/MyList.js +++ b/code/aspen_app/src/screens/MyAccount/Lists/MyList.js @@ -42,6 +42,7 @@ export const MyList = () => { const backgroundColor = useToken('colors', useColorModeValue('warmGray.200', 'coolGray.900')); const textColor = useToken('colors', useColorModeValue('gray.800', 'coolGray.200')); const systemMessagesForScreen = []; + const [paginationLabel, setPaginationLabel] = React.useState('Page 1 of 1'); React.useEffect(() => { if (_.isArray(systemMessages)) { @@ -87,12 +88,15 @@ export const MyList = () => { const { status, data, error, isFetching, isPreviousData } = useQuery(['list', id, user.id, sort, page], () => getListTitles(id, library.baseUrl, page, pageSize, pageSize, sort), { keepPreviousData: false, staleTime: 1000, - }); - - const { data: paginationLabel, isFetching: translationIsFetching } = useQuery({ - queryKey: ['totalPages', library.baseUrl, page, id], - queryFn: () => getTranslationsWithValues('page_of_page', [page, data?.totalPages], language, library.baseUrl), - enabled: !!data, + onSuccess: (data) => { + if (data.totalPages) { + let tmp = getTermFromDictionary(language, 'page_of_page'); + tmp = tmp.replace('%1%', page); + tmp = tmp.replace('%2%', data.totalPages); + console.log(tmp); + setPaginationLabel(tmp); + } + }, }); const handleOpenItem = (id, title) => { @@ -432,7 +436,7 @@ export const MyList = () => { return ( {_.size(systemMessagesForScreen) > 0 ? {showSystemMessage()} : null} - {status === 'loading' || isFetching || translationIsFetching ? ( + {status === 'loading' || isFetching ? ( loadingSpinner() ) : status === 'error' ? ( loadError('Error', '') diff --git a/code/aspen_app/src/screens/MyAccount/MyLibraryCard/MyLibraryCard.js b/code/aspen_app/src/screens/MyAccount/MyLibraryCard/MyLibraryCard.js index f4fbca7799..27c82a07df 100644 --- a/code/aspen_app/src/screens/MyAccount/MyLibraryCard/MyLibraryCard.js +++ b/code/aspen_app/src/screens/MyAccount/MyLibraryCard/MyLibraryCard.js @@ -245,6 +245,11 @@ const CreateLibraryCard = (data) => { } } + let showExpirationDate = true; + if (library.showCardExpiration === '0' || library.showCardExpiration === 0) { + showExpirationDate = false; + } + let icon = library.favicon; if (library.logoApp) { icon = library.logoApp; @@ -274,7 +279,7 @@ const CreateLibraryCard = (data) => { {barcodeValue} - {expirationDate && !neverExpires ? ( + {showExpirationDate && expirationDate && !neverExpires ? ( {expirationText} @@ -312,9 +317,9 @@ const CreateLibraryCard = (data) => { ) : null}
- {expirationDate && !neverExpires && numCards > 1 ? {expirationText} : null} + {showExpirationDate && expirationDate && !neverExpires && numCards > 1 ? {expirationText} : null} {numCards > 1 ? : } - {expirationDate && !neverExpires && numCards === 1 ? ( + {showExpirationDate && expirationDate && !neverExpires && numCards === 1 ? ( {expirationText} diff --git a/code/aspen_app/src/screens/MyAccount/ReadingHistory/ReadingHistory.js b/code/aspen_app/src/screens/MyAccount/ReadingHistory/ReadingHistory.js index d5002a1163..9ca1033725 100644 --- a/code/aspen_app/src/screens/MyAccount/ReadingHistory/ReadingHistory.js +++ b/code/aspen_app/src/screens/MyAccount/ReadingHistory/ReadingHistory.js @@ -31,6 +31,7 @@ export const MyReadingHistory = () => { const url = library.baseUrl; const pageSize = 25; const systemMessagesForScreen = []; + const [paginationLabel, setPaginationLabel] = React.useState('Page 1 of 1'); const [sortBy, setSortBy] = React.useState({ title: 'Sort by Title', @@ -51,16 +52,17 @@ export const MyReadingHistory = () => { staleTime: 1000, onSuccess: (data) => { updateReadingHistory(data); + if (data.totalPages) { + let tmp = getTermFromDictionary(language, 'page_of_page'); + tmp = tmp.replace('%1%', page); + tmp = tmp.replace('%2%', data.totalPages); + console.log(tmp); + setPaginationLabel(tmp); + } }, onSettle: (data) => setLoading(false), }); - const { data: paginationLabel, isFetching: translationIsFetching } = useQuery({ - queryKey: ['totalPages', url, page, language], - queryFn: () => getTranslationsWithValues('page_of_page', [page, data.totalPages], language, library.baseUrl), - enabled: !!data, - }); - useFocusEffect( React.useCallback(() => { if (_.isArray(systemMessages)) { diff --git a/code/aspen_app/src/screens/MyAccount/TitlesOnHold/MyHold.js b/code/aspen_app/src/screens/MyAccount/TitlesOnHold/MyHold.js index 41e89ba461..f97332070e 100644 --- a/code/aspen_app/src/screens/MyAccount/TitlesOnHold/MyHold.js +++ b/code/aspen_app/src/screens/MyAccount/TitlesOnHold/MyHold.js @@ -31,19 +31,16 @@ export const MyHold = (props) => { const [holdPosition, setHoldPosition] = React.useState(null); React.useEffect(() => { - async function fetchTranslations() { - if (hold.holdQueueLength) { - await getTranslationsWithValues('hold_position_with_queue', [hold.position, hold.holdQueueLength], language, library.baseUrl).then((result) => { - let term = result; - if (!term.includes('%')) { - setUsesHoldPosition(true); - setHoldPosition(term); - } - }); + if (hold.holdQueueLength) { + let tmp = getTermFromDictionary(language, 'hold_position_with_queue'); + if (hold.holdQueueLength && hold.position) { + tmp = tmp.replace('%1%', hold.position); + tmp = tmp.replace('%2%', hold.holdQueueLength); + console.log(tmp); + setUsesHoldPosition(true); + setHoldPosition(tmp); } } - - fetchTranslations(); }, [language]); if (hold.canFreeze === true) { diff --git a/code/aspen_app/src/screens/MyAccount/TitlesOnHold/MyHolds.js b/code/aspen_app/src/screens/MyAccount/TitlesOnHold/MyHolds.js index 55a2f49cbb..0319186118 100644 --- a/code/aspen_app/src/screens/MyAccount/TitlesOnHold/MyHolds.js +++ b/code/aspen_app/src/screens/MyAccount/TitlesOnHold/MyHolds.js @@ -244,13 +244,32 @@ export const MyHolds = () => { showSelectOptions = true; } + let pendingSortLength = 8 * sortBy.title.length + 80; + if (pendingSortMethod === 'author') { + pendingSortLength = 8 * sortBy.author.length + 80; + } else if (pendingSortMethod === 'format') { + pendingSortLength = 8 * sortBy.format.length + 80; + } else if (pendingSortMethod === 'status') { + pendingSortLength = 8 * sortBy.status.length + 80; + } else if (pendingSortMethod === 'placed') { + pendingSortLength = 8 * sortBy.date_placed.length + 80; + } else if (pendingSortMethod === 'position') { + pendingSortLength = 8 * sortBy.position.length + 80; + } else if (pendingSortMethod === 'location') { + pendingSortLength = 8 * sortBy.pickup_location.length + 80; + } else if (pendingSortMethod === 'libraryAccount') { + pendingSortLength = 8 * sortBy.library_account.length + 80; + } else if (pendingSortMethod === 'sortTitle') { + pendingSortLength = 8 * sortBy.title.length + 80; + } + if (section === 'pending') { if (showSelectOptions) { return ( - + { ); } + let readySortLength = 8 * sortBy.expiration.length + 80; + if (readySortMethod === 'author') { + readySortLength = 8 * sortBy.author.length + 80; + } else if (readySortMethod === 'format') { + readySortLength = 8 * sortBy.format.length + 80; + } else if (readySortMethod === 'status') { + readySortLength = 8 * sortBy.status.length + 80; + } else if (readySortMethod === 'placed') { + readySortLength = 8 * sortBy.date_placed.length + 80; + } else if (readySortMethod === 'position') { + readySortLength = 8 * sortBy.position.length + 80; + } else if (readySortMethod === 'location') { + readySortLength = 8 * sortBy.pickup_location.length + 80; + } else if (readySortMethod === 'libraryAccount') { + readySortLength = 8 * sortBy.library_account.length + 80; + } else if (readySortMethod === 'sortTitle') { + readySortLength = 8 * sortBy.title.length + 80; + } else if (readySortMethod === 'expire') { + readySortLength = 8 * sortBy.expiration.length + 80; + } + if (section === 'ready') { return ( - + { console.log('currentIndex: ' + currentIndex); console.log('currentSource: ' + currentSource); - console.log(SEARCH.availableFacets); let facets = SEARCH.availableFacets ? Object.keys(SEARCH.availableFacets) : []; let pendingFilters = SEARCH.pendingFilters ?? []; diff --git a/code/aspen_app/src/screens/Search/SearchByCategory.js b/code/aspen_app/src/screens/Search/SearchByCategory.js index 8f0501c664..c31afa2ecf 100644 --- a/code/aspen_app/src/screens/Search/SearchByCategory.js +++ b/code/aspen_app/src/screens/Search/SearchByCategory.js @@ -20,14 +20,23 @@ export const SearchResultsForBrowseCategory = () => { const { theme, textColor, colorMode } = React.useContext(ThemeContext); const { systemMessages } = React.useContext(SystemMessagesContext); - const [paginationLabel, setPaginationLabel] = React.useState('1 of 1'); const category = useRoute().params.id ?? ''; + const [paginationLabel, setPaginationLabel] = React.useState('Page 1 of 1'); const { status, data, error, isFetching, isPreviousData } = useQuery({ queryKey: ['searchResultsForBrowseCategory', category, page, 25, library.baseUrl, language], queryFn: () => fetchSearchResultsForBrowseCategory(category, page, 25, library.baseUrl, language), keepPreviousData: true, staleTime: 1000, + onSuccess: (data) => { + if (data.totalPages) { + let tmp = getTermFromDictionary(language, 'page_of_page'); + tmp = tmp.replace('%1%', page); + tmp = tmp.replace('%2%', data.totalPages); + console.log(tmp); + setPaginationLabel(tmp); + } + }, }); const systemMessagesForScreen = []; @@ -42,17 +51,6 @@ export const SearchResultsForBrowseCategory = () => { } }, [systemMessages]); - const { data: paginationLabelData, isFetching: translationIsFetching } = useQuery({ - queryKey: ['totalPages', library.baseUrl, page, category, language], - queryFn: () => getTranslationsWithValues('page_of_page', [page ?? 1, data?.totalPages ?? 1], language, library.baseUrl), - enabled: !!data, - onSuccess: (data) => { - if (!data.includes('%')) { - setPaginationLabel(data); - } - }, - }); - const Paging = () => { if (data.totalPages > 1) { return ( @@ -111,7 +109,7 @@ export const SearchResultsForBrowseCategory = () => { return ( {_.size(systemMessagesForScreen) > 0 ? {showSystemMessage()} : null} - {status === 'loading' || isFetching || translationIsFetching ? ( + {status === 'loading' || isFetching ? ( loadingSpinner('Fetching results...') ) : status === 'error' ? ( loadError('Error', '') diff --git a/code/aspen_app/src/screens/Search/SearchResults.js b/code/aspen_app/src/screens/Search/SearchResults.js index b02bd7113f..c6d186079c 100644 --- a/code/aspen_app/src/screens/Search/SearchResults.js +++ b/code/aspen_app/src/screens/Search/SearchResults.js @@ -36,7 +36,7 @@ export const SearchResults = () => { const { currentIndex, currentSource, updateCurrentIndex, updateCurrentSource, updateIndexes, updateSources } = React.useContext(SearchContext); const { theme, textColor, colorMode } = React.useContext(ThemeContext); const url = library.baseUrl; - const [paginationLabel, setPaginationLabel] = React.useState('1 of 1'); + const [paginationLabel, setPaginationLabel] = React.useState('Page 1 of 1'); const queryClient = useQueryClient(); const { systemMessages, updateSystemMessages } = React.useContext(SystemMessagesContext); @@ -85,6 +85,13 @@ export const SearchResults = () => { keepPreviousData: true, staleTime: 1000, onSuccess: (data) => { + if (data.totalPages) { + let tmp = getTermFromDictionary(language, 'page_of_page'); + tmp = tmp.replace('%1%', page); + tmp = tmp.replace('%2%', data.totalPages); + console.log(tmp); + setPaginationLabel(tmp); + } if ((data.totalResults === 1 || data.totalResults === '1') && isScannerSearch) { const result = data.results[0]; if (result.key) { @@ -99,17 +106,6 @@ export const SearchResults = () => { }, }); - const { data: paginationLabelData, isFetching: translationIsFetching } = useQuery({ - queryKey: ['totalPages', url, page, term, scope, params, language, currentIndex, currentSource], - queryFn: () => getTranslationsWithValues('page_of_page', [page ?? 1, data?.totalPages ?? 1], language, library.baseUrl), - enabled: !!data, - onSuccess: (data) => { - if (!data.includes('%')) { - setPaginationLabel(data); - } - }, - }); - const Header = () => { const num = _.toInteger(data?.totalResults); if (num > 0) { @@ -193,7 +189,7 @@ export const SearchResults = () => { return ( {_.size(systemMessagesForScreen) > 0 ? {showSystemMessage()} : null} - {status === 'loading' || isFetching || translationIsFetching ? ( + {status === 'loading' || isFetching ? ( loadingSpinner() ) : status === 'error' ? ( loadError('Error', '') diff --git a/code/aspen_app/src/themes/theme.js b/code/aspen_app/src/themes/theme.js index dce69f5a1c..fc473eb1e7 100644 --- a/code/aspen_app/src/themes/theme.js +++ b/code/aspen_app/src/themes/theme.js @@ -106,7 +106,16 @@ export async function getThemeInfo(url = null) { const getColorNumber = (index) => (index === 0 ? 50 : index * 100); -const getContrastText = (color) => (chroma.contrast(color, '#ffffff') < 6 ? '#000000' : '#ffffff'); +const getContrastText = (color) => { + let ratioOnWhite = chroma.contrast(color, '#ffffff'); + let ratioOnBlack = chroma.contrast(color, '#000000'); + + if (ratioOnBlack > ratioOnWhite) { + return '#000000'; + } else { + return '#ffffff'; + } +}; function generateSwatches(swatch) { const LIGHTNESS_MAP = [0.95, 0.85, 0.75, 0.65, 0.55, 0.45, 0.35, 0.25, 0.15, 0.05]; diff --git a/code/aspen_app/src/translations/defaults.json b/code/aspen_app/src/translations/defaults.json index b006822ebc..bb62255da6 100644 --- a/code/aspen_app/src/translations/defaults.json +++ b/code/aspen_app/src/translations/defaults.json @@ -545,5 +545,8 @@ "allowed_screen_brightness": "is allowed to access this device's screen brightness.", "not_allowed_screen_brightness": "is not allowed to access this device's brightness.", "update_screen_brightness_ios": "Open your device settings and choose how you'd like to allow access to adjusting your screen brightness.", - "update_screen_brightness_android": "Open your device settings, tap 'Permissions' and choose how you'd like to allow access to adjusting your screen brightness." + "update_screen_brightness_android": "Open your device settings, tap 'Permissions' and choose how you'd like to allow access to adjusting your screen brightness.", + "last_checkout": "Last Checkout", + "appears_on_these_lists": "Appears on these lists", + "appears_on_list": "Appears on list" } \ No newline at end of file diff --git a/code/aspen_app/src/util/recordActions.js b/code/aspen_app/src/util/recordActions.js index 5952917176..c3041f4c69 100644 --- a/code/aspen_app/src/util/recordActions.js +++ b/code/aspen_app/src/util/recordActions.js @@ -1,12 +1,11 @@ import { create } from 'apisauce'; import _ from 'lodash'; import * as WebBrowser from 'expo-web-browser'; - +import AsyncStorage from '@react-native-async-storage/async-storage'; // custom components and helper files import { popAlert, popToast } from '../components/loadError'; import { createAuthTokens, getHeaders, postData, problemCodeMap } from './apiAuth'; import { GLOBALS } from './globals'; -import { LIBRARY } from './loadLibrary'; import { getTermFromDictionary } from '../translations/TranslationService'; // complete the action on the item, i.e. checkout, hold, or view sample diff --git a/code/aspen_app/src/util/search.js b/code/aspen_app/src/util/search.js index 536ef84757..3ba6d8fe12 100644 --- a/code/aspen_app/src/util/search.js +++ b/code/aspen_app/src/util/search.js @@ -418,7 +418,7 @@ export function buildParamsForUrl() { facet = facet.replaceAll(' ', '+'); params = params.concat('&filter[]=' + field + ':' + facet); } else { - params = params.concat('&filter[]=' + encodeURIComponent(field + ':' + facet)); + params = params.concat('&filter[]=' + field + ':' + facet); } }); } diff --git a/code/web/CatalogConnection.php b/code/web/CatalogConnection.php index ae53b7bc80..7e181f36b6 100644 --- a/code/web/CatalogConnection.php +++ b/code/web/CatalogConnection.php @@ -1774,12 +1774,12 @@ public function initiatePasswordResetByBarcode() : array { } } - public function checkoutBySip(User $patron, $barcode, $locationId): array { - return $this->driver->checkoutBySip($patron, $barcode, $locationId); + public function checkoutBySip(User $patron, $barcode, $currentLocationId): array { + return $this->driver->checkoutBySip($patron, $barcode, $currentLocationId); } - public function checkoutByAPI(User $patron, $barcode, $locationId): array { - return $this->driver->checkoutByAPI($patron, $barcode, $locationId); + public function checkoutByAPI(User $patron, $barcode, $currentLocationId): array { + return $this->driver->checkoutByAPI($patron, $barcode, $currentLocationId); } public function allowUpdatesOfPreferredName(User $patron) : bool { diff --git a/code/web/Drivers/AbstractIlsDriver.php b/code/web/Drivers/AbstractIlsDriver.php index 216dca1158..aed92d375b 100644 --- a/code/web/Drivers/AbstractIlsDriver.php +++ b/code/web/Drivers/AbstractIlsDriver.php @@ -717,7 +717,7 @@ public function bypassReadingHistoryUpdate($patron, $isNightlyUpdate) : bool { return false; } - public function checkoutBySip(User $patron, $barcode, $locationId) { + public function checkoutBySip(User $patron, $barcode, $currentLocationId) { $checkout_result = []; $success = false; $title = translate([ @@ -735,6 +735,17 @@ public function checkoutBySip(User $patron, $barcode, $locationId) { ]), ]; + require_once ROOT_DIR . '/sys/AspenLiDA/SelfCheckSetting.php'; + $scoSettings = new AspenLiDASelfCheckSetting(); + $checkoutLocationSetting = $scoSettings->getCheckoutLocationSetting($currentLocationId); + $checkoutLocation = $currentLocationId; // assign checkout to current location logged into (default) + if($checkoutLocationSetting == 1) { + // assign checkout to user home location + $checkoutLocation = $patron->getHomeLocationCode(); + } /* else if ($checkoutLocationSetting == 2) { + // not yet supported with SIP since we can't easily find the item off barcode only + } */ + $mySip = new sip2(); $mySip->hostname = $this->accountProfile->sipHost; $mySip->port = $this->accountProfile->sipPort; @@ -755,7 +766,7 @@ public function checkoutBySip(User $patron, $barcode, $locationId) { $mySip->patron = $patron->getBarcode(); $mySip->patronpwd = $patron->getPasswordOrPin(); - $in = $mySip->msgCheckout($barcode, '', 'N', '', 'N', 'N', 'N', $locationId); + $in = $mySip->msgCheckout($barcode, '', 'N', '', 'N', 'N', 'N', $checkoutLocation); $msg_result = $mySip->get_message($in); $checkoutResponse = null; @@ -796,7 +807,7 @@ public function checkoutBySip(User $patron, $barcode, $locationId) { ]; } - public function checkoutByAPI(User $patron, $barcode, $locationId): array { + public function checkoutByAPI(User $patron, $barcode, $currentLocationId): array { return [ 'success' => false, 'message' => 'This functionality has not been implemented for this ILS', diff --git a/code/web/Drivers/CarlX.php b/code/web/Drivers/CarlX.php index 7e156ac980..85e54c9084 100644 --- a/code/web/Drivers/CarlX.php +++ b/code/web/Drivers/CarlX.php @@ -1040,7 +1040,7 @@ function selfRegister(): array { $request->PagingParameters->StartPos = 0; $request->PagingParameters->NoOfRecords = 20; $request->Modifiers = new stdClass(); - $request->Modifiers->InstitutionCode = 'NASH'; + $request->Modifiers->InstitutionCode = $library->institutionCode; $result = $this->doSoapRequest('searchPatron', $request, $this->patronWsdl, $this->genericResponseSOAPCallOptions); if ($result) { $noEmailMatch = stripos($result->ResponseStatuses->ResponseStatus[0]->ShortMessage, 'No matching records found'); @@ -1054,17 +1054,38 @@ function selfRegister(): array { // SEND EMAIL TO DUPLICATE EMAIL ADDRESS try { - $body = $interface->fetch($this->getSelfRegTemplate('duplicate_email')); + require_once ROOT_DIR . '/sys/Email/EmailTemplate.php'; + $emailTemplate = EmailTemplate::getActiveTemplate('duplicateEmail'); + if ($emailTemplate != null) { + $newUser = [ + 'firstname' => $firstName, + 'lastname' => $lastName, + 'ils_barcode' => $tempPatronID, + 'email' => $email, + ]; + if (!empty($newUser->email)) { + $parameters = [ + 'user' => $newUser, + 'library' => $library + ]; + $emailTemplate->sendEmail($newUser->email, $parameters); + } + } +/* $body = $interface->fetch($this->getSelfRegTemplate('duplicate_email')); require_once ROOT_DIR . '/sys/Email/Mailer.php'; $mail = new Mailer(); $subject = 'Nashville Public Library: you have an account!'; - $mail->send($email, $subject, $body, 'no-reply@nashville.gov'); + $mail->send($email, $subject, $body, 'no-reply@nashville.gov');*/ } catch (Exception $e) { // SendGrid Failed } return [ 'success' => false, - 'message' => 'You tried to register for a Digital Access Card, but you might already have a card with Nashville Public Library. Please check your email for further instructions.', + 'message' => translate([ + 'text' => 'This email address already exists in our database. Please contact your library for account information or use a different email.', + 'isPublicFacing' => true, + ]), +/* 'message' => 'You tried to register for a Digital Access Card, but you might already have a card with Nashville Public Library. Please check your email for further instructions.',*/ 'barcode' => $tempPatronID, ]; } @@ -1088,7 +1109,7 @@ function selfRegister(): array { $request->PagingParameters->StartPos = 0; $request->PagingParameters->NoOfRecords = 20; $request->Modifiers = new stdClass(); - $request->Modifiers->InstitutionCode = 'NASH'; + $request->Modifiers->InstitutionCode = $library->institutionCode; $result = $this->doSoapRequest('searchPatron', $request, $this->patronWsdl, $this->genericResponseSOAPCallOptions); if ($result) { $noNameBirthdateMatch = stripos($result->ResponseStatuses->ResponseStatus[0]->ShortMessage, 'No matching records found'); @@ -1102,17 +1123,38 @@ function selfRegister(): array { // SEND EMAIL TO DUPLICATE NAME+BIRTHDATE REGISTRANT EMAIL ADDRESS try { - $body = $interface->fetch($this->getSelfRegTemplate('duplicate_name+birthdate')); + require_once ROOT_DIR . '/sys/Email/EmailTemplate.php'; + $emailTemplate = EmailTemplate::getActiveTemplate('duplicateNameDOB'); + if ($emailTemplate != null) { + $newUser = [ + 'firstname' => $firstName, + 'lastname' => $lastName, + 'ils_barcode' => $tempPatronID, + 'email' => $email, + ]; + if (!empty($newUser->email)) { + $parameters = [ + 'user' => $newUser, + 'library' => $library + ]; + $emailTemplate->sendEmail($newUser->email, $parameters); + } + } +/* $body = $interface->fetch($this->getSelfRegTemplate('duplicate_name+birthdate')); require_once ROOT_DIR . '/sys/Email/Mailer.php'; $mail = new Mailer(); $subject = 'Nashville Public Library: you might already have an account!'; - $mail->send($email, $subject, $body, 'no-reply@nashville.gov'); + $mail->send($email, $subject, $body, 'no-reply@nashville.gov');*/ } catch (Exception $e) { //SendGrid failed } return [ 'success' => false, - 'message' => 'You tried to register for a Digital Access Card, but you might already have a card with Nashville Public Library. Please check your email for further instructions.', + 'message' => translate([ + 'text' => 'This user already exists in our database. Please contact your library for account information.', + 'isPublicFacing' => true, + ]), +/* 'message' => 'You tried to register for a Digital Access Card, but you might already have a card with Nashville Public Library. Please check your email for further instructions.',*/ 'barcode' => $tempPatronID, ]; } @@ -1154,7 +1196,8 @@ function selfRegister(): array { $request->Patron->RegBranch = $configArray['Catalog']['selfRegRegBranch']; $request->Patron->RegisteredBy = $configArray['Catalog']['selfRegRegisteredBy']; - // VALIDATE BIRTH DATE. + // This is now done through setting minimum age required in Primary Configuration +/* // VALIDATE BIRTH DATE. // DENY REGISTRATION IF REGISTRANT IS NOT 13 - 113 YEARS OLD if ($library && $library->promptForBirthDateInSelfReg) { $birthDate = trim($_REQUEST['birthDate']); @@ -1171,7 +1214,7 @@ function selfRegister(): array { 'message' => 'You must be 13 years old to register.', ]; } - } + }*/ $result = $this->doSoapRequest('createPatron', $request, $this->patronWsdl, $this->genericResponseSOAPCallOptions); if ($result) { $success = stripos($result->ResponseStatuses->ResponseStatus->ShortMessage, 'Success') !== false; @@ -1232,19 +1275,37 @@ function selfRegister(): array { // FOLLOWING SUCCESSFUL SELF REGISTRATION, EMAIL PATRON THE LIBRARY CARD NUMBER try { - $body = $firstName . " " . $lastName . "\n\n"; + require_once ROOT_DIR . '/sys/Email/EmailTemplate.php'; + $emailTemplate = EmailTemplate::getActiveTemplate('welcome'); + if ($emailTemplate != null) { + $newUser = [ + 'firstname' => $firstName, + 'lastname' => $lastName, + 'ils_barcode' => $tempPatronID, + 'email' => $email, + ]; + if (!empty($newUser->email)) { + $parameters = [ + 'user' => $newUser, + 'library' => $library + ]; + $emailTemplate->sendEmail($newUser->email, $parameters); + } + } +/* $body = $firstName . " " . $lastName . "\n\n"; $body .= translate([ 'text' => 'Thank you for registering for a Digital Access Card at the Nashville Public Library. Your library card number is:', 'isPublicFacing' => true, 'isAdminEnteredData' => true, ]); $body .= "\n\n" . $tempPatronID . "\n\n"; - $body_template = $interface->fetch($this->getSelfRegTemplate('success')); + $body_template = EmailTemplate::getActiveTemplate('welcome'); + //$body_template = $interface->fetch($this->getSelfRegTemplate('success')); $body .= $body_template; require_once ROOT_DIR . '/sys/Email/Mailer.php'; $mail = new Mailer(); $subject = 'Welcome to the Nashville Public Library'; - $mail->send($email, $subject, $body, 'no-reply@nashville.gov'); + $mail->send($email, $subject, $body, 'no-reply@nashville.gov');*/ } catch (Exception $e) { // SendGrid Failed } diff --git a/code/web/Drivers/Koha.php b/code/web/Drivers/Koha.php index e8a8b30822..54c2bb2ec5 100644 --- a/code/web/Drivers/Koha.php +++ b/code/web/Drivers/Koha.php @@ -8034,7 +8034,7 @@ public function bypassReadingHistoryUpdate($patron, $isNightlyUpdate) : bool { } } - public function checkoutByAPI(User $patron, $barcode, $locationId): array { + public function checkoutByAPI(User $patron, $barcode, $currentLocationId): array { if($this->getKohaVersion() >= 23.11) { $item = []; $result = [ @@ -8071,6 +8071,10 @@ public function checkoutByAPI(User $patron, $barcode, $locationId): array { 'isPublicFacing' => true, ]); } else { + require_once ROOT_DIR . '/sys/AspenLiDA/SelfCheckSetting.php'; + $scoSettings = new AspenLiDASelfCheckSetting(); + $checkoutLocationSetting = $scoSettings->getCheckoutLocationSetting($currentLocationId); + $this->initDatabaseConnection(); /** @noinspection SqlResolve */ $sql = "SELECT itemnumber, biblionumber, holdingbranch FROM items WHERE barcode = '" . mysqli_escape_string($this->dbConnection, $barcode) . "'"; @@ -8078,15 +8082,25 @@ public function checkoutByAPI(User $patron, $barcode, $locationId): array { if ($lookupItemResult->num_rows == 1) { $itemRow = $lookupItemResult->fetch_assoc(); $recordId = $itemRow['itemnumber']; + $holdingBranch = $itemRow['holdingbranch']; $checkoutParams = [ 'patron_id' => (int)$patron->unique_ils_id, 'item_id' => (int)$recordId, ]; $postParams = json_encode($checkoutParams); + $checkoutLocation = $currentLocationId; // assign checkout to current location logged into (default) + if($checkoutLocationSetting == 1) { + // assign checkout to user home location + $checkoutLocation = $patron->getHomeLocationCode(); + } else if ($checkoutLocationSetting == 2) { + // assign checkout to item location/holding branch + $checkoutLocation = $holdingBranch; + } + $this->apiCurlWrapper->addCustomHeaders([ 'Authorization: Bearer ' . $oAuthToken, - 'x-koha-library: ' . $locationId, + 'x-koha-library: ' . $checkoutLocation, 'User-Agent: Aspen Discovery', 'Accept: */*', 'Cache-Control: no-cache', diff --git a/code/web/interface/themes/responsive/RecordDrivers/Events/springshare_libcal_result.tpl b/code/web/interface/themes/responsive/RecordDrivers/Events/springshare_libcal_result.tpl index 50e82b46f4..06b26910e7 100644 --- a/code/web/interface/themes/responsive/RecordDrivers/Events/springshare_libcal_result.tpl +++ b/code/web/interface/themes/responsive/RecordDrivers/Events/springshare_libcal_result.tpl @@ -44,7 +44,7 @@ {/if} - {if !empty($room)} + {if !empty($recordDriver->getBranch())}
{translate text="Location" isPublicFacing=true}
@@ -58,11 +58,6 @@
{$room}
- {elseif !empty($recordDriver->getBranch())} -
{translate text="Location" isPublicFacing=true}
-
- {$recordDriver->getBranch()} -
{else} {*Empty div so buttons display correctly even without location info*}
diff --git a/code/web/release_notes/24.04.00.MD b/code/web/release_notes/24.04.00.MD index c906c9f61e..ab85abd8ce 100644 --- a/code/web/release_notes/24.04.00.MD +++ b/code/web/release_notes/24.04.00.MD @@ -1,6 +1,13 @@ ## Aspen LiDA Updates +- Fixes on various screens to only load translations when a desirable translation is returned from Discovery. (Tickets 127275, 127601, 127621, 128390) (*KK*) +- Fixed a bug where sometimes facets would not apply correctly to searches. (Tickets 127775, 127944, 129301, 129539) (*KK*) - Fixed a bug where device browsers were not properly closing and would not allow additional browsers to be opened. (Ticket 127963) (*KK*) -- Fixed a bug with cover images on the Holds screen sometimes loading incorrect images. Ticket (129284) (*KK*) +- The Library Card screen will now respect the Discovery setting for hiding card expiration dates. (Ticket 128690) (*KK*) +- Fixed a bug with cover images on the Holds screen sometimes loading incorrect images. (Ticket 129284) (*KK*) +- Select menus when placing a hold (i.e. Pickup Locations, Volumes, Linked Accounts) will now allow users to scroll through the options if needed. (Ticket 129876) (*KK*) +- Fixed a bug where some devices weren't allowing users to option select menus when placing a hold. (Ticket 129876) (*KK*) +- Menu links without a scheme or domain will now properly load when opened. (Ticket 129553) (*KK*) +- Fixed a bug that would sometimes display a lesser contrasting color for text color on buttons. (Ticket 129608) (*KK*) - If a library has been put in Offline Mode, patrons will not be able to log into Aspen LiDA until it's back online. (*KK*) - Added a Permissions Dashboard screen to view what's enabled/disabled and how to update the status. (*KK*) @@ -55,8 +62,23 @@ // kirstien ### Koha Updates - For Messaging Settings, allow selecting Digest checkbox for Holds Filled as part of Koha 23.11. (*KK*) +- For Self-Check with Aspen LiDA libraries using Koha 23.11 or newer, we will use the Checkout API endpoint instead of SIP. (*KK*) + +### Self-Check Updates +- Added settings to define where a checkout should be assigned to. + +
+ +#### New Settings +- Aspen LiDA > Self-Check Settings > Assign Checkouts To +
// kodi +### carlX Updates +- Updated self registration to be more generic (Ticket 127443) (*KL*) + +### Springshare LibCal Updates +- Fixed issue with display information for Springshare LibCal events with no 'campus' location (*KL*) // other ### Other Updates diff --git a/code/web/services/API/AbstractAPI.php b/code/web/services/API/AbstractAPI.php index 39449d014a..d3ecfffb9a 100644 --- a/code/web/services/API/AbstractAPI.php +++ b/code/web/services/API/AbstractAPI.php @@ -36,4 +36,64 @@ function getLiDAVersion() { } return 0; } + + function getLiDASession() { + if (function_exists('getallheaders')) { + foreach (getallheaders() as $name => $value) { + if ($name == 'LiDA-SessionID' || $name == 'lida-sessionid') { + $sessionId = explode(' ', $value); + return $sessionId[0]; + } + } + } + return false; + } + + function getLiDAUserAgent() { + if (function_exists('getallheaders')) { + foreach (getallheaders() as $name => $value) { + if ($name == 'User-Agent' || $name == 'user-agent') { + if (str_contains($value, 'Aspen LiDA') || str_contains($value, 'aspen lida')) { + return true; + } + } + } + } + return false; + } + + /** + * @return array + * @noinspection PhpUnused + */ + function loadUsernameAndPassword() { + $username = $_REQUEST['username'] ?? ''; + $password = $_REQUEST['password'] ?? ''; + + if (isset($_POST['username']) && isset($_POST['password'])) { + $username = $_POST['username']; + $password = $_POST['password']; + } + + if (is_array($username)) { + $username = reset($username); + } + if (is_array($password)) { + $password = reset($password); + } + return [$username, $password]; + } + + /** + * @return bool|User + */ + function getUserForApiCall() { + $user = false; + [$username, $password] = $this->loadUsernameAndPassword(); + $user = UserAccount::validateAccount($username, $password); + if ($user !== false && $user->source == 'admin') { + return false; + } + return $user; + } } \ No newline at end of file diff --git a/code/web/services/API/EventAPI.php b/code/web/services/API/EventAPI.php index 925f04ecb9..43cf9f1028 100644 --- a/code/web/services/API/EventAPI.php +++ b/code/web/services/API/EventAPI.php @@ -631,41 +631,6 @@ function getSavedEvents() { } } - /** - * @return array - * @noinspection PhpUnused - */ - private function loadUsernameAndPassword() { - $username = $_REQUEST['username'] ?? ''; - $password = $_REQUEST['password'] ?? ''; - - if (isset($_POST['username']) && isset($_POST['password'])) { - $username = $_POST['username']; - $password = $_POST['password']; - } - - if (is_array($username)) { - $username = reset($username); - } - if (is_array($password)) { - $password = reset($password); - } - return [$username, $password]; - } - - /** - * @return bool|User - */ - protected function getUserForApiCall() { - $user = false; - [$username, $password] = $this->loadUsernameAndPassword(); - $user = UserAccount::validateAccount($username, $password); - if ($user !== false && $user->source == 'admin') { - return false; - } - return $user; - } - function getBreadcrumbs(): array { return []; } diff --git a/code/web/services/API/ItemAPI.php b/code/web/services/API/ItemAPI.php index d12fdb26cf..adb8d0edc5 100644 --- a/code/web/services/API/ItemAPI.php +++ b/code/web/services/API/ItemAPI.php @@ -1166,10 +1166,19 @@ function getVariations() { /** @var File_MARC_Control_Field $oclcNumber */ $oclcNumber = $relatedRecordDriver->getOCLCNumber(); + $actionButtons = []; + $actions = $relatedVariation->getActions(); + foreach ($actions as $key => $action) { + $actionButtons[$key]['id'] = $action['id'] . '_' . $key; + $actionButtons[$key]['type'] = $action['type']; + $actionButtons[$key]['title'] = $action['title']; + $actionButtons[$key]['requireLogin'] = $action['requireLogin']; + } + $variations[$relatedVariation->label]['id'] = $relatedRecord->id; $variations[$relatedVariation->label]['source'] = $relatedRecord->source; $variations[$relatedVariation->label]['closedCaptioned'] = (int) $relatedRecord->closedCaptioned; - $variations[$relatedVariation->label]['actions'] = $relatedVariation->getActions(); + $variations[$relatedVariation->label]['actions'] = $actionButtons; $variations[$relatedVariation->label]['variationId'] = $relatedVariation->databaseId; $variations[$relatedVariation->label]['holdType'] = $holdType; $variations[$relatedVariation->label]['statusIndicator'] = [ @@ -1272,7 +1281,17 @@ function getRecords() { 'showItsHere' => (int)$library->showItsHere, 'isGlobalScope' => $interface->getVariable('isGlobalScope'), ]; - $records[$relatedRecord->id]['actions'] = $relatedRecord->getActions(); + + $buttons = []; + $actionButtons = $relatedRecord->getActions(); + foreach ($actionButtons as $key => $actionButton) { + $buttons[$key]['id'] = $actionButton['id'] . '_' . $key; + $buttons[$key]['type'] = $actionButton['type']; + $buttons[$key]['title'] = $actionButton['title']; + $buttons[$key]['requireLogin'] = $actionButton['requireLogin']; + } + + $records[$relatedRecord->id]['actions'] = $buttons; //$records[$relatedRecord->id]['information'] = $relatedRecord->getItemSummary(); if ($source == 'hoopla') { diff --git a/code/web/services/API/ListAPI.php b/code/web/services/API/ListAPI.php index 7275297c38..d1b28f0730 100644 --- a/code/web/services/API/ListAPI.php +++ b/code/web/services/API/ListAPI.php @@ -308,32 +308,6 @@ function getUserLists() { ]; } - /** - * @return array - * @noinspection PhpUnused - */ - private function loadUsernameAndPassword(): array { - $username = $_REQUEST['username'] ?? ''; - $password = $_REQUEST['password'] ?? ''; - - // check for post request data - if (isset($_POST['username']) && isset($_POST['password'])) { - $username = $_POST['username']; - $password = $_POST['password']; - } - - if (is_array($username)) { - $username = reset($username); - } - if (is_array($password)) { - $password = reset($password); - } - return [ - $username, - $password, - ]; - } - /** * Get's RSS Feed * @noinspection PhpUnused diff --git a/code/web/services/API/SearchAPI.php b/code/web/services/API/SearchAPI.php index b92f3479eb..5e934c19ed 100644 --- a/code/web/services/API/SearchAPI.php +++ b/code/web/services/API/SearchAPI.php @@ -2668,50 +2668,6 @@ function getSavedSearchResults() { return $response; } - /** - * @return array - * @noinspection PhpUnused - */ - private function loadUsernameAndPassword(): array { - if (isset($_REQUEST['username'])) { - $username = $_REQUEST['username']; - } else { - $username = ''; - } - if (isset($_REQUEST['password'])) { - $password = $_REQUEST['password']; - } else { - $password = ''; - } - - // check for post request data - if (isset($_POST['username']) && isset($_POST['password'])) { - $username = $_POST['username']; - $password = $_POST['password']; - } - - if (is_array($username)) { - $username = reset($username); - } - if (is_array($password)) { - $password = reset($password); - } - return [ - $username, - $password, - ]; - } - - protected function getUserForApiCall() { - $user = false; - [$username, $password] = $this->loadUsernameAndPassword(); - $user = UserAccount::validateAccount($username, $password); - if ($user !== false && $user->source == 'admin') { - return false; - } - return $user; - } - /** @noinspection PhpUnused */ function getAppSearchResults(): array { global $configArray; @@ -3283,9 +3239,55 @@ function searchLite() { $items[$recordKey]['language'] = $record['language'][0]; $items[$recordKey]['summary'] = $record['display_description']; $items[$recordKey]['itemList'] = []; + $items[$recordKey]['lastCheckOut'] = null; + $items[$recordKey]['appearsOnLists'] = []; require_once ROOT_DIR . '/RecordDrivers/GroupedWorkDriver.php'; $groupedWorkDriver = new GroupedWorkDriver($record['id']); if ($groupedWorkDriver->isValid()) { + $user = $this->getUserForApiCall(); + if ($user && !($user instanceof AspenError)) { + require_once ROOT_DIR . '/sys/ReadingHistoryEntry.php'; + $readingHistoryEntry = new ReadingHistoryEntry(); + $readingHistoryEntry->userId = $user->id; + $readingHistoryEntry->deleted = 0; + $readingHistoryEntry->groupedWorkPermanentId = $groupedWorkDriver->getPermanentId(); + $readingHistoryEntry->groupBy('groupedWorkPermanentId'); + $readingHistoryEntry->selectAdd(); + $readingHistoryEntry->selectAdd('MAX(checkOutDate) as checkOutDate'); + if ($readingHistoryEntry->find(true)) { + $items[$recordKey]['lastCheckOut'] = $readingHistoryEntry->checkOutDate; + } + + $userLists = []; + require_once ROOT_DIR . '/sys/UserLists/UserList.php'; + require_once ROOT_DIR . '/sys/UserLists/UserListEntry.php'; + $userListEntry = new UserListEntry(); + $userListEntry->source = 'GroupedWork'; + $userListEntry->sourceId = $groupedWorkDriver->getPermanentId(); + $userListEntry->find(); + while ($userListEntry->fetch()) { + $userList = new UserList(); + $userList->id = $userListEntry->listId; + if ($userList->find(true)) { + $okToShow = false; + $key = $userList->id; + if (!$userList->deleted) { + if($user->id == $userList->user_id || ($userList->public == 1 && $userList->searchable == 1)) { + $okToShow = true; + } + } + + if ($okToShow) { + $userLists[$key] = [ + 'id' => $userList->id, + 'title' => $userList->title, + ]; + } + } + } + ksort($userLists); + $items[$recordKey]['appearsOnLists'] = $userLists; + } $i = 0; $relatedManifestations = $groupedWorkDriver->getRelatedManifestations(); foreach ($relatedManifestations as $relatedManifestation) { diff --git a/code/web/services/API/SystemAPI.php b/code/web/services/API/SystemAPI.php index ef94fc4f9e..c7222aee4c 100644 --- a/code/web/services/API/SystemAPI.php +++ b/code/web/services/API/SystemAPI.php @@ -1189,41 +1189,6 @@ function getLibraryLinks() { } } - /** - * @return array - * @noinspection PhpUnused - */ - private function loadUsernameAndPassword() { - $username = $_REQUEST['username'] ?? ''; - $password = $_REQUEST['password'] ?? ''; - - if (isset($_POST['username']) && isset($_POST['password'])) { - $username = $_POST['username']; - $password = $_POST['password']; - } - - if (is_array($username)) { - $username = reset($username); - } - if (is_array($password)) { - $password = reset($password); - } - return [$username, $password]; - } - - /** - * @return bool|User - */ - protected function getUserForApiCall() { - $user = false; - [$username, $password] = $this->loadUsernameAndPassword(); - $user = UserAccount::validateAccount($username, $password); - if ($user !== false && $user->source == 'admin') { - return false; - } - return $user; - } - function getBreadcrumbs(): array { return []; } diff --git a/code/web/services/API/UserAPI.php b/code/web/services/API/UserAPI.php index c5a7bb6465..6a0a4d61d8 100644 --- a/code/web/services/API/UserAPI.php +++ b/code/web/services/API/UserAPI.php @@ -3963,32 +3963,6 @@ function getPaymentDetails($paymentId = null) { return $result; } - /** - * @return array - * @noinspection PhpUnused - */ - private function loadUsernameAndPassword(): array { - $username = $_REQUEST['username'] ?? ''; - $password = $_REQUEST['password'] ?? ''; - - // check for post request data - if (isset($_POST['username']) && isset($_POST['password'])) { - $username = $_POST['username']; - $password = $_POST['password']; - } - - if (is_array($username)) { - $username = reset($username); - } - if (is_array($password)) { - $password = reset($password); - } - return [ - $username, - $password, - ]; - } - /** @noinspection PhpUnused */ function getBarcodeForPatron(): array { $results = [ @@ -4651,7 +4625,7 @@ function getUserByBarcode(): array { /** * @return bool|User */ - protected function getUserForApiCall() { + function getUserForApiCall() { if ($this->context == 'internal') { return UserAccount::getActiveUserObj(); } else { @@ -4710,27 +4684,6 @@ function getLiDAVersion() { return 0; } - function getLiDASession() { - foreach (getallheaders() as $name => $value) { - if ($name == 'LiDA-SessionID' || $name == 'lida-sessionid') { - $sessionId = explode(' ', $value); - return $sessionId[0]; - } - } - return false; - } - - function getLiDAUserAgent() { - foreach (getallheaders() as $name => $value) { - if ($name == 'User-Agent' || $name == 'user-agent') { - if(str_contains($value, 'Aspen LiDA') || str_contains($value, 'aspen lida')) { - return true; - } - } - } - return false; - } - function getLinkedAccounts() { $user = $this->getUserForApiCall(); diff --git a/code/web/services/API/WorkAPI.php b/code/web/services/API/WorkAPI.php index 23aede4bf4..528147c09e 100644 --- a/code/web/services/API/WorkAPI.php +++ b/code/web/services/API/WorkAPI.php @@ -99,6 +99,8 @@ function getGroupedWork(): array { if ($itemData['description'] == '') { $itemData['description'] = 'Description Not Provided'; } + $itemData['lastCheckOut'] = null; + $itemData['appearsOnLists'] = []; $language = $groupedWorkDriver->getLanguage(); $itemData['language'] = $language[0] ?? $language; $itemData['cover'] = $configArray['Site']['url'] . '/bookcover.php?id=' . $this->id . '&size=large&type=grouped_work&category=' . rawurlencode($groupedWorkDriver->getFormatCategory()); @@ -137,6 +139,51 @@ function getGroupedWork(): array { }*/ + $user = $this->getUserForApiCall(); + if ($user && !($user instanceof AspenError)) { + require_once ROOT_DIR . '/sys/ReadingHistoryEntry.php'; + $readingHistoryEntry = new ReadingHistoryEntry(); + $readingHistoryEntry->userId = $user->id; + $readingHistoryEntry->deleted = 0; + $readingHistoryEntry->groupedWorkPermanentId = $groupedWorkDriver->getPermanentId(); + $readingHistoryEntry->groupBy('groupedWorkPermanentId'); + $readingHistoryEntry->selectAdd(); + $readingHistoryEntry->selectAdd('MAX(checkOutDate) as checkOutDate'); + if ($readingHistoryEntry->find(true)) { + $itemData['lastCheckOut'] = $readingHistoryEntry->checkOutDate; + } + + $userLists = []; + require_once ROOT_DIR . '/sys/UserLists/UserList.php'; + require_once ROOT_DIR . '/sys/UserLists/UserListEntry.php'; + $userListEntry = new UserListEntry(); + $userListEntry->source = 'GroupedWork'; + $userListEntry->sourceId = $groupedWorkDriver->getPermanentId(); + $userListEntry->find(); + while ($userListEntry->fetch()) { + $userList = new UserList(); + $userList->id = $userListEntry->listId; + if ($userList->find(true)) { + $okToShow = false; + $key = $userList->id; + if (!$userList->deleted) { + if($user->id == $userList->user_id || ($userList->public == 1 && $userList->searchable == 1)) { + $okToShow = true; + } + } + + if ($okToShow) { + $userLists[$key] = [ + 'id' => $userList->id, + 'title' => $userList->title, + ]; + } + } + } + ksort($userLists); + $itemData['appearsOnLists'] = $userLists; + } + return $itemData; } return [ @@ -244,7 +291,7 @@ public function getIsbnsForWork($permanentId = null) { return $record['isbn']; } } - + function getBreadcrumbs(): array { return []; } diff --git a/code/web/sys/AspenLiDA/SelfCheckSetting.php b/code/web/sys/AspenLiDA/SelfCheckSetting.php index 47b5d6ad87..45ac9ff847 100644 --- a/code/web/sys/AspenLiDA/SelfCheckSetting.php +++ b/code/web/sys/AspenLiDA/SelfCheckSetting.php @@ -7,6 +7,7 @@ class AspenLiDASelfCheckSetting extends DataObject { public $id; public $name; public $isEnabled; + public $checkoutLocation; private $_locations; private $_barcodeStyles; @@ -26,6 +27,12 @@ static function getObjectStructure($context = ''): array { $allBarcodeStyles = AspenLiDASelfCheckBarcode::getObjectStructure($context); + $checkout_location_options = [ + '0' => 'Current Location User is Logged Into', + '1' => 'User Home Location', + '2' => 'Item Location (Koha 23.11+ Only)' + ]; + $structure = [ 'id' => [ 'property' => 'id', @@ -48,6 +55,14 @@ static function getObjectStructure($context = ''): array { 'description' => 'Whether or not patrons can self-check using Aspen LiDA', 'required' => false, ], + 'checkoutLocation' => [ + 'property' => 'checkoutLocation', + 'type' => 'enum', + 'values' => $checkout_location_options, + 'label' => 'Assign Checkouts To', + 'description' => 'Location where a checkout should be assigned', + 'required' => false, + ], 'barcodeStyles' => [ 'property' => 'barcodeStyles', 'type' => 'oneToMany', @@ -187,6 +202,20 @@ public function saveBarcodeStyles() { } } + public function getCheckoutLocationSetting($locationId) { + $location = new Location(); + $location->code = $locationId; + if ($location->find(true)) { + $scoSettings = new AspenLiDASelfCheckSetting(); + $scoSettings->id = $location->lidaSelfCheckSettingId; + if ($scoSettings->find(true)) { + return $scoSettings->checkoutLocation; + } + } + + return false; + } + function getEditLink($context): string { return '/AspenLiDA/SelfCheckSettings?objectAction=edit&id=' . $this->id; } diff --git a/code/web/sys/DBMaintenance/version_updates/24.04.00.php b/code/web/sys/DBMaintenance/version_updates/24.04.00.php index db7b89a689..6030545669 100644 --- a/code/web/sys/DBMaintenance/version_updates/24.04.00.php +++ b/code/web/sys/DBMaintenance/version_updates/24.04.00.php @@ -62,9 +62,25 @@ function getUpdates24_04_00(): array { ], //axis360_restrict_scopes_by_audience //kirstien - ByWater + 'self_check_checkout_location' => [ + 'title' => 'Add self-check option to set checkout location', + 'description' => 'Add self-check option to set checkout location', + 'continueOnError' => false, + 'sql' => [ + 'ALTER TABLE aspen_lida_self_check_settings ADD COLUMN checkoutLocation TINYINT(1) DEFAULT 0', + ], + ], + //self_check_checkout_location //kodi - ByWater - + 'institution_code' => [ + 'title' => 'Institution Code', + 'description' => 'Add institution code for CarlX self registration to library table', + 'continueOnError' => false, + 'sql' => [ + "ALTER TABLE library ADD COLUMN institutionCode varchar(100) default ''", + ], + ], //lucas - Theke //alexander - PTFS Europe diff --git a/code/web/sys/Email/EmailTemplate.php b/code/web/sys/Email/EmailTemplate.php index a8d8c7b32a..aa55d5cea1 100644 --- a/code/web/sys/Email/EmailTemplate.php +++ b/code/web/sys/Email/EmailTemplate.php @@ -14,10 +14,26 @@ class EmailTemplate extends DataObject { private $_libraries; static function getObjectStructure($context = ''): array { + $isCarlX = false; $libraryList = Library::getLibraryList(!UserAccount::userHasPermission('Administer All Email Templates')); - $availableTemplates = [ - 'welcome' => 'Welcome' - ]; + foreach (UserAccount::getAccountProfiles() as $accountProfileInfo) { + /** @var AccountProfile $accountProfile */ + $accountProfile = $accountProfileInfo['accountProfile']; + if ($accountProfile->ils == 'carlx') { + $isCarlX = true; + } + } + if ($isCarlX){ + $availableTemplates = [ + 'welcome' => 'Welcome', + 'duplicateNameDOB' => 'Duplicate Name and Birthdate', + 'duplicateEmail' => 'Duplicate Email' + ]; + } else { + $availableTemplates = [ + 'welcome' => 'Welcome' + ]; + } require_once ROOT_DIR . '/sys/Translation/Language.php'; $validLanguage = new Language(); $validLanguage->orderBy("weight"); diff --git a/code/web/sys/LibraryLocation/Library.php b/code/web/sys/LibraryLocation/Library.php index a7d4fcced4..80306f4831 100644 --- a/code/web/sys/LibraryLocation/Library.php +++ b/code/web/sys/LibraryLocation/Library.php @@ -2384,6 +2384,14 @@ static function getObjectStructure($context = ''): array { 'hideInLists' => true, 'default' => 'default', ], + 'institutionCode' => [ + 'property' => 'institutionCode', + 'type' => 'text', + 'label' => 'Institution Code', + 'description' => 'The institution code for self registration (CarlX Only).', + 'hideInLists' => true, + 'default' => '', + ], ], ], 'thirdPartyRegistrationSection' => [