Skip to content

Commit 382f503

Browse files
Merge branch '2026.1' into 2026.x
2 parents a1bb4d2 + 4228043 commit 382f503

789 files changed

Lines changed: 2327 additions & 30143 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

assets/build/api/docs.jsonopenapi.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

assets/js/src/core/modules/app/error-handler/constants/errorTypes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export enum ErrorTypes {
1818
export enum ErrorKeyTypes {
1919
GENERIC_ERROR = 'error_something_generic_went_wrong',
2020
ELEMENT_EXISTS = 'error_element_exists',
21+
DOCUMENT_LANGUAGE_NOT_SET = 'error_document_language_not_set',
2122
FOLDER_EXISTS = 'error_folder_exists',
2223
INVALID_ARGUMENT = 'error_invalid_argument',
2324
WIDGET_NAME_MISSING = 'error_widget_name_missing',

assets/js/src/core/modules/asset/asset-api-slice.gen.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -537,13 +537,13 @@ export type AssetDocumentStreamCustomApiArg = {
537537
dpi?: number;
538538
cropPercent?: boolean;
539539
/** CropWidth of image thumbnail */
540-
cropWidth?: any;
540+
cropWidth?: number;
541541
/** CropHeight of image thumbnail */
542-
cropHeight?: any;
542+
cropHeight?: number;
543543
/** CropTop of image thumbnail */
544-
cropTop?: any;
544+
cropTop?: number;
545545
/** CropLeft of image thumbnail */
546-
cropLeft?: any;
546+
cropLeft?: number;
547547
};
548548
export type AssetDocumentStreamDynamicApiResponse =
549549
/** status 200 Document image stream based on dynamic thumbnail configuration */ Blob;
@@ -577,13 +577,13 @@ export type AssetDocumentStreamByThumbnailApiArg = {
577577
thumbnailName: string;
578578
cropPercent?: boolean;
579579
/** CropWidth of image thumbnail */
580-
cropWidth?: any;
580+
cropWidth?: number;
581581
/** CropHeight of image thumbnail */
582-
cropHeight?: any;
582+
cropHeight?: number;
583583
/** CropTop of image thumbnail */
584-
cropTop?: any;
584+
cropTop?: number;
585585
/** CropLeft of image thumbnail */
586-
cropLeft?: any;
586+
cropLeft?: number;
587587
};
588588
export type AssetDownloadZipApiResponse = /** status 200 ZIP archive as attachment */ Blob;
589589
export type AssetDownloadZipApiArg = {
@@ -794,13 +794,13 @@ export type AssetImageStreamCustomApiArg = {
794794
forceResize?: boolean;
795795
cropPercent?: boolean;
796796
/** CropWidth of image thumbnail */
797-
cropWidth?: any;
797+
cropWidth?: number;
798798
/** CropHeight of image thumbnail */
799-
cropHeight?: any;
799+
cropHeight?: number;
800800
/** CropTop of image thumbnail */
801-
cropTop?: any;
801+
cropTop?: number;
802802
/** CropLeft of image thumbnail */
803-
cropLeft?: any;
803+
cropLeft?: number;
804804
};
805805
export type AssetImageStreamDynamicApiResponse =
806806
/** status 200 Image asset stream based on dynamic thumbnail configuration */ Blob;
@@ -844,13 +844,13 @@ export type AssetImageStreamByThumbnailApiArg = {
844844
thumbnailName: string;
845845
cropPercent?: boolean;
846846
/** CropWidth of image thumbnail */
847-
cropWidth?: any;
847+
cropWidth?: number;
848848
/** CropHeight of image thumbnail */
849-
cropHeight?: any;
849+
cropHeight?: number;
850850
/** CropTop of image thumbnail */
851-
cropTop?: any;
851+
cropTop?: number;
852852
/** CropLeft of image thumbnail */
853-
cropLeft?: any;
853+
cropLeft?: number;
854854
/** Mime type of steamed image. */
855855
mimeType?: "JPEG" | "PNG" | "source" | "original" | "print";
856856
};

assets/js/src/core/modules/asset/listing/decorator/tag-filter/view-layer/components/sidebar/hooks/with-tab-tag-filter-entry.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { type AbstractDecoratorProps } from '@Pimcore/modules/element/listing/de
1414
import { Icon } from '@Pimcore/components/icon/icon'
1515
import { TagFiltersContainer } from '../tabs/tag-filters/tag-filters-container'
1616
import { useTagFilter } from '../../../../context-layer/provider/tag-filter/use-tag-filter'
17+
import { isAllowed } from '@Pimcore/modules/auth/permission-helper'
1718

1819
export const withTabTagFilterEntry = (useBaseHook: AbstractDecoratorProps['useSidebarOptions']): AbstractDecoratorProps['useSidebarOptions'] => {
1920
const useTabTagFilterEntry: typeof useBaseHook = () => {
@@ -23,6 +24,11 @@ export const withTabTagFilterEntry = (useBaseHook: AbstractDecoratorProps['useSi
2324

2425
const getProps: typeof baseGetProps = () => {
2526
const baseProps = baseGetProps()
27+
28+
if (!isAllowed('tags_search')) {
29+
return baseProps
30+
}
31+
2632
let sidebarHighlights: typeof baseProps['highlights'] = baseProps.highlights ?? []
2733

2834
if (tags.length > 0) {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* This source file is available under the terms of the
3+
* Pimcore Open Core License (POCL)
4+
* Full copyright and license information is available in
5+
* LICENSE.md which is distributed with this source code.
6+
*
7+
* @copyright Copyright (c) Pimcore GmbH (https://www.pimcore.com)
8+
* @license Pimcore Open Core License (POCL)
9+
*/
10+
11+
import { useUser } from '@Pimcore/modules/auth/hooks/use-user'
12+
import { useSettings } from '@Pimcore/modules/app/settings/hooks/use-settings'
13+
14+
/** Returns the user's profile language if it is a valid system content language, or null otherwise. */
15+
export const useUserContentLanguage = (): string | null => {
16+
const user = useUser()
17+
const settings = useSettings()
18+
19+
const validLanguages: string[] = settings.validLanguages ?? []
20+
21+
return validLanguages.includes(user.language) ? user.language : null
22+
}

assets/js/src/core/modules/classification-store-config/components/store-editor/tabs/collections-tab.tsx

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ import { IconButton } from '@Pimcore/components/icon-button/icon-button'
2020
import { IconTextButton } from '@Pimcore/components/icon-text-button/icon-text-button'
2121
import { Grid } from '@Pimcore/components/grid/grid'
2222
import { Input } from '@Pimcore/components/input/input'
23+
import { SearchInput } from '@Pimcore/components/search-input/search-input'
2324
import { createColumnHelper } from '@tanstack/react-table'
24-
import { type RowSelectionState } from '@tanstack/react-table'
25+
import { type RowSelectionState, type SortingState } from '@tanstack/react-table'
2526
import { SplitLayout } from '@Pimcore/components/split-layout/split-layout'
2627
import { ContentLayout } from '@Pimcore/components/content-layout/content-layout'
2728
import { Header } from '@Pimcore/components/header/header'
@@ -55,17 +56,47 @@ export const CollectionsTab = ({ storeId }: ICollectionsTabProps): React.JSX.Ele
5556
const [detailForm] = Form.useForm<ICollectionDetailFormValues>()
5657

5758
const [selectedRows, setSelectedRows] = useState<RowSelectionState>({})
58-
const [page, setPage] = useState(1)
59-
const [pageSize, setPageSize] = useState(20)
59+
const [page, setPage] = useState<number>(1)
60+
const [pageSize, setPageSize] = useState<number>(20)
61+
const [searchTerm, setSearchTerm] = useState<string>('')
62+
const [sorting, setSorting] = useState<SortingState>([])
6063

61-
const { data, isLoading, isFetching, refetch } = useClassificationStoreConfigurationCollectionCollectionQuery({
64+
const onSortingChange = useCallback((newSorting: SortingState) => {
65+
setSorting(newSorting)
66+
setPage(1)
67+
}, [])
68+
69+
const queryArgs = useMemo(() => ({
6270
storeId,
63-
body: { filters: { page, pageSize } }
64-
})
71+
body: {
72+
filters: {
73+
page,
74+
pageSize,
75+
columnFilters: searchTerm.length > 0
76+
? [{ type: 'search', filterValue: searchTerm }]
77+
: [],
78+
...(sorting.length > 0
79+
? {
80+
sortFilter: {
81+
key: sorting[0].id,
82+
direction: sorting[0].desc ? 'DESC' : 'ASC'
83+
}
84+
}
85+
: {})
86+
}
87+
}
88+
}), [storeId, page, pageSize, searchTerm, sorting])
89+
90+
const { data, isLoading, isFetching, refetch } = useClassificationStoreConfigurationCollectionCollectionQuery(queryArgs)
6591

6692
const collections = data?.items ?? []
6793
const total = data?.totalItems ?? 0
6894

95+
useEffect(() => {
96+
if (data?.items.length === 0 && data.totalItems > 0 && page > 1) {
97+
setPage(page - 1)
98+
}
99+
}, [data, page])
69100
const [createCollection] = useClassificationStoreConfigurationCollectionCreateMutation()
70101
const [updateCollection] = useClassificationStoreConfigurationCollectionUpdateMutation()
71102
const [deleteCollection] = useClassificationStoreConfigurationCollectionDeleteMutation()
@@ -256,13 +287,26 @@ export const CollectionsTab = ({ storeId }: ICollectionsTabProps): React.JSX.Ele
256287
justify="space-between"
257288
style={ { padding: '8px 16px' } }
258289
>
259-
<Header title={ t('classification-store.tabs.collections') } />
260-
<IconTextButton
261-
icon={ { value: 'new' } }
262-
onClick={ handleAdd }
290+
<Flex
291+
align="center"
292+
gap="small"
263293
>
264-
{t('classification-store.add-collection')}
265-
</IconTextButton>
294+
<Header title={ t('classification-store.tabs.collections') } />
295+
<IconTextButton
296+
icon={ { value: 'new' } }
297+
onClick={ handleAdd }
298+
>
299+
{t('classification-store.add-collection')}
300+
</IconTextButton>
301+
</Flex>
302+
<SearchInput
303+
loading={ isFetching }
304+
onSearch={ (value) => {
305+
setSearchTerm(value)
306+
setPage(1)
307+
} }
308+
placeholder={ t('search') }
309+
/>
266310
</Flex>
267311
}
268312
>
@@ -274,8 +318,11 @@ export const CollectionsTab = ({ storeId }: ICollectionsTabProps): React.JSX.Ele
274318
columns={ columns }
275319
data={ collections }
276320
enableRowSelection
321+
enableSorting
277322
isLoading={ isLoading || isFetching }
323+
manualSorting
278324
onSelectedRowsChange={ (rows) => { setSelectedRows(rows) } }
325+
onSortingChange={ onSortingChange }
279326
onUpdateCellData={ ({ columnId, value, rowData }) => {
280327
if (columnId === 'name') {
281328
const newName = (value as string).trim()
@@ -295,6 +342,7 @@ export const CollectionsTab = ({ storeId }: ICollectionsTabProps): React.JSX.Ele
295342
} }
296343
selectedRows={ selectedRows }
297344
setRowId={ (row: ClassificationStoreConfigurationCollectionDetail) => row.id !== undefined ? String(row.id) : undefined as unknown as string }
345+
sorting={ sorting }
298346
/>
299347
</Flex>
300348
</ContentLayout>

assets/js/src/core/modules/classification-store-config/components/store-editor/tabs/groups-tab.tsx

Lines changed: 61 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ import { IconButton } from '@Pimcore/components/icon-button/icon-button'
2020
import { IconTextButton } from '@Pimcore/components/icon-text-button/icon-text-button'
2121
import { Grid } from '@Pimcore/components/grid/grid'
2222
import { Input } from '@Pimcore/components/input/input'
23+
import { SearchInput } from '@Pimcore/components/search-input/search-input'
2324
import { createColumnHelper } from '@tanstack/react-table'
24-
import { type RowSelectionState } from '@tanstack/react-table'
25+
import { type RowSelectionState, type SortingState } from '@tanstack/react-table'
2526
import { SplitLayout } from '@Pimcore/components/split-layout/split-layout'
2627
import { ContentLayout } from '@Pimcore/components/content-layout/content-layout'
2728
import { Header } from '@Pimcore/components/header/header'
@@ -55,17 +56,48 @@ export const GroupsTab = ({ storeId }: IGroupsTabProps): React.JSX.Element => {
5556
const [detailForm] = Form.useForm<IGroupDetailFormValues>()
5657

5758
const [selectedRows, setSelectedRows] = useState<RowSelectionState>({})
58-
const [page, setPage] = useState(1)
59-
const [pageSize, setPageSize] = useState(20)
59+
const [page, setPage] = useState<number>(1)
60+
const [pageSize, setPageSize] = useState<number>(20)
61+
const [searchTerm, setSearchTerm] = useState<string>('')
62+
const [sorting, setSorting] = useState<SortingState>([])
6063

61-
const { data, isLoading, isFetching, refetch } = useClassificationStoreConfigurationGroupCollectionQuery({
64+
const onSortingChange = useCallback((newSorting: SortingState) => {
65+
setSorting(newSorting)
66+
setPage(1)
67+
}, [])
68+
69+
const queryArgs = useMemo(() => ({
6270
storeId,
63-
body: { filters: { page, pageSize } }
64-
})
71+
body: {
72+
filters: {
73+
page,
74+
pageSize,
75+
columnFilters: searchTerm.length > 0
76+
? [{ type: 'search', filterValue: searchTerm }]
77+
: [],
78+
...(sorting.length > 0
79+
? {
80+
sortFilter: {
81+
key: sorting[0].id,
82+
direction: sorting[0].desc ? 'DESC' : 'ASC'
83+
}
84+
}
85+
: {})
86+
}
87+
}
88+
}), [storeId, page, pageSize, searchTerm, sorting])
89+
90+
const { data, isLoading, isFetching, refetch } = useClassificationStoreConfigurationGroupCollectionQuery(queryArgs)
6591

6692
const groups = data?.items ?? []
6793
const total = data?.totalItems ?? 0
6894

95+
useEffect(() => {
96+
if (data?.items.length === 0 && data.totalItems > 0 && page > 1) {
97+
setPage(page - 1)
98+
}
99+
}, [data, page])
100+
69101
const [createGroup] = useClassificationStoreConfigurationGroupCreateMutation()
70102
const [updateGroup] = useClassificationStoreConfigurationGroupUpdateMutation()
71103
const [deleteGroup] = useClassificationStoreConfigurationGroupDeleteMutation()
@@ -262,13 +294,26 @@ export const GroupsTab = ({ storeId }: IGroupsTabProps): React.JSX.Element => {
262294
justify="space-between"
263295
style={ { padding: '8px 16px' } }
264296
>
265-
<Header title={ t('classification-store.tabs.groups') } />
266-
<IconTextButton
267-
icon={ { value: 'new' } }
268-
onClick={ handleAdd }
297+
<Flex
298+
align="center"
299+
gap="small"
269300
>
270-
{t('classification-store.add-group')}
271-
</IconTextButton>
301+
<Header title={ t('classification-store.tabs.groups') } />
302+
<IconTextButton
303+
icon={ { value: 'new' } }
304+
onClick={ handleAdd }
305+
>
306+
{t('classification-store.add-group')}
307+
</IconTextButton>
308+
</Flex>
309+
<SearchInput
310+
loading={ isFetching }
311+
onSearch={ (value) => {
312+
setSearchTerm(value)
313+
setPage(1)
314+
} }
315+
placeholder={ t('search') }
316+
/>
272317
</Flex>
273318
}
274319
>
@@ -280,8 +325,11 @@ export const GroupsTab = ({ storeId }: IGroupsTabProps): React.JSX.Element => {
280325
columns={ columns }
281326
data={ groups }
282327
enableRowSelection
328+
enableSorting
283329
isLoading={ isLoading || isFetching }
330+
manualSorting
284331
onSelectedRowsChange={ (rows) => { setSelectedRows(rows) } }
332+
onSortingChange={ onSortingChange }
285333
onUpdateCellData={ ({ columnId, value, rowData }) => {
286334
if (columnId === 'name') {
287335
const newName = (value as string).trim()
@@ -301,6 +349,7 @@ export const GroupsTab = ({ storeId }: IGroupsTabProps): React.JSX.Element => {
301349
} }
302350
selectedRows={ selectedRows }
303351
setRowId={ (row: ClassificationStoreConfigurationGroupDetail) => row.id !== undefined ? String(row.id) : undefined as unknown as string }
352+
sorting={ sorting }
304353
/>
305354
</Flex>
306355
</ContentLayout>

0 commit comments

Comments
 (0)