Compare commits

...
Author SHA1 Message Date
Lan Nguyen Thuy 2c497fd19f npm update 2026-04-14 13:35:49 +07:00
Lan Nguyen Thuy ae3add6f7b update storybook 2026-04-14 12:54:48 +07:00
Lan Nguyen Thuy 7cf691888b update prettier, global, eslint 2026-04-14 12:47:20 +07:00
Laura NetoandGitHub 4638406fc9 Tests: Fix unit test build (#22453)
Fix ElementPickerValueConverterTests build by passing IPropertyRenderingContextAccessor

The PublishedProperty constructor was updated to take an
IPropertyRenderingContextAccessor as its 4th argument, but
ElementPickerValueConverterTests was not updated, breaking the
Umbraco.Tests.UnitTests build.
2026-04-13 18:54:50 +02:00
Laura Neto 704ee94101 Merge branch 'main' into v18/dev 2026-04-13 17:40:29 +02:00
6c96ad1f93 Elements: Cleanup element TODOs in infrastructure (#22443)
* Remove obsolete TODO (already fixed to the extend possible)

* Remove irrelevant TODO

* Refactor publishable entity building from DTOs

* Fix TODO for presentation factory

* Move shared view models from Document to Content

* Clarify TODO after testing refactoring feasibility

* Update src/Umbraco.Cms.Api.Management/ViewModels/Content/ScheduleRequestModel.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Update src/Umbraco.Cms.Api.Management/Factories/IElementPresentationFactory.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-04-13 08:09:13 +02:00
Kenn JacobsenandGitHub 8cebbd23d8 Elements: Cleanup element TODOs in core (#22399)
* Cleanup element TODOs in core (first take)

* Cleanup more element TODOs in PublishableContentServiceBase and ElementEditingService

* Implement Delivery API for ElementPickerValueConverter (removes TODOs and add a few new ones)

* Move the generic implementation of PublishedElementWrapped to its own class file

* Review comments for IPublishableContentRepository
2026-04-13 08:08:39 +02:00
4a4437b500 Rendering: Use explicit dependency instead of access-via-casting (#22442)
* Use explicit dependency instead of access-via-casting

* Update src/Umbraco.PublishedCache.HybridCache/PublishedElement.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-04-13 07:21:08 +02:00
Andy Butland bd83df28bc Merge branch 'main' into v18/dev 2026-04-11 11:37:15 +02:00
010ceab910 Elements: Align ElementPermissionService for performance improvements (#22405)
* Align ElementPermissionService with ContentPermissionService performance improvements

* Don't fetch entities we don't need.

* Update src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Add unit tests for ElementPermissionService

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-04-10 12:14:25 +00:00
Andy Butland c6f1cfdd4a Fixed test helpers build. 2026-04-09 21:12:25 +02:00
Andy Butland e09cf2aaf8 Fix build of integration tests and failing unit test. 2026-04-09 19:52:12 +02:00
Andy Butland 85680bd36d Merge branch 'v18/dev' of https://github.com/umbraco/Umbraco-CMS into v18/dev 2026-04-09 16:01:22 +02:00
Andy Butland 36f3bf6b9c Merge branch 'main' into v18/dev 2026-04-09 16:00:10 +02:00
8df1c3f152 Deprecations: Client-side removal of v18 deprecated code (#21984)
* chore(tree): remove deprecated tree store infrastructure

Remove the entire tree store pattern that was deprecated in favor of
direct tree repository queries. This deletes 29 tree store files,
removes the ManifestTreeStore extension type, updates all 15+ tree
repository constructors to remove store context token parameters,
cleans up manifests/constants/index exports, and migrates all
skip/take pagination to the paging property pattern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore(workspace): remove deprecated methods and properties

Remove deprecated methods/properties across workspace contexts, menu
structures, tree items, and collections:
- Tree item context: getManifest(), loadMore()
- Content workspace: loadSegments()
- Entity detail workspace: parentUnique/parentEntityType observables,
  getParent/setParent/getParentUnique/getParentEntityType methods,
  _scaffoldProcessData (replaced by _processIncomingData)
- Menu structure contexts: #parent state, provideContext('UmbMenuStructureWorkspaceContext')
- Document/media/blueprint/member workspaces: contentTypeHasCollection,
  getCollectionAlias(), getContentTypeId() (replaced by getContentTypeUnique())
- Collection context: setManifest(), getManifest() from interface and implementation
- Bulk delete action: deprecated _items getter/setter

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore(core): remove deprecated type aliases and exports

Remove deprecated type aliases scheduled for v18 removal:
- PackageManifestResponse (use UmbPackageManifestResponse)
- UmbSectionDefaultElement (use UmbDefaultSectionElement)
- ConditionsCollectionView (use UmbConditionsCollectionView)
- MediaValueType (use UmbMediaValueType)
- UrlParametersRecord (use UmbUrlParametersRecord)
- ActiveVariant (use UmbActiveVariant)
- UmbPropertyValueChangeEvent class and deprecated property-value-change
  event listeners

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore(ui): remove deprecated config and UI exports

- Textarea: remove deprecated minHeight/maxHeight config reads
- Image cropper modal: remove deprecated default export
- UFM filters: remove 3 deprecated camelCase filter manifests
  (StripHtmlCamelCase, TitleCaseCamelCase, WordLimitCamelCase)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore(repository): make totalAfter/totalBefore mandatory in UmbTargetPagedModel

Make totalAfter and totalBefore required properties (were optional),
fulfilling the TODO to make these mandatory in v18. All downstream
tree data sources already provide these values.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style: fix lint formatting

Auto-fixed formatting from lint run (line wrapping, trailing newlines).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(collection): default filter parameter in element collection repositories

The UmbCollectionRepository interface defines filter as optional.
Without a default, calling requestCollection() without arguments
would throw when accessing filter.skip/filter.take.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(backoffice): resolve ESLint errors, fix pagination metadata, and remove missed deprecations

- Remove unused UmbObjectState import (ESLint error from merge)
- Remove unused offsetPaging variable in tree-item-children.manager.ts
- Fix totalBefore/totalAfter in all tree data sources to account for skip
  offset (was always reporting totalBefore: 0 regardless of skip value)
- Remove deprecated entityType property from UmbElementValueModel
  (marked for v18 removal)
- Remove deprecated _items getter/setter from UmbTrashEntityBulkAction
  (marked for v18 removal)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(backoffice): remove entityType references from tests and source after type removal

Remove entityType property from test fixtures and media-dropzone.manager.ts
following removal of the deprecated entityType from UmbElementValueModel.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>
2026-04-09 13:53:48 +00:00
ee44dbd677 Global Elements: Create options with allowed types and entityCreateOptionAction extensions (#22265)
* entity-action manifests shuffle

* feat(elements): show allowed element types in create action modal

Replace the generic document type picker with a custom create options
modal that fetches allowed element types from the library API and
displays them alongside a folder creation option, following the
established Media create pattern.

* feat(elements): add collection create action with allowed types and folder option

Add custom collection action element that fetches allowed element types
and discovers entityCreateOptionAction extensions (e.g. folder creation),
rendering them as a button or dropdown in the collection toolbar.

* refactor(elements): use dynamic entityCreateOptionAction extensions in create modals

Replace hardcoded folder option in the element create options modal with
UmbExtensionsApiInitializer to dynamically discover entityCreateOptionAction
extensions, enabling 3rd party extensibility.

* style(elements): clean up redundant state, magic strings, and empty styles

Use UMB_ELEMENT_ROOT_ENTITY_TYPE constant instead of magic string,
remove unused _headline state and empty css template, inline
single-use getter.

* fix(elements): address PR review feedback and export missing constants

- Extend UmbNamedEntityModel instead of duplicating name field
- Add getHref() support and error handling matching core patterns
- Add max-height on scroll container, icon fallbacks, element-specific
  localization key
- Export UMB_ELEMENT_CREATE_OPTIONS_MODAL and
  UMB_ELEMENT_TYPE_STRUCTURE_REPOSITORY_ALIAS through index chain
- Add feature parity checklist to clean-code docs

* style(elements): add noElementTypes localization entry and lint tweaks

* fix(elements): handle href navigation and error handling in create options modal

Navigate via history.pushState when href is present on create option
actions. Only close modal on successful execute, keeping it open on
failure so users can retry.

* Add temporary .skip tag to element smoke tests due to UI changes - to be fixed in another PR

---------

Co-authored-by: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com>
Co-authored-by: Nhu Dinh <hnd@umbraco.dk>
2026-04-09 12:40:06 +01:00
MoleandGitHub 05112b559f Management API: Fix ambiguous constructor in PasswordConfigurationPresentationFactory (#22391)
* Fix ambiguous constructor

* Add clarifying comment
2026-04-09 10:09:33 +00:00
0fa669db94 Code Clean-up (18): Remove obsoleted code flagged for removal (Part 3) (#22335)
* remove obsolete code from services

* remove obsolete code from IEmailSenderClient

* remove obsolete code from Notifications

* unchange MemberServiceTest

* Remove obsolete code from CopyingNotification

* remove ContentFinderByUrl and ContentFinderByUrlAndTemplate

* Remove DefaultUrlProvider, remove obsolete code from ContentPermissions, update MemberRoleStoreTests

* unchange PropertyCacheLevelTests

* unchange ConvertersTests

* Update src/Umbraco.Core/Notifications/ContentCopiedNotification.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Update src/Umbraco.Core/Notifications/ContentCopyingNotification.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Update src/Umbraco.Core/Notifications/CopiedNotification.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Update src/Umbraco.Core/Notifications/CopyingNotification.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Update src/Umbraco.Core/Notifications/ElementCopiedNotification.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Update src/Umbraco.Core/Notifications/ElementCopyingNotification.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Update src/Umbraco.Core/Services/ContentService.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Update src/Umbraco.Infrastructure/Mail/BasicSmtpEmailSenderClient.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* update tests, rename, remove file tests...

* Minor formatting tidy-up.

---------

Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-04-09 06:57:00 +00:00
e5d44cd9c3 Code Clean-up (18): Resolve V18 TODO comments (#22357)
* todo cleanup

* adding activatorUtilitiesConstructor atribute

* fix failed test by adding ActivatorUtilitiesConstructor

* Apply suggestion from @AndyButland

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Apply suggestion from @AndyButland

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Apply suggestion from @AndyButland

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Apply suggestion from @AndyButland

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Apply suggestion from @AndyButland

Co-authored-by: Andy Butland <abutland73@gmail.com>

* update umbracoPlan and remove ConfigureSecurityStampOptions

* Removed uneeded using.

---------

Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-04-09 06:34:42 +00:00
5d682f69a8 UUI: Updates to UI Library version 2.0.0-alpha.1 (#21994)
* build(deps): bumps @umbraco-ui to 2.0.0-alpha.1 with new themes

* fix: updates paths to new themes

* feat: uses new uui themes for static cshtml files

* feat: updates to use UUISelectOption and UUIFormControlWithBasicsMixin

* build: copy all themes to "themes" folder

* build(uui): updates themes path so it works relatively with fonts

* build: updates minimum node.js version to build from 22 to 24 to support UUI

* fix: corrects paths to theme css

* docs: update CLAUDE.md files to reflect UUI 2.x for CMS v18

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(storybook): adds theme switcher

* docs(storybook): updates paths

* docs(web): document UUI theme CSS pipeline across build files

Add comments linking the files involved in UUI theme CSS handling:
- manifests.ts: where theme CSS paths are declared, with note on UUI origin
- external/uui/vite.config.ts: where themes are copied for production builds
- vite.config.ts: where themes are copied for dev server and PR previews
- copy-to-cms.js: clarifies UUI themes are already in dist-cms at this point

Each file points to the others, making the dependency on UUI theme
filenames visible without adding abstraction.

https://claude.ai/code/session_015ntS4GXa4s9BQHsjvigDh2

* Update package.json

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix: adjusts types

* update lockfile

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
2026-04-08 10:36:23 +00:00
Nhu DinhandGitHub df3724c830 E2E: QA Remove @smoke tags from element tests (temporary) (#22363)
Temporary remove .smoke tags for the element-related tests
2026-04-08 09:29:06 +07:00
e093ca5f49 Global Elements: Workspace UI updates: split view, variant selector, save modal, and pending changes (#21897)
* feat(elements): add contentTypeIcon observable and _handleSave override to workspace context

Adds contentTypeIcon observable, icon field to UmbElementDetailModel, and maps icon from server response. Adds _handleSave override to remap validation error colors to warning colors during save, matching Document workspace behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(elements): add loading state, variant selector, and cleanup to split view

Adds loading state observation and binding, variant selector slot with new element-specific variant selector component, and element sortVariants utility. Removes dead #breadcrumbs CSS rule and reorders splitViewIndex to match Document workspace conventions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(elements): wire up publishing workspace context in variant selector

Consumes UMB_ELEMENT_PUBLISHING_WORKSPACE_CONTEXT in the element variant selector, mirroring the Document pattern. Fixes PUBLISHED_PENDING_CHANGES localization to use the correct key.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(elements): add save modal for element workspace variant picker

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Adds "Update" permission condition on Folder Rename entity-action

* feat(elements): add pending changes manager for element workspace

Mirror the Document workspace's UmbDocumentPublishedPendingChangesManager
to provide client-side comparison of persisted vs published element data.
The variant selector now uses this manager to determine pending changes
state instead of relying solely on the API state. The actual API call to
fetch published element data is left as a TODO until the backend endpoint
exists.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Update src/Umbraco.Web.UI.Client/src/packages/elements/modals/save-modal/element-save-modal.element.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/Umbraco.Web.UI.Client/src/packages/elements/utils.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* refactor(menu): delegate breadcrumb href to menu structure context

Move the href resolution logic from the breadcrumb element into the
menu structure workspace context via a new `getItemHref` method on the
interface and base class. This eliminates the need for duplicate
breadcrumb elements that only differ in href behavior, and mirrors the
existing pattern used by the variant breadcrumb.

* feat(elements): add menu structure context and breadcrumb for element folders

Add UmbElementFolderMenuStructureContext that overrides getItemHref to
make folder ancestors and the section root clickable in the breadcrumb.
Register the menu structure context and breadcrumb footer app in the
element folder workspace manifests.

* fix(elements): provide synthetic variant data for folder tree items

Folders don't have variants from the API, so provide a synthetic
published variant using the folder name. This prevents errors when
the tree item mapper expects variant data.

* Updates "umb-element-table-collection-view"

to add the column elements for "name" and (published) "state".

* Refactor exports in constants.ts for clarity

* fix(workspace): prevent breadcrumb TypeError for contexts without getItemHref

Menu structure contexts that don't extend the tree base class (e.g.
UmbLanguageNavigationStructureWorkspaceContext) lack getItemHref, causing
a runtime TypeError in the breadcrumb element. Use optional chaining to
gracefully handle missing implementations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs(menu): add JSDoc to UmbMenuStructureWorkspaceContext interface

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>
2026-04-07 11:17:41 +02:00
b972d1db5a Global Elements: Element Tree Item "Draft" state (#22228)
* Adds "umb-element-tree-item" custom component

Updates context to use the item data resolver..

* Adds manifests for Element entity-signs

for "Has Pending Changes" and "Has Scheduled Publish"

* Update src/Umbraco.Web.UI.Client/src/packages/elements/tree/element-tree-item.element.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Attempt to fix the Item Data Resolver `setData` type-casting

* Align element tree item model with item model for type safety

Add required `flags` field to `UmbElementTreeItemModel` (via
`UmbEntityWithFlags`) and `UmbElementTreeItemVariantModel`, matching
the document tree pattern. This ensures the data resolver's `#setFlags()`
receives actual data instead of silently accessing undefined properties.

The `as unknown as` cast in the context remains due to nominal type
differences (entityType union, variant state enum) but is now structurally
safe at runtime.

* Maps `flags` in `UmbElementTreeItemVariantModel`

* Updated locator for element tree item due to UI changes

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
Co-authored-by: Nhu Dinh <hnd@umbraco.dk>
2026-04-03 07:45:12 +00:00
Andy Butland 015d17db3f Merge branch 'main' into v18/dev 2026-04-02 08:19:35 +02:00
Andy Butland 617f2441fc Merge branch 'main' into v18/dev 2026-04-01 15:39:23 +02:00
Niels Lyngsø c18f28d22d Merge branch 'main' into v18/dev
# Conflicts:
#	src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteSyntaxProvider.cs
#	src/Umbraco.Core/Services/OperationStatus/UserOperationStatus.cs
2026-04-01 13:58:53 +02:00
Andy Butland f550eeec28 Fixed client-side build. 2026-04-01 12:36:20 +02:00
Andy Butland 22907e01ef Updated OpenApi.json. 2026-04-01 09:54:54 +02:00
59a7d99e3c Elements: Add PublishedCultures and UnpublishedCultures to ElementCacheRefresher (#22302)
* Add PublishedCultures and UnpublishedCultures to ElementCacheRefresher.JsonPayload

Adds culture-specific publishing details to the element cache refresher payload,
matching the existing ContentCacheRefresher.JsonPayload structure. Also replicates
the performance optimization from #21415 by only clearing partial view cache when
there are actual publish/unpublish culture changes, and fixes the Remove change
type check to use HasType instead of equality (flags enum).

* Reuse content cache logic for partial view cache clearing

---------

Co-authored-by: kjac <kja@umbraco.dk>
2026-04-01 09:27:33 +02:00
Niels Lyngsø f4b3a0f4ac update management api types 2026-04-01 09:19:47 +02:00
Niels Lyngsø 68d9e02417 Merge branch 'main' into v18/dev
# Conflicts:
#	src/Umbraco.Cms.Api.Management/OpenApi.json
#	src/Umbraco.Web.UI.Client/src/packages/core/backend-api/sdk.gen.ts
2026-04-01 09:13:55 +02:00
6e85871595 Global Elements: Recycle Bin UI (#21872)
* Uncommented placeholders for restore endpoints

* Delete (inside Recycle Bin): wired up correct endpoints

* Added condition for "Empty Recycle Bin" collection-action

to only display in the Recycle Bin root.

* feat(recycle-bin): add destination entity overrides to restoreFromRecycleBin kind

Add optional destinationItemRepositoryAlias, destinationItemDataResolver,
and destinationRootEntityType properties to support cross-entity-type
restore (e.g. element restoring into element-folder). Existing document
and media manifests are unaffected as all new properties fall back to
the original values. Also adds element folder restore manifest.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(recycle-bin): extract #resolveDestinationItemName to reduce complexity

Extract resolver logic from setDestination into a dedicated method to
bring cyclomatic complexity under the threshold of 9.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Removed Restore Element Folder From Recycle Bin Entity Action

(This is for a separate PR)

* feat(elements): enable element and folder restore from recycle bin

Uncomment element restore manifest with destination overrides, add
folder picker modal, and add null guard for restore item lookup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fixes bug with selecting the Root for the restore target

* Corrected manifest aliases to use appropriate entity-type name for `ElementFolder`

* Added `UmbElementFolderItemDataResolver` to resolve folder names in recycle bin restore modal

* E2E: QA Added acceptance tests for restoring elements and deleting elements from recycle bin (#22069)

* Updated test helper for move a folder to recycle bin

* Added tests for restore element and delete element from recycle bin

* Added ocmment for the failing tests

* Make recycle bin tests run in the pipeline

* Fixed comment

* Removed duplication code

* Reverted npm command

* Adds `itemDataResolver` to the Element Trash entity-action

* Makes trashed Element Folder name to be read-only

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
Co-authored-by: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com>
Co-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com>
2026-04-01 09:01:05 +02:00
Andy ButlandandGitHub 6853f2c910 EF Core: Align casing of EF Core code constructs (closes #22247) (#22313)
* Align casing of EFCore code constructs.

* Handle code review feedback.
2026-04-01 06:56:48 +02:00
Andy Butland 9d94b26cc2 Merge branch 'main' into v18/dev 2026-03-31 12:12:57 +02:00
58cbc9790f Global Elements: Adds "Start Node" and "Ignore User Start Nodes" to Element Picker configuration (#22255)
* Element Picker property-editor: adds "Start Node" configuration

* [WIP] Adds server config for Element start node

* [WIP] Attempts to wire up the `dataTypeId`

for the Element Picker start node

* Removed `StartNodeId` from the server config

* Implemented `requestTreeStartNode`

on Element Picker data-source

* Fix duplicate config entries in input-element property setters

The `folderOnly` and `startNode` setters used `.push()` without
deduplication, causing config entries to accumulate on Lit re-renders.
Filter existing entries before pushing to prevent duplicates.

* Update OpenAPI spec and regenerate TypeScript bindings

Add dataTypeId query parameter to element tree endpoints.

* Refactor input-element to compute dataSourceConfig on demand

Replace mutable #dataSourceConfig array with plain Lit properties for
folderOnly and startNode, computing the config inline in render. This
eliminates the duplicate-entry bug and simplifies the component.

Also fix "dont" typo in ignoreUserStartNodes description.

---------

Co-authored-by: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com>
2026-03-31 08:11:06 +01:00
8311bae2ab User Permissions: Resolve and persist element start node IDs when updating a user (#22297)
* Resolve and persist element start node IDs when updating a user

The UpdateAsync method in UserService only resolved Document and Media
start node keys to IDs, completely ignoring ElementStartNodeKeys from
the update model. This caused element start node configuration to be
silently lost on user save.

* Add ElementStartNodeNotFound status and fix XML doc for MapUserUpdate

Introduces a dedicated ElementStartNodeNotFound operation status to
distinguish missing element start nodes from missing element items in
other operations, consistent with ContentStartNodeNotFound and
MediaStartNodeNotFound. Also adds the missing XML doc param for
startElementIds on MapUserUpdate.

* Add blank line to re-trigger the build.

---------

Co-authored-by: kjac <kja@umbraco.dk>
2026-03-31 07:26:58 +02:00
2bfb83d6f7 Global Elements: Add "Allowed in library" toggle to Document Type structure view (#21875)
* Added localization keys

* Fixed mock data

* Adds UI for "Allowed in library" configuration

* Capitalize nouns regarding allow in library

* Focuses `allowedInLibrary` on Document/Element Types

* refactor(web): extract route setup from UmbDocumentTypeWorkspaceContext constructor

Move route configuration into a private #setupRoutes() method to reduce
constructor cyclomatic complexity below the threshold of 9.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
Co-authored-by: Claude <noreply@anthropic.com>
2026-03-30 17:46:13 +01:00
Laura NetoandGitHub f1a7c6bbc1 Localization: Remove unused recycleBin keys from XML language files (#22299)
Remove unused recycleBin keys from XML language files

The recycleBin area contained keys (contentTrashed, mediaTrashed,
elementTrashed, elementContainerTrashed, itemCannotBeRestored,
itemCannotBeRestoredHelpText, wasRestored) that are no longer
referenced by any backend code since the audit logging was removed
from RelateOnTrashNotificationHandler in #21481.
2026-03-30 17:04:34 +02:00
4d993a6dd1 Elements: Add flag support for pending changes and scheduled publish (#21877)
* Add flag support for pending changes and scheduled publish

Add entity sign manifests, tree item rendering, and flag provider
support so element tree items display pending changes (pencil) and
scheduled publish (clock) icons, mirroring the existing document
behavior.

Refactor flag providers and presentation factories to reduce
duplication, and move shared IHasFlags implementation into
PublishableVariantResponseModelBase.

* Fix HasScheduleFlagProvider test mocks to match refactored per-item lookups

* Extract PublishableVariantItemResponseModelBase to deduplicate variant item models

* Extract shared base class from Document/Element presentation factories

Introduce PublishableContentPresentationFactoryBase to eliminate code
duplication between DocumentPresentationFactory and ElementPresentationFactory.
Add async alternatives (CreateVariantsItemResponseModelsAsync,
CreateItemResponseModelAsync, PopulateFlagsAsync) and migrate callers in
async contexts to use them. Sync callers in tree/recycle bin controllers
use .GetAwaiter().GetResult() to avoid breaking changes in base classes.

Add IPublishableContentEntitySlim overload to DocumentVariantStateHelper
to unify the identical IDocumentEntitySlim/IElementEntitySlim overloads.

Make RelationTypePresentationFactory properly async with Task.WhenAll.

* Fix flags fallback to use empty array instead of empty string

* Acceptance Tests: Fix element tree item locator to match both elements and folders

The element tree renders umb-element-tree-item for elements but
umb-default-tree-item for folders. Update the E2E test helper locator
to use :is() to match both custom element types.

* Split HasScheduleFlagProvider into document and element providers

Address PR review feedback:
- Split HasScheduleFlagProvider into HasDocumentScheduleFlagProvider and
  HasElementScheduleFlagProvider with a shared HasScheduleFlagProviderBase
- Fix N+1 query: use batch GetContentSchedulesByKeys instead of per-item
  GetContentScheduleByContentId
- Add GetContentSchedulesByKeys to IPublishableContentService and implement
  in PublishableContentServiceBase, removing the duplicate from IContentService
  and ContentService
- Inject TimeProvider into base class, replacing DateTime.Now with
  _timeProvider.GetUtcNow()
- Split tests to match new provider structure and verify batch retrieval

* Make tree and recycle bin mapping methods async

Remove .GetAwaiter().GetResult() calls introduced by the element flag
support changes. Rename MapTreeItemViewModel to MapTreeItemViewModelAsync
and MapRecycleBinViewModel to MapRecycleBinViewModelAsync across all
tree and recycle bin controllers, properly awaiting async factory calls.

* Extract Task.WhenAll select expressions into named variables

* Add missing XML docs to async methods on IDocumentPresentationFactory

* Fix DateTime vs DateTimeOffset comparison in schedule flag provider

Compare schedule.Date against _timeProvider.GetUtcNow().UtcDateTime
instead of the DateTimeOffset directly, avoiding implicit conversion
issues with DateTimeKind.Unspecified.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
2026-03-30 12:54:37 +02:00
Andy Butland 950470aade Make long-running concurrent save test more robust. 2026-03-28 17:01:11 +01:00
7f1255b5e7 Content Types: Granular content type change types (#22223)
* Add more granularity to ContentTypeChangeTypes and handle for structucal changes (pending non-structucal changes).

* Integration tests to validate the granular, structucal change types

* Implement "other" changes

* Make "other" changes less granular.

* Update tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentTypeEditingServiceTests.ChangeTypes.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Clean up

* Add test proving the sub-flags do not collide

* Support change detection for both structural and non-structural changes in one operation

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-03-27 09:55:35 +01:00
6e27ab2e0a Elements: Add missing notifications to element container and element editing services (#22012)
Add missing notifications to element container and element editing services

Add ElementDeletingNotification and ElementTreeChangeNotification to
ElementContainerService for EmptyRecycleBin, Move, MoveToRecycleBin,
and Delete operations, aligning with ContentService notification patterns.

Add ElementTreeChangeNotification to ElementEditingService for Move
and Copy operations.

Refactor DeleteDescendantsLocked to return deleted elements and
DeleteItem to return the deleted entity for use in tree change
notifications.

Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
2026-03-27 08:55:59 +01:00
Andy Butland da614e7d2c Fixed integration tests failing on SQL Server and NUnit 4. 2026-03-27 08:26:18 +01:00
Andy Butland 858c450223 Merge branch 'main' into v18/dev 2026-03-26 16:51:27 +01:00
e8f5b98cb0 Code Clean-up (18): Remove obsoleted code flagged for removal (Part 2) (#22137)
* Remove obsolete code

* Update tests in BlockEditorBackwardsCompatibilityTests

* update languageId, remove obsolete construcor from ApiLink

* remove the tests

* Fixed build of unit tests.

* Reverted removal of UmbracoApiController for now (we should do this in a single PR).

* Code style fix.

---------

Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-03-26 15:08:10 +00:00
Nhu DinhandGitHub 339da5e19e E2E: V18 Fixed the failing smoke tests (#22248)
* Reverted fix

* Updated tests regarding duplication due to UI changes
2026-03-25 09:35:40 +00:00
Andy Butland 9ef7c5b955 Further fix to failing acceptance test. 2026-03-25 08:37:35 +01:00
Andy Butland ad8db9c567 Fixed failing integration and acceptance tests after merge. 2026-03-25 06:51:24 +01:00
Andy Butland 0210dd00a0 Fix after merge. 2026-03-24 17:37:17 +01:00
Andy Butland 41f62ae03b Merge branch 'main' into v18/dev 2026-03-24 16:58:46 +01:00
Sven Geusens b19f8a2eb1 Add v18/dev to nightly build trigger 2026-03-24 11:11:51 +01:00
Kenn JacobsenandGitHub 1a86c9f45c Change the default webhook payload type to "minimal" (#22217)
* Change the default webhook payload type to "minimal"

* Include expected defaults in webhook telemetry + use core constants instead of local strings
2026-03-23 09:19:17 +01:00
Nhu DinhandGitHub 5968bae002 Build: Cherry pick #22164 for V18 (#22165)
Serialize E2E stages and stagger branch schedules to reduce agent usage
2026-03-19 21:20:06 +07:00
7014f9a125 Dependencies: Upgrade NUnit and related test dependencies to latest major versions (#22155)
* Update Nunit and AutoFixture.Nunit to new versions

* Adding NonParallelizable

* Add blame-hang timeout to integration tests to detect hanging tests

* remove NonParallelizable, update NUnit3TestAdapter, add Ingore to CoreConfigurationHttpTests

* Resolve CoreConfigurationHttpTests hang with NUnit 4.

  - Use Task.Run in CreateHost to escape NUnit 4's SynchronizationContext
    which deadlocks sync-over-async calls from async test methods.
  - Use await using for factory disposal to avoid same deadlock on shutdown
  - Remove WithWebHostBuilder which wraps the factory in a
    DelegatedWebApplicationFactory that bypasses the CreateHost override.
  - Add ContentRoot property to UmbracoWebApplicationFactory so content
    root can be set without WithWebHostBuilder.
  - Set ModelsBuilder mode to Nothing to prevent BootFailedException.
  - Add AddTestServices for infrastructure test doubles (MainDom, etc.).

* Revert changes to pipelines.

* Remove remaining CollectionAssert using legacy syntax.

---------

Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-03-19 11:49:08 +00:00
Niels LyngsøandGitHub 7536d6b95f Library: remove library sidebar app (#22139)
remove library sidebar app
2026-03-17 15:09:06 +00:00
Kenn JacobsenandGitHub 6036b13e94 Elements: Clean up container relations before deleting them (#22154)
Clean up element container relations before deleting them
2026-03-17 10:31:38 +01:00
d2e7fc1863 Dependencies: Update selected dependencies to latest major versions (#22060)
* update outdated dependencies to their latest major versions

* change version of JsonPatch.Net back to 3.*.*

* Upgrade Umbraco.Code package

* Update tests

* Resolve NUnit 4 migration issues causing test hangs

* Fix for dotnet test on the pipeline.

* Debug: Fix attempt for integration tests on the pipeline.

* Revert pipeline changes and go back to 5.2.0.

* Debug: Omit suspect tests.

* Debug: Disable tests with timeout.

* Debug: Try 4.6.0.

* Debug: Added reference to Microsoft.CodeAnalysis.CSharp.Workspaces.

* Roll back NUnit upgrade.

---------

Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk>
Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-03-17 06:37:21 +01:00
27b7f220a3 Elements: Fixes HasChildren for Element Folder entities (#22142)
* Fixes `HasChildren` for Element Folder entities

* Remove HasChilden mapping

---------

Co-authored-by: kjac <kja@umbraco.dk>
2026-03-16 17:01:08 +00:00
Andreas ZerbstandGitHub 9e360e8dc0 QA: Fix element tree integration tests and SQL Server container service error (#22130)
* fix SQL Server OFFSET/FETCH error

* added ActionElementBrowse.ActionLetter permission
2026-03-16 13:11:23 +07:00
a2acb7a53f Code Clean-up (18): Remove obsoleted code flagged for removal and address TODO comments (#21980)
* remove obsolete constructor

* adjust RootDictionaryTreeController constructor to use non-obsolete constructor and remove obsolete base

* todo action v18

* remove ActivatorUtilitiesConstructor atribute that there's only one constructor

* remove obsolete class and method

* remove obsolete code in v18

* remove obsolete code from repositories

* remove obsolete for blocks

* remove obsolete code from services

* remove Icomponent

* remove incorrect IRequestSegmmentService

* remove obsolete properties

* undo change of blocklayoutitembase because of test failed

* bring back somes code due to pr 21999

* bring back some codes and update ContentRouteBuildertests

* remove obsolete code from domains, notification controller and some services

* remove obsolete constructor from ElementMapDefinition

---------

Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk>
Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
2026-03-13 14:40:13 +01:00
Nhu DinhandGitHub d9db6b02ce E2E: QA Added .skip tags to failing acceptance tests due to known issues (#22122)
* Added .skip tags for the failing tests due to an actual issue

* Change the way to verify the validation message

* Added .skip tags for failing tests due to the actual issues
2026-03-13 16:30:00 +07:00
Nhu DinhandGitHub ae80d921c0 E2E: QA Updated acceptance tests in v18 due to the auth changes (#22110)
Updated tests due to the auth changes
2026-03-13 03:18:13 +00:00
Andy Butland 14a2ca89ea Adds XML documentation to elements management API controllers, models and mappers. 2026-03-12 18:20:13 +01:00
Andy Butland b2517e3302 Fix post merge issues. 2026-03-12 18:05:48 +01:00
Andy Butland f83949c508 Merge branch 'main' into v18/dev 2026-03-12 17:59:17 +01:00
Andy Butland af439c6a66 Fixes and additional documentation after merge. 2026-03-12 16:16:11 +01:00
Andy Butland 71d700ea28 Merge branch 'main' into v18/dev 2026-03-12 16:15:40 +01:00
Niels Lyngsø 90b2062d85 Merge branch 'main' into v18/dev
# Conflicts:
#	src/Umbraco.Cms.Api.Management/Controllers/Content/ContentControllerBase.cs
#	src/Umbraco.Web.UI.Client/package.json
#	src/Umbraco.Web.UI.Client/src/packages/core/backend-api/sdk.gen.ts
#	tests/Umbraco.Tests.AcceptanceTest/lib/helpers/ContentUiHelper.ts
#	tests/Umbraco.Tests.Integration/CompatibilitySuppressions.xml
#	version.json
2026-03-12 14:21:54 +01:00
Jacob Overgaard e6a91e5f6c Merge remote-tracking branch 'origin/main' into v18/dev 2026-03-11 15:30:02 +01:00
Andreas Zerbst 421d616682 Merge branch 'main' into v18/dev
# Conflicts:
#	tests/Umbraco.Tests.AcceptanceTest/lib/helpers/ApiHelpers.ts
#	tests/Umbraco.Tests.AcceptanceTest/lib/helpers/DataTypeUiHelper.ts
#	tests/Umbraco.Tests.AcceptanceTest/lib/helpers/UiBaseLocators.ts
#	tests/Umbraco.Tests.AcceptanceTest/lib/helpers/UserApiHelper.ts
#	tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/MediaPicker.spec.ts
#	tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/ContentStartNodes.spec.ts
#	tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/MediaStartNodes.spec.ts
#	tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/UserGroups.spec.ts
2026-03-10 17:05:11 +01:00
Niels LyngsøandGitHub fb5030010c Form Control: only validate if value was changed during focus (#21815)
* poc of minimizing unrelevant validation messages

* remove submit method from interface

* remove call to re-validate, as that is already trigger via `updated`--callback
2026-03-09 08:44:46 +00:00
Laura NetoandGitHub 421ad35034 Elements: Split content type validation for create and update (#21906)
* Split content type validation for create and update to allow saving elements no longer permitted in library

* Add integration test for element update after AllowedInLibrary toggle

Verify that ElementEditingService.UpdateAsync succeeds when the content
type's AllowedInLibrary flag is set to false after the element was
created, covering the split validation introduced for create vs update.

* Move content type validation into TryGetAndValidateContentType override

Eliminate redundant content type lookups in CreateAsync and UpdateAsync
by moving the IsElement/AllowedInLibrary check into the
TryGetAndValidateContentType override, which distinguishes create from
update by checking if the model is a ContentCreationModelBase.

* Use Assert.Multiple for element property assertions in update test

* Extract IsAllowedLibraryElement static method for readability
2026-03-06 17:34:00 +00:00
Andy Butland a17aef5af8 Fix sortable property update issue introduced in merge from main. 2026-03-06 17:55:07 +01:00
Andy Butland 6544c5cbcc Merge branch 'main' into v18/dev 2026-03-06 11:05:17 +01:00
Andy Butland a0518a0636 Merge branch 'main' into v18/dev 2026-03-06 08:09:37 +01:00
Andy Butland 5e669bb8c7 Merge branch 'main' into v18/dev 2026-03-06 08:08:28 +01:00
Nhu DinhandGitHub 29ef442e99 E2E: QA Added acceptance tests for element picker in content and element (#21745)
* Added tests for content with element picker

* Added tests for element with element picker

* Bumped version

* Renamed tests

* Make tests run in the pipeline

* Bumped version

* Fixed failing tests

* Moved goToBackOffice step to beforeEach

* Moved goToBackOffice to beforeEach

* Fixed comment

* Fixed afterEach() step

* Fixed import

* Fixed

* Reverted npm command
2026-03-06 10:51:52 +07:00
2ae5582a9e Recycle Bin: Adds destination overrides to restoreFromRecycleBin entity-action kind (#21867)
* feat(recycle-bin): add destination entity overrides to restoreFromRecycleBin kind

Add optional destinationItemRepositoryAlias, destinationItemDataResolver,
and destinationRootEntityType properties to support cross-entity-type
restore (e.g. element restoring into element-folder). Existing document
and media manifests are unaffected as all new properties fall back to
the original values. Also adds element folder restore manifest.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(recycle-bin): extract #resolveDestinationItemName to reduce complexity

Extract resolver logic from setDestination into a dedicated method to
bring cyclomatic complexity under the threshold of 9.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Removed Restore Element Folder From Recycle Bin Entity Action

(This is for a separate PR)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 13:45:59 +01:00
0ec334e252 fix(media): allow focal point to be set to null in image cropper (#21340)
* fix(media): allow focal point to be set to null in image cropper

- Updated UmbImageCropperPropertyEditorValue type to allow null for focalPoint
- Changed component state to use null as default instead of { left: 0.5, top: 0.5 }
- Replaced logical OR (||) with nullish coalescing (??) to preserve null values
- Updated reset function to set focalPoint to null
- Added null handling in all rendering and calculation logic
- Components now default to center (0.5, 0.5) for display when focalPoint is null

Fixes #21273

* refactor(media): extract logic from initializeCrop to reduce function size

- Extracted mask dimension calculation into #calculateMaskDimensions
- Extracted mask style application into #applyMaskStyles
- Extracted image scale calculation into #calculateImageScales
- Extracted image position calculation into separate methods:
  - #calculateImagePositionWithCoordinates (for existing crops)
  - #calculateImagePositionWithFocalPoint (for focal point positioning)
- Extracted image style application into #applyImageStyles
- Extracted zoom level update into #updateZoomLevel

Reduces #initializeCrop from 72 lines to 33 lines, meeting CI/CD threshold of 70 lines.

Related to #21273

* refactor(media): replace primitive parameters with interfaces to fix code quality warnings

- Created ViewportDimensions interface to group viewport width/height
- Created MaskDimensions interface to group mask dimensions and position
- Created ImageDimensions interface to group image dimensions and position
- Refactored all functions to use interface objects instead of multiple primitives
- Reduced #calculateImageDimensionsAndPosition from 5 args to 2
- Reduced #calculateImagePositionWithCoordinates from 5 args to 2
- Reduced #calculateImagePositionWithFocalPoint from 4 args to 1

Fixes primitive obsession (85.7% -> reduced) and excessive function arguments warnings.

Related to #21273

* fix(media): update modal value interface to allow null focal point

- Updated UmbImageCropperEditorModalValue interface to allow null for focalPoint
- Added explicit null handling when assigning focalPoint in onChange handler

Fixes TypeScript build error where null focalPoint was not assignable to non-nullable type.

Related to #21273

* Refactor image cropper focal-point handling

* Set defaultFocalPoint to null in test file.

* Default focalPoint to null and adjust checks.

---------

Co-authored-by: Francluob <francluob.dev@gmail.com>
Co-authored-by: Engiber Lozada <89547469+engijlr@users.noreply.github.com>
Co-authored-by: Emma L Garland <1649855+emmagarland@users.noreply.github.com>
Co-authored-by: engjlr <enl@umbraco.dk>
2026-03-05 10:57:50 +00:00
Niels Lyngsø db9cbcb457 Merge branch 'main' into v18/dev 2026-03-05 11:07:26 +01:00
Niels Lyngsø d04f8d9029 fit unit test types 2026-03-05 11:04:41 +01:00
Andy Butland c4d09893cd Merge branch 'main' into v18/dev 2026-03-05 09:22:54 +01:00
Jacob Overgaard 040735b1c1 build: optimises azure static builds in order not to consume too many environments 2026-03-05 08:32:25 +01:00
Andy Butland d92502b212 Merge branch 'main' into v18/dev 2026-03-04 12:15:01 +01:00
Andy Butland 6b00925a6a Merge branch 'main' into v18/dev 2026-03-04 11:55:57 +01:00
Andy Butland b9142ad728 Merge branch 'main' into v18/dev 2026-03-04 11:54:49 +01:00
Nhu DinhandGitHub b71c2e53b7 E2E: QA Added acceptance tests for reference tracking info tab of elements (#21949)
* Added tests for element reference tracking in info tab

* Removed tags

* Make all ElementReferenceTracking tests run in the pipeline

* Moved goToBackOffice step to beforeEach

* Updated import file

* Make tests run in the pipeline before merging

* Fixed npm command

* Revert npm command
2026-03-04 10:11:46 +00:00
598a2186d7 Elements: Treat local elements as global elements (#21795)
* Make local and global elements behave the same (use the same implementation)

* Await async calls, don't fire-and-forget

* Fix the remaining unit tests

* Flush static fields on friendly published extensions before starting tests

---------

Co-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com>
2026-03-04 10:05:52 +01:00
fcdfb9e588 E2E: QA: Merge moved testhelpers/builders from 17 to 18 (#21970)
* Moved helpers/builder from v18

* Updated existing helpers/builder

* Updated ui helper for updating property editor in document type

* Fixed failing tests

* Revert changes to package-lock

* Cherry pick latest updates from main

---------

Co-authored-by: Nhu Dinh <hnd@umbraco.dk>
2026-03-04 12:02:24 +07:00
Jacob Overgaard b00840e147 build(login): syncs lockfile 2026-03-03 16:20:43 +01:00
Jacob Overgaard f2473f7f74 build(login): syncs lockfile 2026-03-03 16:19:08 +01:00
Jacob Overgaard 673d97c19b build(login): syncs package files 2026-03-03 16:17:06 +01:00
Andy Butland 32acc62cde Merge branch 'main' into v18/dev 2026-03-03 15:44:45 +01:00
Laura Neto c568db2704 Re-generate Umbraco.Tests.AcceptanceTest/package-lock.json 2026-03-03 12:45:34 +01:00
Andy Butland fa6c5d0537 Merge branch 'main' into v18/dev 2026-03-03 11:44:10 +01:00
Laura NetoandGitHub 62d9a002f7 Elements: Add missing documentation endpoint attributes (#21979)
Add missing EndpointSummary and EndpointDescription attributes to element recycle bin restore controllers
2026-03-03 08:47:01 +01:00
Andy Butland 8b59e8eb3b Merge branch 'main' into v18/dev 2026-03-03 08:06:31 +01:00
Niels Lyngsø eec6a27cca Merge branch 'main' into v18/dev
# Conflicts:
#	tests/Umbraco.Tests.AcceptanceTest/package-lock.json
#	tests/Umbraco.Tests.AcceptanceTest/package.json
#	tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/ContentStartNodes.spec.ts
#	tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/MediaStartNodes.spec.ts
2026-03-02 14:00:47 +01:00
Nhu DinhandGitHub 3b68fef220 E2E: QA Added acceptance tests for global elements (#21608) 2026-02-26 17:28:21 +00:00
Andy Butland 92ec78086d Merge branch 'main' into v18/dev 2026-02-26 07:06:34 +01:00
cf9c2908b4 Elements: Add permission-based filtering to element tree endpoints (#21729)
* Add permission-based filtering to element tree endpoints

The element tree endpoints now filter results based on the current
user's browse permissions via a new IElementPermissionFilterService,
mirroring the existing document tree behavior.

Also extracts shared filtering logic from DocumentPermissionFilterService
into a PermissionFilterServiceBase to avoid duplication.

* Add unit tests for ElementPermissionFilterService

* Replace document-specific inheritdoc with neutral XML docs in PermissionFilterServiceBase

* Fix GetPermissionsAsync to use the provided objectTypes parameter instead of hardcoded Document type

---------

Co-authored-by: Kenn Jacobsen <kja@umbraco.dk>
2026-02-25 15:13:42 +00:00
Laura NetoandGitHub 765a3b2968 Tests: Set AllowedInLibrary on element content type in permission tests (#21908)
Set AllowedInLibrary on element content type in permission tests

The GetElementPermissionsCurrentUserControllerTests were failing because the
test setup created an element content type without setting AllowedInLibrary
to true. The ElementEditingService.TryGetAndValidateContentType method now
requires both IsElement and AllowedInLibrary to be true for element creation.
2026-02-25 15:02:12 +01:00
Laura NetoandGitHub 89c7bb356b Elements: Replace block keys on element copy and save (#21814)
* Handle element saving and copying notifications in complex property editors

Extend ComplexPropertyEditorContentNotificationHandler to also handle
ElementSavingNotification and ElementCopyingNotification, ensuring that
block property key replacement (BlockList, BlockGrid, RichText) is
applied to elements the same way it is for content.

* Add integration tests for element copy with block editors

Test that block keys are regenerated and block structure is preserved
when copying elements with BlockList, BlockGrid, and RichText editors,
for both invariant and culture-variant content.
2026-02-25 14:06:55 +01:00
Andy Butland 023c08fcdb Merge branch 'main' into v18/dev 2026-02-25 11:30:01 +01:00
Andreas ZerbstandGitHub 29233a3455 QA: E2E: Added v18/dev so it runs on the nightly test pipeline (#21902)
Removed v15dev and added 18dev to nightly pipeline
2026-02-25 10:22:04 +00:00
Andy Butland 7b97bdd0ef Merge branch 'main' into v18/dev 2026-02-25 09:41:15 +01:00
Andy Butland 21597fa2f8 Merge branch 'main' into v18/dev 2026-02-24 06:57:14 +01:00
Andy Butland 336039f963 Merge branch 'main' into v18/dev 2026-02-24 06:40:18 +01:00
Laura NetoandGitHub c7125967c9 Elements: Add scheduled publishing support for elements (#21796)
* Add scheduled publishing support for elements

Move PerformScheduledPublish from IContentService to the shared
IPublishableContentService<T> interface so both documents and elements
support scheduled publishing.

Filter ClearSchedule and HasContentForRelease/Expiration queries in
PublishableContentRepositoryBase by NodeObjectTypeId to prevent document
and element schedules from interfering with each other.

Update ScheduledPublishingJob to process both document and element
schedules, and add integration tests verifying cross-entity isolation.

* Simplify ScheduledPublishingJob.ExecuteAsync

Extract duplicated scheduled publishing logic into a generic helper
method and include the entity type in the log message.
2026-02-23 20:12:00 +01:00
3e0a3ff1ea Content Version Cleanup: Include element versions in the background cleanup job (#21839)
* Add element version cleanup to the content version cleanup background job

The existing ContentVersionCleanupJob only cleaned up document versions.
Element versions were left to accumulate despite having the same cleanup
service infrastructure available. This extends the job to also clean up
element versions using the same configuration toggle and schedule.

* Use PascalCase for structured logging names.

* Fixed code warnings and duplicate line breaks.

* Cleaned up usings.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-02-23 18:58:15 +00:00
Laura NetoandGitHub fa95f956a0 Elements: Add audit log retrieval endpoint and UI support (#21777)
* Add missing AuditType.Copy audit log for element copy operations

Make the abstract Copy method in ContentEditingServiceBase async and
accept a Guid userKey instead of int userId, allowing the element
copy implementation to use the audit service directly. Add the
missing _auditService.AddAsync(AuditType.Copy, ...) call in
ElementEditingService.CopyAsync to match the document equivalent
in ContentService.Copy.

* Fix copy audit log to record against the original element

The copy audit entry was being logged against the new copy's ID
instead of the original element's ID, inconsistent with how
documents handle copy audit logging.

* Add audit log retrieval endpoint for elements and wire up frontend

Add GET /{id:guid}/audit-log endpoint following the document audit
log pattern. Wire up the existing frontend data source to call the
new API, add element-specific localization strings, and remove the
unsupported sort audit type.
2026-02-23 08:43:36 +01:00
Niels Lyngsø ba83cc1006 Merge branch 'main' into v18/dev 2026-02-20 13:48:48 +01:00
Laura Neto 17dd0757d3 Merge branch 'main' into v18/dev 2026-02-20 13:38:06 +01:00
Laura NetoandGitHub e02a3d452c Repository Caches: Fix GUID cache key prefix in PublishableContentRepositoryBase (#21836)
Fix GUID repository cache key prefix in PublishableContentRepositoryBase

The merge of the GUID cache key collision fix (9ea0520) applied
IContent-specific changes from DocumentRepository's nested class, but
in v18/dev this code lives in the generic base class. Two issues:

- EntityByGuidReadRepository.GetCacheKey used the "uRepo_" prefix
  while GuidReadRepositoryCachePolicy looks up entries with "uRepoGuid_",
  causing PopulateCacheByKey to insert under a key the policy never finds.
- PersistUpdatedItem cleared GetGuidKey<IContent> instead of
  GetGuidKey<TEntity>, so ElementRepository would clear the wrong key.
2026-02-20 11:35:52 +00:00
Niels Lyngsø 33a30c38dc Merge branch 'main' into v18/dev 2026-02-19 10:50:40 +01:00
Andy Butland 9a0309ac62 Merge branch 'main' into v18/dev 2026-02-19 10:03:10 +01:00
Niels Lyngsø 31ffc9ff02 Merge branch 'main' into v18/dev 2026-02-17 10:37:21 +01:00
8e911d728d Elements: Add AllowedInLibrary flag to content types with dedicated endpoint (#21723)
* Add AllowedInLibrary flag to content types

Add a new boolean property AllowedInLibrary across all layers to
indicate whether a content type is allowed in the library. This is
only meaningful for element types (IsElement = true).

Changes span the core domain model, Management API request/response
models, persistence DTOs/mappers/factories, and a database migration
to add the column to the cmsContentType table.

* Enforce AllowedInLibrary in ElementEditingService.CreateAsync

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(api): add AllowedInLibrary filter to document type search endpoint

Add allowedInLibrary query parameter to GET /document-type/search,
following the same pattern as the existing isElement filter. The old
SearchAsync overload without the parameter is preserved as a default
interface method and marked obsolete (scheduled for removal in v19).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore(api): regenerate OpenApi.json and backoffice client types

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(api): make search query parameter nullable for document type search

Allow the document type search endpoint to be called without a text
query, enabling filter-only usage (e.g. filtering by isElement and
allowedInLibrary without requiring a search term).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(api): replace search allowedInLibrary filter with dedicated endpoint

Revert the search endpoint changes (IContentTypeSearchService, controller)
and instead add a dedicated GET /document-type/allowed-in-library endpoint
that follows the AllowedAtRoot pattern. This ensures IContentTypeFilter
support and a cleaner API separation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test(api): add integration tests for GetAllAllowedInLibraryAsync and AllowedInLibraryDocumentTypeController

Add service-level tests verifying correct filtering by IsElement + AllowedInLibrary, pagination, and IContentTypeFilter integration. Add controller-level authorization tests for the allowed-in-library endpoint.

* Map allowedInLibrary through content type data sources

Add allowedInLibrary to UmbContentTypeModel and map it consistently
across document, media, and member type data sources for scaffold, read,
create, and update operations.

* Add AllowedInLibrary support to test builders and set it in all element tests

ElementEditingService.CreateAsync checks contentType.AllowedInLibrary and
returns NotAllowed if false. All element test types were missing this flag,
causing test failures. Adds IWithAllowedInLibraryBuilder interface, extension
method, and sets AllowedInLibrary=true on all element type creation in tests.

* Also enforce IsElement check when creating elements in the library

* Set IsElement and AllowedInLibrary on ElementPublishingServiceTests content types

* Remove AllowedInLibrary from document type tree item response model

The AllowedInLibrary property is not relevant for tree items and is not
used by the frontend. This removes it from the tree item model, its
mapping in the tree controller, and regenerates the OpenAPI spec and
TypeScript client accordingly.

* Refactor element content type validation into base class override

Make TryGetAndValidateContentType protected virtual in
ContentEditingServiceBase and override it in ElementEditingService to
check IsElement and AllowedInLibrary. This guards both create and update
paths (previously only create was guarded) and eliminates duplicate
ContentTypeNotFound handling.

Enable the previously-ignored
Cannot_Create_Element_Based_On_NonElement_ContentType test and add a new
test for the AllowedInLibrary check.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 10:01:34 +01:00
Andy Butland 7a12f056e4 Merge branch 'main' into v18/dev 2026-02-17 07:33:01 +01:00
Kenn JacobsenandGitHub 391e8a0867 Fix the integration tests project file structure (#21766) 2026-02-16 11:27:11 +00:00
0ca1a861e9 Elements: Add webhooks support (#21697)
* Add webhooks for elements

* Review: Removed unused payload type

* Use new object as empty payload

---------

Co-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com>
2026-02-16 10:36:00 +01:00
Laura Neto e3135685da Merge branch 'main' into v18/dev
# Conflicts:
#	src/Umbraco.Core/Services/UserService.cs
2026-02-16 10:28:54 +01:00
6f0fd8f6ec Elements: Add reference settings support (#21601)
* Elements: Add DisableDeleteWhenReferenced support and fix delete notifications

- Add DisableDeleteWhenReferenced check to ElementContainerService delete operations
- Fire ElementDeletedNotification and EntityContainerDeletedNotification per item during descendant deletion
- Fix potential infinite loop when items are skipped due to being referenced
- Simplify EmptyRecycleBinAsync to use DeleteDescendantsLocked directly
- Use path descending ordering for consistent deletion order (children before parents)
- Add test for descendant delete notifications

* Elements: Fix EmptyRecycleBin pagination with DisableDeleteWhenReferenced

When DisableDeleteWhenReferenced is enabled and some items are skipped,
the standard skip/take pagination breaks. This change:

- Adds SqlLessThan/SqlGreaterThan SQL expression extensions for string
  comparison in LINQ queries
- Uses path-based cursor pagination instead of skip/take
- Tracks protected paths to prevent deleting containers that have
  referenced descendants
- Adds ElementRecycleBin to UmbracoObjectTypes enum

* Tests: Add DisableUnpublishWhenReferenced tests for elements

Verify that DisableUnpublishWhenReferenced works correctly for elements
(inherited from ContentPublishingServiceBase):
- Cannot unpublish an element that is being referenced
- Can unpublish an element that is doing the referencing

* Elements: Remove redundant Trashed filter from DeleteDescendantsLocked

The Trashed filter was redundant because:
- EmptyRecycleBinAsync only operates on items under the recycle bin root
- DeleteFromRecycleBinAsync requires containers to be trashed, and all
  descendants are marked as trashed when moved to recycle bin

Removing the filter simplifies the query and handles edge cases better.

* Elements: Add proper ProblemDetails responses for publish/unpublish endpoints

Move ContentPublishingOperationStatusResult from DocumentControllerBase to
ContentControllerBase so it can be shared. Add ElementPublishingOperationStatusResult
to ElementControllerBase and update PublishElementController and
UnpublishElementController to return proper error responses instead of empty
BadRequest() when operations fail (e.g., when DisableUnpublishWhenReferenced is enabled).

* Refactor: Use abstract EntityName for content controller error messages

Replace hardcoded "document" terminology in shared ContentControllerBase
error messages with an abstract EntityName property, so each subclass
(document, element, media, member, etc.) provides context-appropriate
error messages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix: Check DisableUnpublishWhenReferenced when moving elements to recycle bin

ElementEditingService.MoveToRecycleBinAsync was missing the reference
check that ContentEditingService already performs for documents. This
allowed referenced elements to be moved to the recycle bin even when
DisableUnpublishWhenReferenced was enabled.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Prevent moving container to recycle bin when descendants are referenced

Add server-side validation to ElementContainerService.MoveToRecycleBinAsync
that checks for referenced descendants when DisableUnpublishWhenReferenced
is enabled. Uses ITrackedReferencesService.GetPagedDescendantsInReferencesAsync
as an upfront check before any move processing begins.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 16:02:43 +01:00
Laura NetoandGitHub ebcb996431 Elements: Fix delete blocked by trash-tracking relation (#21725)
Fix element delete blocked by trash-tracking relation

ElementEditingService was missing the RelateParentOnDeleteAlias
override, so the "relate parent on delete" relation created when
trashing was not excluded from the reference check. This caused
"Cannot delete a referenced content item" when
DisableDeleteWhenReferenced was enabled, even for unreferenced
elements.
2026-02-13 07:30:28 +01:00
8cbf68223a Global Elements: UI refinements, element picker and constants tidy-up (#21737)
* improvement(elements): general UI updates, element picker rework, and constants tidy-up

* fix(elements): forward min/max messages through umb-input-element and minor cleanups

Add minMessage/maxMessage properties to UmbInputElementElement so validation
messages are properly forwarded to the inner umb-input-entity-data component.
Also fix JSDoc grammar, variable naming, and comment tidying.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(elements): sync value/selection and register inner form control in umb-input-element

Add getter/setter overrides for value and selection that keep them in sync
(matching umb-input-content pattern), and register the inner umb-input-entity-data
via addFormControlElement() in firstUpdated() so validation propagates correctly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(elements): use correct element ID in referenced-by mock handler

Change sentinel ID from 'all-property-editors-document-id' to 'simple-element-id'
to match the actual element mock fixture IDs in element.data.ts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test(elements): add unit test for umb-input-element

Add instantiation and conditional a11y audit tests following the
umb-input-document.test.ts pattern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 18:49:56 +00:00
39f19bf3ab Global Elements: Rollback UI (#21712)
* feat(elements): add element rollback repository, modal, and audit log

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Exported rollback constants

* Update src/Umbraco.Web.UI.Client/src/packages/elements/rollback/modal/rollback-modal.element.ts

Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>
2026-02-12 17:08:08 +00:00
Niels Lyngsø e8c0dd897b Merge branch 'main' into v18/dev 2026-02-12 16:27:28 +01:00
66fbade194 Global Elements: Workspace Validation UI (#21711)
* feat(elements): add element workspace validation repository

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Global Elements: Address Copilot review feedback on validation PR

Fix JSDoc comments on validation repository/data-source to accurately
describe validation behavior instead of persistence. Use barrel import
for validation repository and remove leftover commented-out code.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Global Elements: Remove redundant guard clauses in validation data source

Remove TypeScript-redundant checks in validateCreate to reduce
cyclomatic complexity below the CodeScene threshold of 9.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>
2026-02-12 15:12:23 +01:00
ae2761f204 Global Elements: Reference Tracking UI (#21710)
* feat(elements): add element reference tracking repository

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fixed linting errors

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 15:02:20 +01:00
Andy Butland a49981b6c5 Merge branch 'main' into v18/dev 2026-02-12 06:50:15 +01:00
Lee KelleherandGitHub d21d1453f5 Global Elements: Exports all constants for @umbraco-cms/backoffice/element (#21727) 2026-02-11 17:52:40 +01:00
Niels Lyngsø 11e19466f8 Merge branch 'main' into v18/dev 2026-02-11 12:52:14 +01:00
Laura Neto d51de53804 chore(api): regenerate OpenApi.json and backoffice client SDK
The OpenAPI definition and backoffice TypeScript client were out of
sync with recent Management API changes already on v18/dev. Regenerated
to bring them up to date.
2026-02-10 15:25:21 +01:00
Laura Neto 8b5448adcd Merge branch 'main' into v18/dev
# Conflicts:
#	src/Umbraco.Core/Constants-Security.cs
#	src/Umbraco.Core/Extensions/ClaimsIdentityExtensions.cs
#	src/Umbraco.Core/Models/PublishedContent/PublishedContentBase.cs
#	src/Umbraco.Infrastructure/Events/RelateOnTrashNotificationHandler.cs
#	src/Umbraco.PublishedCache.HybridCache/PublishedContent.cs
#	src/Umbraco.Web.UI.Client/src/packages/core/backend-api/sdk.gen.ts
2026-02-10 14:02:51 +01:00
cf70d7ff13 Global Elements: Refactor content and element repositories into a common base (#21637)
* Begin implementation of repo base

* Move internal mapping - part 1

* Move internal mapping - part 2

* Move versioning, persistence, GUID sub repo and utilities to base

* Fix wrong assumption in cache tests

* Move content repo + recycle bin to base

* Move schedule to base

* Move common delete clauses to base

* Move DTO mapping to base

* Fix a few of the pending TODOs for elements

* Abstract OnUowRefreshedEntity away to concrete implementations

* Handle template editing in a less hardcoded way

* Restore DTO visibility for elements

* Update src/Umbraco.Core/Cache/CacheKeys.cs

Co-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com>

* Update src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PublishableContentRepositoryBase.cs

Co-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com>

* Update src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PublishableContentRepositoryBase.cs

Co-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com>

* Update cache key (review comment)

---------

Co-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com>
2026-02-10 09:54:38 +01:00
f0dc972bf2 Elements: Add restore from recycle bin functionality (#21556)
* Elements: Add restore from recycle bin functionality

- Add RestoreAsync to IElementEditingService and ElementEditingService
- Add RestoreAsync to IElementContainerService and ElementContainerService
- Add RestoreElementRecycleBinController and RestoreElementFolderRecycleBinController API endpoints
- Add TryGetContainedObjectType to EntityContainer for graceful handling of non-container types
- Update EntityContainerRepository to return null instead of throwing for non-container entities
- Add comprehensive integration tests for element and container restore operations

* Refactor: Remove entity return from Move/Restore/MoveToRecycleBin methods

Simplify the return types of IElementEditingService and IElementContainerService
move operations to return only the operation status instead of the entity.

These operations don't meaningfully change entity data (just location/state),
and no consumers were using the returned entities. Callers can use GetAsync
if they need the updated entity afterward.

* Fix: Capture original path before move for restore relation cleanup

The MoveEventInfo.OriginalPath was incorrectly set to the element's path
after the move, causing DeleteOriginalParentRelationsOnRestore to fail
because the path no longer contained the recycle bin path prefix.

* Tiny little formatting

---------

Co-authored-by: kjac <kja@umbraco.dk>
2026-02-09 13:39:25 +00:00
Andy Butland 81276fc048 Adds endpoint summaries and descriptions to new controllers introduced since 17. 2026-02-09 13:04:54 +01:00
Andy Butland 58f300a20d Merge branch 'main' into v18/dev 2026-02-09 12:36:23 +01:00
Niels Lyngsø f530772b80 Merge branch 'main' into v18/dev 2026-02-05 12:49:26 +01:00
Niels Lyngsø 8cea6cb6b5 Merge branch 'main' into v18/dev 2026-02-05 09:06:55 +01:00
Laura NetoandGitHub 7cf4c7857f Elements: Add reference tracking and recycle bin query support (#21481)
* Elements: Add reference tracking and recycle bin query support

- Add Element reference tracking API endpoints (referenced-by, are-referenced, referenced-descendants)
- Add Element recycle bin original-parent and referenced-by endpoints
- Add ElementReferenceResponseModel and ElementContainerReferenceResponseModel
- Add IElementRecycleBinQueryService for querying original parents of trashed elements
- Add Element relation type constants for parent tracking on delete
- Add translation strings for Element recycle bin operations
- Refactor RelateOnTrashNotificationHandler to reduce code duplication using generic helper methods
- Add Element and ElementContainer support to RelateOnTrashNotificationHandler

* Elements: Register Element notification handlers for relation tracking

Add Element and EntityContainer notification handlers for:
- RelateOnTrashNotificationHandler (move to/from recycle bin)
- ContentRelationsUpdate (track element content relations)

* Elements: Remove ReferencedDescendantsElementController

Elements are leaf nodes in the folder structure and cannot have
descendants, making this endpoint unnecessary.

* Elements: Fix ElementPickerPropertyEditor reference extraction

The element picker stores element IDs as Guids, not as UDI strings.
Updated GetReferences to deserialize as Guid array and create UDIs
from the Guid values.

* Elements: Fix TrackedReferencesRepository to include Element published state

Add LEFT JOIN to ElementDto and use COALESCE to get the published state
from either DocumentDto or ElementDto, fixing the issue where Element
references returned published = null.

* Elements: Add ReferencedDescendantsElementFolderController

Add endpoint to get referenced descendants of an element folder.
Unlike elements (which are leaf nodes), folders can have descendants
that may be referenced elsewhere.

* Elements: Add integration tests for Element reference tracking

Add TrackedReferencesServiceElementTests covering:
- GetPagedRelationsForItemAsync for Elements
- GetPagedRelationsForRecycleBinAsync for Elements
- GetPagedKeysWithDependentReferencesAsync for Elements
- GetPagedDescendantsInReferencesAsync for Element containers

* Elements: Add management API controller permission tests for Element reference endpoints

- Add ReferencedByElementControllerTests for element referenced-by endpoint permissions
- Add AreReferencedElementControllerTests for element are-referenced endpoint permissions
- Add ReferencedDescendantsElementFolderControllerTests for folder descendants endpoint permissions
- Fix GetManagementApiUrl to respect [FromQuery(Name="...")] attribute for proper URL generation

* Elements: Split OriginalParentElementRecycleBinController into two controllers

Split the controller to follow the controller-per-operation pattern,
consistent with DeleteElementRecycleBinController and
DeleteElementFolderRecycleBinController.

- OriginalParentElementRecycleBinController: for elements
- OriginalParentElementFolderRecycleBinController: for folders

* Elements: Fix user fallback for audit logging in RelateOnTrashNotificationHandler

Update the handler to properly resolve user key for audit logging, using a switch expression
to handle different entity types. Also sets CreatorId on EntityContainer creation.

* Tests: Fix duplicate query parameter in ItemElementItemControllerTests

Remove the ClientRequest() override that was appending a duplicate id query
parameter to the URL. The base MethodSelector already includes the element key
which gets converted to the query parameter by GetManagementApiUrl, causing
the URL to become ?id=<guid>?id=<guid> and model binding to fail.

* Tests: Fix permission controller tests to use correct entity types

These tests were passing on the base branch only because the URL was being
constructed incorrectly (missing query parameters). After be399134f8 fixed
the GetManagementApiUrl helper to properly include FromQuery parameters,
the tests now correctly build URLs and revealed that they were using user
keys instead of the expected document/media/element node keys.

Updated tests to create the appropriate entity type (document, media, or
element) and pass its key to the permission endpoints.

* Tests: Update RelationTypeRepositoryTest for new element relation types

Update expected counts and fix hardcoded ID lookup after new element
reference tracking relation types were added to the system:
- umbElement (RelatedElement)
- relateParentElementContainerOnElementDelete
- relateParentElementContainerOnContainerDelete

Changes:
- Store created test relation type in field to use actual ID instead of
  hardcoded ID 9 which shifted when built-in types were added
- Update GetAll expected count from 9 to 12 (9 built-in + 3 test data)
- Update Count query expected from 6 to 8 (aliases starting with "relate")

* Refactor: Rename methods in RelateOnTrashNotificationHandler for clarity

Rename methods and parameters to better describe their purpose:
- DeleteRelationsOnRestore → DeleteOriginalParentRelationsOnRestore
- CreateRelationsOnTrashAsync → CreateOriginalParentRelationOnTrashAsync
- relationTypeAlias → originalParentRelationTypeAlias
- relationTypeName → originalParentRelationTypeName

These names clarify that the methods handle "original parent" relations
used for restoring items from the recycle bin, not all relations.

* Tests: Fix ReferencedDescendantsElementFolderControllerTests expectations

- Use unique folder names to prevent conflicts between test runs
- Add assertion to verify folder creation succeeds
- Correct expected status codes for Editor and Writer to OK (not NotFound)

The NotFound responses were caused by folder creation failures due to
duplicate names, not actual permission restrictions.

* Tests: Add success assertions to Element controller test setup methods

Add Assert.IsTrue checks after service calls in test setup to ensure
test prerequisites are correctly established before running actual tests.
This prevents silent failures in setup from causing misleading test results.

Assertions added for:
- ElementContainerService.CreateAsync (9 tests)
- ElementEditingService.CreateAsync (19 tests)
- ElementEditingService.MoveToRecycleBinAsync (6 tests)
- ElementContainerService.MoveToRecycleBinAsync (3 tests)

* Elements: Fix MapReference to return un-enriched response when entity not found

Return the mapped response model instead of null when the matching entity
cannot be found for enrichment. This preserves basic reference information
even when variant data cannot be loaded, preventing valid references from
being silently dropped.

Also clean up ElementContainerReferenceResponseModel formatting.

* Fix: Guard GetSlimEntities against empty keys to prevent loading all entities

* Fix: Return ParentIsTrashed status when original parent is in recycle bin

* Breaking: Remove duplicate sync notification handler interfaces

Remove INotificationHandler<ContentMovedToRecycleBinNotification> and
INotificationHandler<MediaMovedToRecycleBinNotification> interfaces along
with their obsolete sync Handle methods. Only the async handlers should
be implemented.

* Tests: Simplify TrackedReferencesServiceElementTests

- Simplify assertions in Get_Descendants_In_References test
- Create Element3 after folder creation to avoid unnecessary update

* Revert: Remove changes to be moved to separate PRs

Revert EntityTypeContainerService.CreateAsync CreatorId change and
permission controller test changes - these should be addressed in
separate PRs.

* Revert: Remove GetMediaPermissionsCurrentUserControllerTests changes

This change should be addressed in a separate PR for v17.

* Refactor: Move recycle bin audit logging to services

Move audit logging for recycle bin operations from RelateOnTrashNotificationHandler
to the individual services (ContentService, MediaService, ElementEditingService,
ElementContainerService). This simplifies the notification handler and keeps audit
logging closer to the operations being performed.

- Simplify audit messages to "Moved to recycle bin from parent {parentId}"
- Add AuditMoveToRecycleBin helper methods to Content and Media services
- Add AuditMoveAsync helper methods to Element services
- Remove unused audit dependencies from RelateOnTrashNotificationHandler
- Add obsolete constructor bridge for backwards compatibility

* Refactor: Extract GetParentIdFromPath extension method

Add GetParentIdFromPath string extension to consolidate duplicate logic
for extracting parent ID from entity path strings. This replaces 5
instances of the same path parsing pattern across services and handlers.

- Add GetParentIdFromPath to StringExtensions.Parsing.cs
- Inline audit calls in ContentService, MediaService,
  ElementEditingService, and ElementContainerService
- Update RelateOnTrashNotificationHandler to use the new extension
- Add unit tests for the new extension method

* Refactor: Make CreateOriginalParentRelationOnTrash synchronous

Remove unnecessary async from CreateOriginalParentRelationOnTrash since
the method contains no async operations. Update handlers to return
Task.CompletedTask directly.
2026-02-04 14:54:50 +00:00
5fe5a0febf Elements: Implement validation for Element editing endpoints (#21562)
* Elements: Implement validation for Element editing endpoints

Move ValidateCulturesAndPropertiesAsync and GetCulturesToValidate from
ContentEditingService to ContentEditingServiceBase, enabling reuse in
ElementEditingService.

- Implement ValidateCreateAsync and ValidateUpdateAsync in ElementEditingService
- Update Element API controllers to return validation results properly
- Update all inheriting services (Media, Member, Blueprint) with new params

* Elements: Add validation tests for ElementEditingService

- Add tests for ValidateUpdateAsync and ValidateCreateAsync
- Cover invariant, culture variant, and permission-based validation scenarios

* Fix bad merge

* Removed unused fields

* Removed old editor UI

---------

Co-authored-by: kjac <kja@umbraco.dk>
2026-02-04 13:35:54 +00:00
Andy Butland d4ee843a01 Merge branch 'main' into v18/dev 2026-02-04 12:06:42 +01:00
Niels Lyngsø c48ef57a84 Merge branch 'main' into v18/dev 2026-02-04 09:20:08 +01:00
Andy Butland 0c3d1d7058 Merge branch 'main' into v18/dev 2026-02-03 17:14:16 +01:00
Andy Butland eda9857200 Merge branch 'main' into v18/dev 2026-02-03 09:37:26 +01:00
46b96f3811 Global Elements - take one (#21431)
* CRUD + folders + API

* Fix infinite recursion

* Distributed cache handling for Elements

* Publishing for Elements (incl. refactor)

* Fix bad file name

* Added "foldersOnly" option to the siblings endpoint

* Update src/Umbraco.Core/Models/UmbracoObjectTypes.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* API for publishing elements

* Published element cache (WIP)

* Fix delete at repo level

* Fixing up a little tests

* Element picker property editor

* Added tests to prove published element status

* Move scheduled content keys to base abstraction

* Add request caching for published element creation (similar to published document creation)

* Apply conditional appcache access to elements as well

* Fix test build errors

* Fix merge from main

* Fix merge

* Add cache invalidation on update (like content and media)

* Move element (incl. tests)

* Element copying

* Add items endpoint incl. variation info at item level

* Make the Element tree items look like Document tree items (with variations)

* Rename all things ElementType to DocumentType

* Move ElementRepository to the right place

* Fix auditing after merge (changes from #19357)

* Fix dates after merge (changes from #19822)

* Fix NPoco querying after merge (changes from #20184)

* Fix various build errors after merge

* Move containers

* Add migration to create element tables

* Re-implement #21105 at base class level

* Fix merge

* Add element tree recycle bin + move element to/from recycle bin

* Controllers for move to recycle bin + recycle bin root

* Support element containers in recycle bin (no controllers)

* Handle error cases for element moves and add more tests

* Do not allow creation of IPublishedElement for trashed elements

* Amend recycle bin controller output and add children controller

* Regenerate OpenApi.json with Element APIs

* Housekeeping: Organize element container service tests

* Fix bad housekeeping

* Add missing siblings controller for recycle bin

* Add "delete from recycle bin" and "empty recycle bin" operations (including API)

* Updated OpenApi.json to reflect new endpoints

* Added `CreateDate` to `ElementTreeItemResponseModel`

Marked `ElementRecycleBinItemResponseModel.DocumentTypeReferenceResponseModel` as nullable.

* Re-generated OpenAPI.json

* Add configuration endpoint for Elements

* Explicitly unpublish published elements when restoring from recycle bin.

* Elements: Remove invalid templateId from ElementVersionDto index definitions (#21384)

* Persistence: Remove invalid templateId from ElementVersionDto index definitions

The ElementVersionDto had index definitions that referenced templateId in
their IncludeColumns, but the ElementVersion table only has id and published
columns. This caused SQL Server clean installs to fail with error 1911:
"Column name 'templateId' does not exist in the target table or view."

This was likely a copy-paste error from DocumentVersionDto which does have
a templateId column.

* Ignore Cannot_Create_Element_Based_On_NonElement_ContentType for the time being

---------

Co-authored-by: kjac <kja@umbraco.dk>

* Fix the ordering of items in the tree

* It's 2026 now...

* Fix missing project structure

* Amend empty recycle bin

* Elements: Fix element recycle bin node insertion on SQL Server (#21390)

Enable IDENTITY_INSERT before inserting the element recycle bin node with an explicit ID, then disable it afterward. This fixes the migration failing on SQL Server with "Cannot insert explicit value for identity column" error.

* Fix count trashed children

* Moved newly added entity service tests to an isolated, per-test DB class so they do not interfere with the existing per-fixture DB tests

* Elements: Element start node permissions (#21375)

* Add Element start node support for Users and UserGroups

- Add StartElementId to UserGroup and element start nodes to User
- Add UserStartNodeFolderTreeControllerBase for tree filtering with folder support
- Update ElementTreeControllerBase to use start node filtering
- Add ElementTreeItemResponseModel.NoAccess property for "no access" items
- Add UserExtensions methods for element start node calculation
- Update User/UserGroup API models and factories
- Add database migration for startElementId column
- Add SectionAccessForElementTree authorization policy

Note: Granular element permissions deferred for future implementation

* Add element root access for default user groups on fresh install

Set StartElementId = -1 for Administrators, Writers, Editors, and
Translators user groups in DatabaseDataCreator, giving them element
root access on fresh installations (matching their content/media access).

* Add multi-type support to UserStartNodeEntitiesService

Added overloads to RootUserAccessEntities, ChildUserAccessEntities, and
SiblingUserAccessEntities that accept multiple UmbracoObjectTypes. This
enables querying for Elements and ElementContainers in a single call
rather than requiring separate queries for each type.

Also added GetAll and GetPagedChildren overloads to IEntityService and
IEntityRepository to support querying multiple object types efficiently
with a single database query.

* Add integration tests for Element start nodes with mixed hierarchy

Added UserStartNodeEntitiesServiceElementTests with a mixed hierarchy
structure containing both containers and elements at each level:
- Level 1: Containers (C1-C5) and Elements (E1-E3)
- Level 2: Child containers (C1-C1 through C1-C10) and Elements (C1-E1, C1-E2)
- Level 3: Leaf elements (C1-C1-E1 through C1-C1-E5)

This tests scenarios where containers and elements are siblings, ensuring
the access filtering works correctly for mixed-type queries.

Also refactored Content and Media tests to use a shared base class
(UserStartNodeEntitiesServiceTestsBase) to reduce code duplication.

* Add Library section for Elements

- Rename Constants.Applications.Elements to Library
- Add SectionAccessLibrary authorization policy
- Add library mapping to SectionMapper
- Grant Library section access to Administrators, Writers, and Editors on fresh install
- Update TreeAccessElements to use Library section

* Add Element tree controller authorization tests

Add integration tests for RootElementTreeController and
ChildrenElementTreeController to verify section-based
authorization works correctly for the Element tree endpoints.

* Fix ReadOnlyUserGroup not passing startElementId to constructor

The obsolete 13-parameter constructor was passing `null` instead of
the actual `startElementId` value to the next constructor, causing
user groups to appear to have no element start node access.

Also update UserFactory.ToReadOnlyGroup to pass the Description
parameter to the ReadOnlyUserGroup constructor.

* Add Element controller authorization tests

Add authorization tests for Element CRUD, Folder, RecycleBin, and Item
controllers to verify user group access permissions.

Tests cover Admin, Editor, Writer, SensitiveData, Translator, and
Unauthorized user groups for each controller endpoint.

* Re-generated OpenApi.json

* Fix Element start node handling to use ElementContainer object type

- Update UserStartNodeFolderTreeControllerBase to query both folder and
  item object types when filtering by user start nodes
- Fix UserGroupPresentationFactory to use ElementContainer instead of
  Element when resolving element start node IDs/keys

* Revert ByKeyElementController to use synchronous Task.FromResult

The method doesn't have any async operations, so async/await adds
unnecessary overhead.

* Fix UserPresentationFactory to use ElementContainer for element start nodes

Element start nodes reference ElementContainer (folders), not Element items.

* Add recycle bin start node access test for Element controllers

- Add WithStartElementId to UserGroupBuilder
- Add ElementRecycleBinControllerTestBase with shared test verifying
  users with non-root element start nodes cannot access recycle bin
- Update all Element recycle bin tests to use the new base class

* Fix UserGroupPresentationFactory and Element test section alias

- Use ElementContainer instead of Element for start node lookups in
  IReadOnlyUserGroup overload
- Use Constants.Applications.Library for Element test section alias

* Add obsolete User constructor overload for backward compatibility

- Add obsolete constructor without startElementIds parameter that delegates
  to the new constructor with an empty array
- Improve XML documentation for all User constructors

* Elements: Move NoAccess property to FolderTreeItemResponseModel base class

This allows both elements and folders to indicate access status in the tree.

* Elements: Add API versioning attributes to SiblingsElementTreeController

* Elements: Add integration tests for element tree start node permissions

Add tests to verify that users with element start node restrictions can only see
and access elements within their permitted hierarchy.

* Group test files

* Remove type check from GetAllPaths overload

---------

Co-authored-by: Kenn Jacobsen <kja@umbraco.dk>

* Elements: Add rollback (#21393)

* Services, repos and tests

* Endpoints for Elements versioning

* Add extra test to prove handling of pinned versions

* Renaming from PR review

* Update tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ElementVersionCleanupServiceTest.cs

Co-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com>

* More code clean-up after review

* Use correct deleting/deleted versions notifications

---------

Co-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com>

* Elements: Regenerate OpenApi.json

* Elements: Add default and granular permissions for Element controllers (#21385)

* Add Element start node support for Users and UserGroups

- Add StartElementId to UserGroup and element start nodes to User
- Add UserStartNodeFolderTreeControllerBase for tree filtering with folder support
- Update ElementTreeControllerBase to use start node filtering
- Add ElementTreeItemResponseModel.NoAccess property for "no access" items
- Add UserExtensions methods for element start node calculation
- Update User/UserGroup API models and factories
- Add database migration for startElementId column
- Add SectionAccessForElementTree authorization policy

Note: Granular element permissions deferred for future implementation

* Add element root access for default user groups on fresh install

Set StartElementId = -1 for Administrators, Writers, Editors, and
Translators user groups in DatabaseDataCreator, giving them element
root access on fresh installations (matching their content/media access).

* Add multi-type support to UserStartNodeEntitiesService

Added overloads to RootUserAccessEntities, ChildUserAccessEntities, and
SiblingUserAccessEntities that accept multiple UmbracoObjectTypes. This
enables querying for Elements and ElementContainers in a single call
rather than requiring separate queries for each type.

Also added GetAll and GetPagedChildren overloads to IEntityService and
IEntityRepository to support querying multiple object types efficiently
with a single database query.

* Add integration tests for Element start nodes with mixed hierarchy

Added UserStartNodeEntitiesServiceElementTests with a mixed hierarchy
structure containing both containers and elements at each level:
- Level 1: Containers (C1-C5) and Elements (E1-E3)
- Level 2: Child containers (C1-C1 through C1-C10) and Elements (C1-E1, C1-E2)
- Level 3: Leaf elements (C1-C1-E1 through C1-C1-E5)

This tests scenarios where containers and elements are siblings, ensuring
the access filtering works correctly for mixed-type queries.

Also refactored Content and Media tests to use a shared base class
(UserStartNodeEntitiesServiceTestsBase) to reduce code duplication.

* Add Library section for Elements

- Rename Constants.Applications.Elements to Library
- Add SectionAccessLibrary authorization policy
- Add library mapping to SectionMapper
- Grant Library section access to Administrators, Writers, and Editors on fresh install
- Update TreeAccessElements to use Library section

* Add Element tree controller authorization tests

Add integration tests for RootElementTreeController and
ChildrenElementTreeController to verify section-based
authorization works correctly for the Element tree endpoints.

* Fix ReadOnlyUserGroup not passing startElementId to constructor

The obsolete 13-parameter constructor was passing `null` instead of
the actual `startElementId` value to the next constructor, causing
user groups to appear to have no element start node access.

Also update UserFactory.ToReadOnlyGroup to pass the Description
parameter to the ReadOnlyUserGroup constructor.

* Add Element controller authorization tests

Add authorization tests for Element CRUD, Folder, RecycleBin, and Item
controllers to verify user group access permissions.

Tests cover Admin, Editor, Writer, SensitiveData, Translator, and
Unauthorized user groups for each controller endpoint.

* Re-generated OpenApi.json

* Fix Element start node handling to use ElementContainer object type

- Update UserStartNodeFolderTreeControllerBase to query both folder and
  item object types when filtering by user start nodes
- Fix UserGroupPresentationFactory to use ElementContainer instead of
  Element when resolving element start node IDs/keys

* Revert ByKeyElementController to use synchronous Task.FromResult

The method doesn't have any async operations, so async/await adds
unnecessary overhead.

* Fix UserPresentationFactory to use ElementContainer for element start nodes

Element start nodes reference ElementContainer (folders), not Element items.

* Add recycle bin start node access test for Element controllers

- Add WithStartElementId to UserGroupBuilder
- Add ElementRecycleBinControllerTestBase with shared test verifying
  users with non-root element start nodes cannot access recycle bin
- Update all Element recycle bin tests to use the new base class

* Fix UserGroupPresentationFactory and Element test section alias

- Use ElementContainer instead of Element for start node lookups in
  IReadOnlyUserGroup overload
- Use Constants.Applications.Library for Element test section alias

* Add obsolete User constructor overload for backward compatibility

- Add obsolete constructor without startElementIds parameter that delegates
  to the new constructor with an empty array
- Improve XML documentation for all User constructors

* Elements: Add granular permissions for Element controllers

Add Element-specific permission actions:
- ActionElementBrowse, ActionElementNew, ActionElementUpdate, ActionElementDelete
- ActionElementPublish, ActionElementUnpublish, ActionElementMove, ActionElementCopy

Add permission infrastructure:
- ElementPermissionResource for authorization checks
- ElementPermissionHandler and ElementPermissionRequirement
- ElementPermissionService and IElementPermissionService
- ElementPermissionAuthorizer and IElementPermissionAuthorizer
- ElementGranularPermission model
- ElementPermissionMapper for user group permissions

Update Element controllers with authorization:
- Add HandleRequest pattern via CreateElementControllerBase and UpdateElementControllerBase
- Pass cultures for Publish/Unpublish authorization
- Apply authorization checks to Element CRUD and publishing operations

* Elements: Add default element permissions to user groups

Add element action permissions for Admin, Editor, Writer, and Translator
user groups in DatabaseDataCreator, mirroring the document permission pattern.

* Elements: Add current user element permissions endpoint and fix folder authorization

- Add GetElementPermissionsCurrentUserController endpoint to get current user's element permissions
- Fix ElementPermissionService to authorize both Element and ElementContainer (folders)
- Add GetElementPermissionsAsync to IUserService/UserService
- Add ElementNodeNotFound to UserOperationStatus
- Add IEntityService.GetAll overloads for multiple object types

* Elements: Move NoAccess property to FolderTreeItemResponseModel base class

This allows both elements and folders to indicate access status in the tree.

* Elements: Add default implementation to IUserService.GetElementPermissionsAsync

Adds a default throwing implementation to avoid breaking existing IUserService implementations when this method is added.

* Elements: Add API versioning attributes to SiblingsElementTreeController

* Elements: Add integration tests for element tree start node permissions

Add tests to verify that users with element start node restrictions can only see
and access elements within their permitted hierarchy.

* Add granular permissions to element rollback

* Update src/Umbraco.Core/Actions/ActionElementCopy.cs

Co-authored-by: Kenn Jacobsen <kja@umbraco.dk>

* Elements: Use lowercase action aliases for consistency

Update all Element action aliases to lowercase to comply with the
IAction.Alias requirement for case-sensitive filesystems. Also rename
ActionElementNew alias from "elementNew" to "elementcreate" to match
the document action's "create" alias pattern.

* Elements: Refactor UserService permission methods to reduce duplication

Consolidate GetMediaPermissionsAsync, GetDocumentPermissionsAsync, and
GetElementPermissionsAsync into a single shared implementation via
a new private GetContentPermissionsAsync helper method.

---------

Co-authored-by: kjac <kja@umbraco.dk>

* Add element folder "item" endpoint

* Include "isTrashed" in folder response models

* Update TODOs

* Rollback a few unnecessarily breaking signature changes

* Use schema constants from #21327

* Elements: Add admin group element permissions during upgrade (#21452)

Grant the admin user group access to the element root node and all
element permissions when upgrading from a previous version. This
ensures parity with fresh installations where the admin group receives
these permissions by default.

* Elements: Fix Writer expected status codes in Element controller permission tests

Update WriterUserGroupAssertionModel to expect Forbidden for operations
that Writers don't have permission for, matching Document controller
behavior and the actual permissions assigned to the Writer group.

Changed from OK/Created to Forbidden:
- CopyElementControllerTests
- DeleteElementControllerTests
- MoveElementControllerTests
- MoveToRecycleBinElementControllerTests
- PublishElementControllerTests
- UnpublishElementControllerTests
- Folder/DeleteElementFolderControllerTests
- Folder/MoveElementFolderControllerTests
- Folder/MoveToRecycleBinElementFolderControllerTests
- RecycleBin/DeleteElementRecycleBinControllerTests
- RecycleBin/DeleteElementFolderRecycleBinControllerTests
- RecycleBin/EmptyElementRecycleBinControllerTests

* Elements: Fix duplicate column name in DocumentVersionDto index definition

The ForColumns parameter incorrectly specified PublishedColumnName twice
instead of IdColumnName and PublishedColumnName, causing SQL Server to
reject index creation with "duplicate column names" error on new installs.

* Add missing element mapper and allow deleting element types with active elements (#21483)

* Add missing element mapper and allow deleting element types with active elements

* Update src/Umbraco.Core/Services/ContentTypeService.cs

Co-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com>

* Update src/Umbraco.Core/Services/ContentTypeService.cs

Co-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com>

---------

Co-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com>

* Update src/Umbraco.Core/Cache/Refreshers/Implement/ElementCacheRefresher.cs

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Review comment: ReadOnlyUserGroup constructor

* Update comments in ElementEditingService

* Add Library section access to content, media, and member tree policies

* Elements: Add Elements access to data type, document type, and relation authorization policies (#21501)

Add Elements access to data type, document type, and relation authorization policies

* Amend merge from v18/dev

* Global Elements: Backoffice UI implementation (#21410)

* chore: generate new openapi types

* Added package/module for "Library"

* Added default dashboard for Library section

* [WIP] Adds "Elements" package module

Basics of the tree/menu.

* Adds entity-actions for Create and Reload

* Adds entity-action for Move To

* Adds collection workspace view

for root and folders

* Adds entity-action for Duplicate To

* "Reload Children" should only be for root & folders

* Reworked Library sidebar app

Replaced with Elements sidebar app
Removed the Library menu

* chore: generate new openapi types

* Added Item repository

* Added Reference repository

* Added Element Recycle Bin

Tree, menu, entity-actions, workspace (collection view)

* Adds "umb-element-tree-item" to identify the `isTrashed` state

* Re-added Library sidebar app

Removed Library dashboard (we'll figure it out later)

* Recycle Bin type tweaks

* [WIP] Element "Create" modal

* Reverted Element "Create" modal, to use create-options + picker

* chore: generate new openapi types

* Added Element Detail Repository

* [WIP] Element Workspace + Context

* Elements: Add workspace views for edit and info

Add edit and info workspace views to the Element workspace:
- Edit view using shared 'contentEditor' kind pattern
- Info view displaying state tag, dates, element type, and ID
- Menu structure context for tree navigation
- Split-view component for variant editing

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Elements: Add save action and trash state handling

- Add Save workspace action using UmbSubmitWorkspaceAction
- Add isTrashed property to UmbElementDetailModel
- Implement trash state change handling with read-only guard
- Add recycle bin event listeners for trash/restore actions

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Adds Workspace actions for Save, Publish, Scheduled Publish

* Adds Element Configuration repository

* Adds mock handle + data for Elements

* Adds Publish and Unpublish entity actions for Elements

Implements context menu actions for publishing and unpublishing elements
directly from the tree. Uses existing modals and publishing repository.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* package-lock.json update

* Adds bulk entity actions for Publish, Unpublish, Move and Trash

* Localization keys + code tweaks

* Adds reusable `emptyRecycleBin` `collectionAction` kind

* Adds `emptyRecycleBin` for Element Recycle Bin collection

* Element Recycle Bin refactoring

Working towards folder support

* Relations: exported entity-action types

* Restructured "Element Folder" code

* Restructured "Trash" entity-bulk-action code

* Adds `trashFolder` `entityAction` kind

* Adds "Trash" entity-action for Element Folders

* Tidy-up / restructuring

* [WIP] Element Picker property-editor UI

making use of an Elements property-data-source,
with Entity Picker.

* Renamed `UmbElementPropertyDatasetContext` to `UmbElementWorkspacePropertyDatasetContext`

to de-duplicate a class name clash with the underlying base class.

* Added "entity-data-picker" importmap

Exposing the "umb-input-entity-data" component

* Reworking the "Element Picker" property-editor UI

to reuse the Entity Picker internal input component

* Implemented "Element Item Data Resolver" helper

* chore: generate new openapi types

* Fixed up the mocks and types

with new Element start nodes and `noAccess` fields.

* Added UI for "Elements Start Nodes"

* Added "entity-data-picker" export to the Vite config

* Fixed Element Folder picker for "start nodes"

* Adds UI for Element's User Permissions

* Adds Element User Permission condition

Implemented the user permissions for entity actions, etc.

* Adds UI for Element's Granular Permissions

* Adds element-folder item repository

* Element Recycle Bin: implemented `isTrashed`

* Fixed mock folder data manager

* Adds move entity-action for element-folder

Implements the Move action for element folders using the
ElementService.putElementFolderByIdMove API endpoint.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Fix typos and element tag name mismatches in elements package

- Fix typo 'now' -> 'no' in user-permissions/types.ts
- Fix HTMLElementTagNameMap tag name to match @customElement decorator
- Fix typo 'TDOD' -> 'TODO' in element-detail.server.data-source.ts
- Fix missing 'u' prefix in element-picker tag name declaration

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Ignore local Claude settings in UI Client

* Updated workspace assign access,

to disable root access when start nodes are selected.

* Elements: Display trashed state in Element workspace info panel (#21542)

The state tag in the Element workspace info view was missing a case
for the TRASHED state, causing trashed Elements to incorrectly display
"Not created" instead of "Trashed".

* Elements: Fix folder link in recycle bin list view (#21543)

The trashed element name column always used the element workspace path
pattern, causing folders clicked in the recycle bin list view to show
"Not found". Now checks isFolder and uses the correct workspace path
pattern for folders vs elements.

* Elements: Add missing delete permission conditions to recycle bin actions (#21547)

The Empty Recycle Bin collection action and the folder delete entity
action were missing user permission conditions, making them visible
to users without delete permission.

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com>

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
Co-authored-by: leekelleher <leekelleher@gmail.com>
Co-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com>
Co-authored-by: Lee Kelleher <leekelleher@users.noreply.github.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 07:46:31 +01:00
Laura Neto 7d73588c99 Merge branch 'main' into v18/dev 2026-01-28 13:27:22 +01:00
kjac 40c91262e6 Merge branch 'main' into v18/dev 2026-01-27 10:32:50 +01:00
Niels Lyngsø 31f23204f3 Merge branch 'main' into v18/dev 2026-01-27 09:19:30 +01:00
e53b8bcc52 Variants Sorting: Sort by language name (fix #21408) (#21435)
* Sort at last by language name

* ensure document language picker is sorted as variant selector

* Update src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/shared/document-variant-language-picker.element.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/Umbraco.Web.UI.Client/src/packages/documents/documents/utils.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* refactor to avoid inline methods

* transform into a function

* revert config file commit

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-23 09:18:52 +01:00
Niels Lyngsø 25d3013949 Merge branch 'main' into v18/dev 2026-01-16 17:18:05 +01:00
Andy Butland 8ac46e99b7 Applied naming suggestions made in review of #21374. 2026-01-15 07:51:34 +01:00
Andy Butland 59cc153b84 Merge branch 'main' into v18/dev 2026-01-15 07:46:41 +01:00
29ecae7010 Entities: Prevent changing Key property on existing entities (#21374)
* Prevent setting of entity Key to a new value for already persisted entities.

* Handled file based entities that have a key dependent on their path, so need to be able to have the key changed on move.

* Fixed package data update of content type to resolve failing integration test.

---------

Co-authored-by: Kenn Jacobsen <kja@umbraco.dk>
2026-01-14 06:45:11 +00:00
Laura Neto 409f5072af Merge branch 'main' into v18/dev 2026-01-13 10:09:56 +01:00
Andy ButlandandGitHub 58a0b160b2 Umbraco Helper: Align GetDictionaryValue nullability with behaviour (#21372)
Align GetDictionaryValue nullability with behaviour (returns empty string when no dictionary item is found for the provided key).
2026-01-13 06:42:17 +01:00
Andy Butland c8ba79b2d1 Merge branch 'main' into v18/dev 2026-01-12 11:54:52 +01:00
ef1b48c992 Obsolete Code: Remove obsolete methods and constants relating to allowed application and start node claims (#20124)
* Delete GetStartContentNodes

* Delete GetStartMediaNodes

* Delete GetAllowedApplications

* Delete ClaimTypes

* Update protected recycle bin functionality to no longer used removed claim details.  Added unit tests to verify behaviour.

* Fixed failing unit tests now the number of claims included is reduced.
Addressed comments from code review.

* Minor code tidy.

* Expose claim necessary for retrieving the user key.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
2026-01-09 12:25:33 +00:00
Andy Butland e9819d6e57 Merge branch 'main' into v18/dev 2026-01-09 12:23:44 +01:00
Laura Neto 480a208655 Merge branch 'main' into v18/dev
# Conflicts:
#	src/Umbraco.Web.UI.Client/package-lock.json
#	src/Umbraco.Web.UI.Client/package.json
#	version.json
2026-01-06 14:52:30 +01:00
Laura Neto 179f2e709d Merge branch 'main' into v18/dev 2025-12-16 15:44:08 +01:00
Laura Neto 90c09b1bf1 Merge branch 'main' into v18/dev
# Conflicts:
#	tests/Umbraco.Tests.Integration/CompatibilitySuppressions.xml
2025-12-11 10:17:18 +01:00
Laura Neto e7719c2458 Cleanup compatibility suppressions 2025-12-04 11:15:19 +01:00
Laura Neto fbce5882c6 Set up new v18 branch 2025-12-04 11:03:26 +01:00
1702 changed files with 71062 additions and 25324 deletions
+2 -2
View File
@@ -40,8 +40,8 @@
<!-- Package Validation -->
<PropertyGroup>
<GenerateCompatibilitySuppressionFile>false</GenerateCompatibilitySuppressionFile>
<EnablePackageValidation>true</EnablePackageValidation>
<PackageValidationBaselineVersion>17.0.0</PackageValidationBaselineVersion>
<EnablePackageValidation>false</EnablePackageValidation> <!-- TODO (V18): Set to true once this version is released. -->
<PackageValidationBaselineVersion>18.0.0</PackageValidationBaselineVersion>
<EnableStrictModeForCompatibleFrameworksInPackage>true</EnableStrictModeForCompatibleFrameworksInPackage>
<EnableStrictModeForCompatibleTfms>true</EnableStrictModeForCompatibleTfms>
</PropertyGroup>
+8 -6
View File
@@ -8,13 +8,15 @@
<ItemGroup>
<GlobalPackageReference Include="Nerdbank.GitVersioning" Version="3.9.50" />
<GlobalPackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
<GlobalPackageReference Include="Umbraco.Code" Version="2.4.0" />
<!-- TODO (V18): Bump Umbraco.Code to 3.0.0 stable before release of 18.0.0 -->
<GlobalPackageReference Include="Umbraco.Code" Version="3.0.0-beta" />
<GlobalPackageReference Include="Umbraco.GitVersioning.Extensions" Version="0.2.0" />
</ItemGroup>
<!-- Microsoft packages -->
<ItemGroup>
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="10.0.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="5.0.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="5.0.0" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="10.0.4" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.4" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.4" />
@@ -51,7 +53,7 @@
<PackageVersion Include="JsonPatch.Net" Version="3.3.0" />
<PackageVersion Include="K4os.Compression.LZ4" Version="1.3.8" />
<PackageVersion Include="MailKit" Version="4.15.1" />
<PackageVersion Include="Markdig" Version="0.45.0" />
<PackageVersion Include="Markdig" Version="1.1.1" />
<PackageVersion Include="Markdown" Version="2.2.1" />
<PackageVersion Include="MessagePack" Version="3.1.4" />
<PackageVersion Include="MiniProfiler.AspNetCore.Mvc" Version="4.5.4" />
@@ -63,14 +65,14 @@
<PackageVersion Include="OpenIddict.AspNetCore" Version="7.2.0" />
<PackageVersion Include="OpenIddict.EntityFrameworkCore" Version="7.2.0" />
<PackageVersion Include="Serilog" Version="4.3.1" />
<PackageVersion Include="Serilog.AspNetCore" Version="9.0.0" />
<PackageVersion Include="Serilog.AspNetCore" Version="10.0.0" />
<PackageVersion Include="Serilog.Enrichers.Process" Version="3.0.0" />
<PackageVersion Include="Serilog.Enrichers.Thread" Version="4.0.0" />
<PackageVersion Include="Serilog.Expressions" Version="5.0.0" />
<PackageVersion Include="Serilog.Extensions.Hosting" Version="9.0.0" />
<PackageVersion Include="Serilog.Extensions.Hosting" Version="10.0.0" />
<PackageVersion Include="Serilog.Formatting.Compact" Version="3.0.0" />
<PackageVersion Include="Serilog.Formatting.Compact.Reader" Version="4.0.0" />
<PackageVersion Include="Serilog.Settings.Configuration" Version="9.0.0" />
<PackageVersion Include="Serilog.Settings.Configuration" Version="10.0.0" />
<PackageVersion Include="Serilog.Sinks.Async" Version="2.1.0" />
<PackageVersion Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageVersion Include="Serilog.Sinks.Map" Version="2.0.0" />
+3 -3
View File
@@ -4,11 +4,11 @@ pr: none
trigger: none
schedules:
- cron: '0 3 * * *'
displayName: Daily 3AM build (main)
- cron: '0 6 * * *'
displayName: Daily 6AM build (v18/dev)
branches:
include:
- main
- v18/dev
parameters:
- name: skipIntegrationTests
@@ -10,15 +10,6 @@ public class OperationIdSelector : IOperationIdSelector
{
private readonly IEnumerable<IOperationIdHandler> _operationIdHandlers;
/// <summary>
/// Initializes a new instance of the <see cref="OperationIdSelector"/> class.
/// </summary>
[Obsolete("Use non-obsolete constructor. Scheduled for removal in Umbraco 18.")]
public OperationIdSelector()
: this(Enumerable.Empty<IOperationIdHandler>())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="OperationIdSelector"/> class.
/// </summary>
@@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using OpenIddict.Server;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Security;
using Umbraco.Extensions;
@@ -32,12 +33,12 @@ public class ExposeBackOfficeAuthenticationOpenIddictServerEventsHandler : IOpen
// These are the type identifiers for the claims required by the principal
// for the custom authentication scheme.
// We make available the ID, user name and allowed applications (sections) claims.
// We make available the ID and user name claims, plus the claim necessary for parsing the user key.
_claimTypes =
[
backOfficeIdentityOptions.Value.ClaimsIdentity.UserIdClaimType,
backOfficeIdentityOptions.Value.ClaimsIdentity.UserNameClaimType,
Core.Constants.Security.AllowedApplicationsClaimType,
Constants.Security.OpenIdDictSubClaimType
];
}
@@ -65,7 +65,6 @@ public static class UmbracoBuilderExtensions
ServiceLifetime.Scoped);
builder.Services.AddSingleton<IRequestCultureService, RequestCultureService>();
builder.Services.AddSingleton<IRequestSegmmentService, RequestSegmentService>();
builder.Services.AddSingleton<IRequestSegmentService, RequestSegmentService>();
builder.Services.AddSingleton<IRequestRoutingService, RequestRoutingService>();
builder.Services.AddSingleton<IRequestRedirectService, RequestRedirectService>();
@@ -3,7 +3,7 @@ using Umbraco.Cms.Core.DeliveryApi;
namespace Umbraco.Cms.Api.Delivery.Services;
internal sealed class RequestSegmentService : RequestHeaderHandler, IRequestSegmentService, IRequestSegmmentService
internal sealed class RequestSegmentService : RequestHeaderHandler, IRequestSegmentService
{
public RequestSegmentService(IHttpContextAccessor httpContextAccessor)
: base(httpContextAccessor)
@@ -1,7 +1,9 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.Document;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Models.ContentEditing.Validation;
using Umbraco.Cms.Core.Models.ContentPublishing;
using Umbraco.Cms.Core.PropertyEditors.Validation;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Extensions;
@@ -13,6 +15,8 @@ namespace Umbraco.Cms.Api.Management.Controllers.Content;
/// </summary>
public abstract class ContentControllerBase : ManagementApiControllerBase
{
protected abstract string EntityName { get; }
protected IActionResult ContentEditingOperationStatusResult(ContentEditingOperationStatus status)
=> OperationStatusResult(status, problemDetailsBuilder => status switch
{
@@ -90,12 +94,12 @@ public abstract class ContentControllerBase : ManagementApiControllerBase
.WithDetail("The supplied name is already in use for the same content type.")
.Build()),
ContentEditingOperationStatus.CannotDeleteWhenReferenced => BadRequest(problemDetailsBuilder
.WithTitle("Cannot delete a referenced content item")
.WithDetail("Cannot delete a referenced content item, while the setting ContentSettings.DisableDeleteWhenReferenced is enabled.")
.WithTitle($"Cannot delete a referenced {EntityName}")
.WithDetail($"Cannot delete a referenced {EntityName}, while the setting ContentSettings.DisableDeleteWhenReferenced is enabled.")
.Build()),
ContentEditingOperationStatus.CannotMoveToRecycleBinWhenReferenced => BadRequest(problemDetailsBuilder
.WithTitle("Cannot move a referenced content item to the recycle bin")
.WithDetail("Cannot move a referenced content item to the recycle bin, while the setting ContentSettings.DisableDeleteWhenReferenced is enabled.")
.WithTitle($"Cannot move a referenced {EntityName} to the recycle bin")
.WithDetail($"Cannot move a referenced {EntityName} to the recycle bin, while the setting ContentSettings.DisableUnpublishWhenReferenced is enabled.")
.Build()),
ContentEditingOperationStatus.Unknown => StatusCode(
StatusCodes.Status500InternalServerError,
@@ -118,6 +122,122 @@ public abstract class ContentControllerBase : ManagementApiControllerBase
.Build()),
});
protected IActionResult ContentPublishingOperationStatusResult(
ContentPublishingOperationStatus status,
IEnumerable<string>? invalidPropertyAliases = null,
IEnumerable<ContentPublishingBranchItemResult>? failedBranchItems = null)
=> OperationStatusResult(
status,
problemDetailsBuilder => status switch
{
ContentPublishingOperationStatus.ContentNotFound => NotFound(problemDetailsBuilder
.WithTitle($"The requested {EntityName} could not be found")
.Build()),
ContentPublishingOperationStatus.CancelledByEvent => BadRequest(problemDetailsBuilder
.WithTitle("Publish cancelled by event")
.WithDetail("The publish operation was cancelled by an event.")
.Build()),
ContentPublishingOperationStatus.ContentInvalid => BadRequest(problemDetailsBuilder
.WithTitle($"Invalid {EntityName}")
.WithDetail($"The specified {EntityName} had an invalid configuration.")
.WithExtension("invalidProperties", invalidPropertyAliases ?? Enumerable.Empty<string>())
.Build()),
ContentPublishingOperationStatus.NothingToPublish => BadRequest(problemDetailsBuilder
.WithTitle("Nothing to publish")
.WithDetail("None of the specified cultures needed publishing.")
.Build()),
ContentPublishingOperationStatus.MandatoryCultureMissing => BadRequest(problemDetailsBuilder
.WithTitle("Mandatory culture missing")
.WithDetail("Must include all mandatory cultures when publishing.")
.Build()),
ContentPublishingOperationStatus.HasExpired => BadRequest(problemDetailsBuilder
.WithTitle($"{EntityName.ToFirstUpperInvariant()} expired")
.WithDetail($"Could not publish the {EntityName} because it was expired.")
.Build()),
ContentPublishingOperationStatus.CultureHasExpired => BadRequest(problemDetailsBuilder
.WithTitle($"{EntityName.ToFirstUpperInvariant()} culture expired")
.WithDetail($"Could not publish the {EntityName} because some of the specified cultures were expired.")
.Build()),
ContentPublishingOperationStatus.AwaitingRelease => BadRequest(problemDetailsBuilder
.WithTitle($"{EntityName.ToFirstUpperInvariant()} awaiting release")
.WithDetail($"Could not publish the {EntityName} because it was awaiting release.")
.Build()),
ContentPublishingOperationStatus.CultureAwaitingRelease => BadRequest(problemDetailsBuilder
.WithTitle($"{EntityName.ToFirstUpperInvariant()} culture awaiting release")
.WithDetail(
$"Could not publish the {EntityName} because some of the specified cultures were awaiting release.")
.Build()),
ContentPublishingOperationStatus.InTrash => BadRequest(problemDetailsBuilder
.WithTitle($"{EntityName.ToFirstUpperInvariant()} in the recycle bin")
.WithDetail($"Could not publish the {EntityName} because it was in the recycle bin.")
.Build()),
ContentPublishingOperationStatus.PathNotPublished => BadRequest(problemDetailsBuilder
.WithTitle("Parent not published")
.WithDetail($"Could not publish the {EntityName} because its parent was not published.")
.Build()),
ContentPublishingOperationStatus.InvalidCulture => BadRequest(problemDetailsBuilder
.WithTitle("Invalid cultures specified")
.WithDetail("A specified culture is not valid for the operation.")
.Build()),
ContentPublishingOperationStatus.CultureMissing => BadRequest(problemDetailsBuilder
.WithTitle("Culture missing")
.WithDetail("A culture needs to be specified to execute the operation.")
.Build()),
ContentPublishingOperationStatus.CannotPublishInvariantWhenVariant => BadRequest(problemDetailsBuilder
.WithTitle("Cannot publish invariant when variant")
.WithDetail($"Cannot publish invariant culture when the {EntityName} varies by culture.")
.Build()),
ContentPublishingOperationStatus.CannotPublishVariantWhenNotVariant => BadRequest(problemDetailsBuilder
.WithTitle("Cannot publish variant when not variant.")
.WithDetail($"Cannot publish a given culture when the {EntityName} is invariant.")
.Build()),
ContentPublishingOperationStatus.ConcurrencyViolation => BadRequest(problemDetailsBuilder
.WithTitle("Concurrency violation detected")
.WithDetail("An attempt was made to publish a version older than the latest version.")
.Build()),
ContentPublishingOperationStatus.UnsavedChanges => BadRequest(problemDetailsBuilder
.WithTitle("Unsaved changes")
.WithDetail(
$"Could not publish the {EntityName} because it had unsaved changes. Make sure to save all changes before attempting a publish.")
.Build()),
ContentPublishingOperationStatus.UnpublishTimeNeedsToBeAfterPublishTime => BadRequest(problemDetailsBuilder
.WithTitle("Unpublish time needs to be after the publish time")
.WithDetail(
"Cannot handle an unpublish time that is not after the specified publish time.")
.Build()),
ContentPublishingOperationStatus.PublishTimeNeedsToBeInFuture => BadRequest(problemDetailsBuilder
.WithTitle("Publish time needs to be higher than the current time")
.WithDetail(
"Cannot handle a publish time that is not after the current server time.")
.Build()),
ContentPublishingOperationStatus.UpublishTimeNeedsToBeInFuture => BadRequest(problemDetailsBuilder
.WithTitle("Unpublish time needs to be higher than the current time")
.WithDetail(
"Cannot handle an unpublish time that is not after the current server time.")
.Build()),
ContentPublishingOperationStatus.CannotUnpublishWhenReferenced => BadRequest(problemDetailsBuilder
.WithTitle($"Cannot unpublish {EntityName} when it's referenced somewhere else.")
.WithDetail(
$"Cannot unpublish a referenced {EntityName}, while the setting ContentSettings.DisableUnpublishWhenReferenced is enabled.")
.Build()),
ContentPublishingOperationStatus.FailedBranch => BadRequest(problemDetailsBuilder
.WithTitle("Failed branch operation")
.WithDetail("One or more items in the branch could not complete the operation.")
.WithExtension("failedBranchItems", failedBranchItems?.Select(item => new DocumentPublishBranchItemResult { Id = item.Key, OperationStatus = item.OperationStatus }) ?? [])
.Build()),
ContentPublishingOperationStatus.Failed => BadRequest(
problemDetailsBuilder
.WithTitle("Publish or unpublish failed")
.WithDetail(
"An unspecified error occurred while (un)publishing. Please check the logs for additional information.")
.Build()),
ContentPublishingOperationStatus.TaskResultNotFound => NotFound(problemDetailsBuilder
.WithTitle("The result of the submitted task could not be found")
.Build()),
_ => StatusCode(StatusCodes.Status500InternalServerError, "Unknown content operation status."),
});
protected IActionResult ContentEditingOperationStatusResult<TContentModelBase, TValueModel, TVariantModel>(
ContentEditingOperationStatus status,
TContentModelBase requestModel,
@@ -15,7 +15,7 @@ namespace Umbraco.Cms.Api.Management.Controllers.DataType;
/// </summary>
[VersionedApiBackOfficeRoute(Constants.UdiEntityType.DataType)]
[ApiExplorerSettings(GroupName = "Data Type")]
[Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentsOrMediaOrMembersOrContentTypes)]
[Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentsOrElementsOrMediaOrMembersOrContentTypes)]
public abstract class DataTypeControllerBase : ManagementApiControllerBase
{
protected IActionResult DataTypeOperationStatusResult(DataTypeOperationStatus status) =>
@@ -14,24 +14,6 @@ namespace Umbraco.Cms.Api.Management.Controllers.DataType.Tree;
[ApiVersion("1.0")]
public class AncestorsDataTypeTreeController : DataTypeTreeControllerBase
{
/// <summary>
/// Initializes a new instance of the <see cref="AncestorsDataTypeTreeController"/> class, which provides API endpoints for retrieving ancestor data types in the tree structure.
/// </summary>
/// <param name="entityService">Service used for entity operations within the API.</param>
/// <param name="dataTypeService">Service used for data type management and retrieval.</param>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public AncestorsDataTypeTreeController(IEntityService entityService, IDataTypeService dataTypeService)
: base(entityService, dataTypeService)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="AncestorsDataTypeTreeController"/> class, which manages operations related to ancestor data type trees in the Umbraco CMS.
/// </summary>
/// <param name="entityService">Service used for entity-related operations.</param>
/// <param name="flagProviders">A collection of providers that supply flags for tree nodes.</param>
/// <param name="dataTypeService">Service used for data type management operations.</param>
[ActivatorUtilitiesConstructor]
public AncestorsDataTypeTreeController(IEntityService entityService, FlagProviderCollection flagProviders, IDataTypeService dataTypeService)
: base(entityService, flagProviders, dataTypeService)
{
@@ -15,24 +15,6 @@ namespace Umbraco.Cms.Api.Management.Controllers.DataType.Tree;
[ApiVersion("1.0")]
public class ChildrenDataTypeTreeController : DataTypeTreeControllerBase
{
/// <summary>
/// Initializes a new instance of the <see cref="ChildrenDataTypeTreeController"/> class.
/// </summary>
/// <param name="entityService">Service used for managing and retrieving entities within the system.</param>
/// <param name="dataTypeService">Service used for managing and retrieving data types.</param>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public ChildrenDataTypeTreeController(IEntityService entityService, IDataTypeService dataTypeService)
: base(entityService, dataTypeService)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ChildrenDataTypeTreeController"/> class.
/// </summary>
/// <param name="entityService">Service used for managing and retrieving entities within the system.</param>
/// <param name="flagProviders">A collection of providers that supply additional flags or metadata for entities.</param>
/// <param name="dataTypeService">Service responsible for operations related to data types.</param>
[ActivatorUtilitiesConstructor]
public ChildrenDataTypeTreeController(IEntityService entityService, FlagProviderCollection flagProviders, IDataTypeService dataTypeService)
: base(entityService, flagProviders, dataTypeService)
{
@@ -25,26 +25,6 @@ public class DataTypeTreeControllerBase : FolderTreeControllerBase<DataTypeTreeI
{
private readonly IDataTypeService _dataTypeService;
/// <summary>
/// Initializes a new instance of the <see cref="DataTypeTreeControllerBase"/> class.
/// </summary>
/// <param name="entityService">Service for managing Umbraco entities.</param>
/// <param name="dataTypeService">Service for managing data types within Umbraco.</param>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public DataTypeTreeControllerBase(IEntityService entityService, IDataTypeService dataTypeService)
: this(
entityService,
StaticServiceProvider.Instance.GetRequiredService<FlagProviderCollection>(),
dataTypeService)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DataTypeTreeControllerBase"/> class with the specified services.
/// </summary>
/// <param name="entityService">Service used for entity operations within the data type tree.</param>
/// <param name="flagProviders">A collection of providers that supply flags for entities.</param>
/// <param name="dataTypeService">Service used for managing data types.</param>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 19.")]
public DataTypeTreeControllerBase(IEntityService entityService, FlagProviderCollection flagProviders, IDataTypeService dataTypeService)
: this(
@@ -80,17 +60,17 @@ public class DataTypeTreeControllerBase : FolderTreeControllerBase<DataTypeTreeI
}
}
protected override DataTypeTreeItemResponseModel[] MapTreeItemViewModels(Guid? parentId, IEntitySlim[] entities)
protected override async Task<DataTypeTreeItemResponseModel[]> MapTreeItemViewModelsAsync(Guid? parentId, IEntitySlim[] entities)
{
Dictionary<int, IDataType> dataTypes = entities.Any()
? _dataTypeService
.GetAllAsync(entities.Select(entity => entity.Key).ToArray()).GetAwaiter().GetResult()
? (await _dataTypeService
.GetAllAsync(entities.Select(entity => entity.Key).ToArray()))
.ToDictionary(contentType => contentType.Id)
: new Dictionary<int, IDataType>();
return entities.Select(entity =>
IEnumerable<Task<DataTypeTreeItemResponseModel>> tasks = entities.Select(async entity =>
{
DataTypeTreeItemResponseModel responseModel = MapTreeItemViewModel(parentId, entity);
DataTypeTreeItemResponseModel responseModel = await MapTreeItemViewModelAsync(parentId, entity);
if (dataTypes.TryGetValue(entity.Id, out IDataType? dataType))
{
responseModel.EditorUiAlias = dataType.EditorUiAlias;
@@ -98,6 +78,8 @@ public class DataTypeTreeControllerBase : FolderTreeControllerBase<DataTypeTreeI
}
return responseModel;
}).ToArray();
});
return await Task.WhenAll(tasks);
}
}
@@ -15,24 +15,6 @@ namespace Umbraco.Cms.Api.Management.Controllers.DataType.Tree;
[ApiVersion("1.0")]
public class RootDataTypeTreeController : DataTypeTreeControllerBase
{
/// <summary>
/// Initializes a new instance of the <see cref="RootDataTypeTreeController"/> class.
/// </summary>
/// <param name="entityService">Service used for managing and retrieving entities within Umbraco.</param>
/// <param name="dataTypeService">Service used for managing data types in Umbraco.</param>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public RootDataTypeTreeController(IEntityService entityService, IDataTypeService dataTypeService)
: base(entityService, dataTypeService)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RootDataTypeTreeController"/> class, which manages the root of the data type tree in the Umbraco management API.
/// </summary>
/// <param name="entityService">Service used for entity operations within the tree.</param>
/// <param name="flagProviders">A collection of providers that supply flags for tree nodes.</param>
/// <param name="dataTypeService">Service used for data type management and retrieval.</param>
[ActivatorUtilitiesConstructor]
public RootDataTypeTreeController(IEntityService entityService, FlagProviderCollection flagProviders, IDataTypeService dataTypeService)
: base(entityService, flagProviders, dataTypeService)
{
@@ -13,24 +13,6 @@ namespace Umbraco.Cms.Api.Management.Controllers.DataType.Tree;
/// </summary>
public class SiblingsDataTypeTreeController : DataTypeTreeControllerBase
{
/// <summary>
/// Initializes a new instance of the <see cref="SiblingsDataTypeTreeController"/> class.
/// </summary>
/// <param name="entityService">Service used for managing and retrieving entities within Umbraco.</param>
/// <param name="dataTypeService">Service used for managing data types in Umbraco.</param>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public SiblingsDataTypeTreeController(IEntityService entityService, IDataTypeService dataTypeService)
: base(entityService, dataTypeService)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SiblingsDataTypeTreeController"/> class, which manages operations related to sibling data type trees in the Umbraco CMS.
/// </summary>
/// <param name="entityService">Service used for entity operations within the CMS.</param>
/// <param name="flagProviders">A collection of providers that supply flags for tree nodes.</param>
/// <param name="dataTypeService">Service used for managing data types.</param>
[ActivatorUtilitiesConstructor]
public SiblingsDataTypeTreeController(IEntityService entityService, FlagProviderCollection flagProviders, IDataTypeService dataTypeService)
: base(entityService, flagProviders, dataTypeService)
{
@@ -14,24 +14,6 @@ namespace Umbraco.Cms.Api.Management.Controllers.Dictionary.Tree;
[ApiVersion("1.0")]
public class AncestorsDictionaryTreeController : DictionaryTreeControllerBase
{
/// <summary>
/// Initializes a new instance of the <see cref="AncestorsDictionaryTreeController"/> class.
/// </summary>
/// <param name="entityService">Service used for managing and retrieving entities within Umbraco.</param>
/// <param name="dictionaryItemService">Service used for managing dictionary items in the Umbraco dictionary tree.</param>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public AncestorsDictionaryTreeController(IEntityService entityService, IDictionaryItemService dictionaryItemService)
: base(entityService, dictionaryItemService)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="AncestorsDictionaryTreeController"/> class, which handles operations related to retrieving ancestor dictionary tree items.
/// </summary>
/// <param name="entityService">The service used for entity operations.</param>
/// <param name="flagProviders">A collection of providers for entity flags.</param>
/// <param name="dictionaryItemService">The service used for dictionary item operations.</param>
[ActivatorUtilitiesConstructor]
public AncestorsDictionaryTreeController(IEntityService entityService, FlagProviderCollection flagProviders, IDictionaryItemService dictionaryItemService)
: base(entityService, flagProviders, dictionaryItemService)
{
@@ -16,24 +16,6 @@ namespace Umbraco.Cms.Api.Management.Controllers.Dictionary.Tree;
[ApiVersion("1.0")]
public class ChildrenDictionaryTreeController : DictionaryTreeControllerBase
{
/// <summary>
/// Initializes a new instance of the <see cref="ChildrenDictionaryTreeController"/> class.
/// </summary>
/// <param name="entityService">Service used for managing and retrieving entities within the system.</param>
/// <param name="dictionaryItemService">Service used for managing dictionary items.</param>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public ChildrenDictionaryTreeController(IEntityService entityService, IDictionaryItemService dictionaryItemService)
: base(entityService, dictionaryItemService)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ChildrenDictionaryTreeController"/> class.
/// </summary>
/// <param name="entityService">Service for managing and retrieving entities within Umbraco.</param>
/// <param name="flagProviders">A collection of providers that supply additional flags or metadata for entities.</param>
/// <param name="dictionaryItemService">Service for managing dictionary items used for localization.</param>
[ActivatorUtilitiesConstructor]
public ChildrenDictionaryTreeController(IEntityService entityService, FlagProviderCollection flagProviders, IDictionaryItemService dictionaryItemService)
: base(entityService, flagProviders, dictionaryItemService)
{
@@ -1,13 +1,11 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Management.Controllers.Tree;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Api.Management.Services.Flags;
using Umbraco.Cms.Api.Management.ViewModels;
using Umbraco.Cms.Api.Management.ViewModels.Tree;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Web.Common.Authorization;
@@ -25,26 +23,6 @@ namespace Umbraco.Cms.Api.Management.Controllers.Dictionary.Tree;
// tree controller base. We'll keep it though, in the hope that we can mend EntityService.
public class DictionaryTreeControllerBase : NamedEntityTreeControllerBase<NamedEntityTreeItemResponseModel>
{
/// <summary>
/// Initializes a new instance of the <see cref="DictionaryTreeControllerBase"/> class.
/// </summary>
/// <param name="entityService">Service used for managing and retrieving entities within the Umbraco system.</param>
/// <param name="dictionaryItemService">Service used for managing and retrieving dictionary items for localization.</param>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public DictionaryTreeControllerBase(IEntityService entityService, IDictionaryItemService dictionaryItemService)
: this(
entityService,
StaticServiceProvider.Instance.GetRequiredService<FlagProviderCollection>(),
dictionaryItemService)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DictionaryTreeControllerBase"/> class.
/// </summary>
/// <param name="entityService">Service for managing entities within the system.</param>
/// <param name="flagProviders">A collection of providers for entity flags.</param>
/// <param name="dictionaryItemService">Service for managing dictionary items.</param>
public DictionaryTreeControllerBase(
IEntityService entityService,
FlagProviderCollection flagProviders,
@@ -5,6 +5,7 @@ using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.Services.Flags;
using Umbraco.Cms.Api.Management.ViewModels.Tree;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
@@ -16,13 +17,12 @@ namespace Umbraco.Cms.Api.Management.Controllers.Dictionary.Tree;
[ApiVersion("1.0")]
public class RootDictionaryTreeController : DictionaryTreeControllerBase
{
/// <summary>
/// Initializes a new instance of the <see cref="RootDictionaryTreeController"/> class.
/// </summary>
/// <param name="entityService">Service used for entity operations within the dictionary tree.</param>
/// <param name="dictionaryItemService">Service used for managing dictionary items.</param>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 20.")]
public RootDictionaryTreeController(IEntityService entityService, IDictionaryItemService dictionaryItemService)
: base(entityService, dictionaryItemService)
: this(
entityService,
StaticServiceProvider.Instance.GetRequiredService<FlagProviderCollection>(),
dictionaryItemService)
{
}
@@ -1,13 +1,11 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.Services.Flags;
using Umbraco.Cms.Api.Management.ViewModels.Document.Collection;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Security;
@@ -26,16 +24,6 @@ public class ByKeyDocumentCollectionController : DocumentCollectionControllerBas
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
private readonly IDocumentCollectionPresentationFactory _documentCollectionPresentationFactory;
/// <summary>
/// Initializes a new instance of the <see cref="Umbraco.Cms.Api.Management.Controllers.Document.Collection.ByKeyDocumentCollectionController"/> class,
/// which handles document collection operations by document key.
/// </summary>
/// <param name="contentListViewService">Service for retrieving and managing content list views.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context.</param>
/// <param name="mapper">The Umbraco object mapper used for mapping between models.</param>
/// <param name="documentCollectionPresentationFactory">Factory for creating document collection presentation models.</param>
/// <param name="flagProviders">A collection of providers for document collection flags.</param>
[ActivatorUtilitiesConstructor]
public ByKeyDocumentCollectionController(
IContentListViewService contentListViewService,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
@@ -49,28 +37,6 @@ public class ByKeyDocumentCollectionController : DocumentCollectionControllerBas
_documentCollectionPresentationFactory = documentCollectionPresentationFactory;
}
/// <summary>
/// Initializes a new instance of the <see cref="Umbraco.Cms.Api.Management.Controllers.Document.Collection.ByKeyDocumentCollectionController"/> class.
/// </summary>
/// <param name="contentListViewService">Service for managing content list views.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security operations.</param>
/// <param name="mapper">Maps Umbraco objects to API models.</param>
/// <param name="documentCollectionPresentationFactory">Factory for creating document collection presentation models.</param>
[Obsolete("Please use the constructor with all parameters. Scheduled for removal in Umbraco 18.")]
public ByKeyDocumentCollectionController(
IContentListViewService contentListViewService,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IUmbracoMapper mapper,
IDocumentCollectionPresentationFactory documentCollectionPresentationFactory)
: this(
contentListViewService,
backOfficeSecurityAccessor,
mapper,
documentCollectionPresentationFactory,
StaticServiceProvider.Instance.GetRequiredService<FlagProviderCollection>())
{
}
/// <summary>
/// Retrieves a paged collection of documents identified by the provided unique identifier.
/// </summary>
@@ -21,6 +21,8 @@ namespace Umbraco.Cms.Api.Management.Controllers.Document;
[Authorize(Policy = AuthorizationPolicies.TreeAccessDocuments)]
public abstract class DocumentControllerBase : ContentControllerBase
{
protected override string EntityName => "document";
protected IActionResult DocumentNotFound()
=> OperationStatusResult(ContentEditingOperationStatus.NotFound, problemDetailsBuilder
=> NotFound(problemDetailsBuilder
@@ -38,118 +40,7 @@ public abstract class DocumentControllerBase : ContentControllerBase
ContentPublishingOperationStatus status,
IEnumerable<string>? invalidPropertyAliases = null,
IEnumerable<ContentPublishingBranchItemResult>? failedBranchItems = null)
=> OperationStatusResult(status, problemDetailsBuilder => status switch
{
ContentPublishingOperationStatus.ContentNotFound => NotFound(problemDetailsBuilder
.WithTitle("The requested document could not be found")
.Build()),
ContentPublishingOperationStatus.CancelledByEvent => BadRequest(problemDetailsBuilder
.WithTitle("Publish cancelled by event")
.WithDetail("The publish operation was cancelled by an event.")
.Build()),
ContentPublishingOperationStatus.ContentInvalid => BadRequest(problemDetailsBuilder
.WithTitle("Invalid document")
.WithDetail("The specified document had an invalid configuration.")
.WithExtension("invalidProperties", invalidPropertyAliases ?? Enumerable.Empty<string>())
.Build()),
ContentPublishingOperationStatus.NothingToPublish => BadRequest(problemDetailsBuilder
.WithTitle("Nothing to publish")
.WithDetail("None of the specified cultures needed publishing.")
.Build()),
ContentPublishingOperationStatus.MandatoryCultureMissing => BadRequest(problemDetailsBuilder
.WithTitle("Mandatory culture missing")
.WithDetail("Must include all mandatory cultures when publishing.")
.Build()),
ContentPublishingOperationStatus.HasExpired => BadRequest(problemDetailsBuilder
.WithTitle("Document expired")
.WithDetail("Could not publish the document because it was expired.")
.Build()),
ContentPublishingOperationStatus.CultureHasExpired => BadRequest(problemDetailsBuilder
.WithTitle("Document culture expired")
.WithDetail("Could not publish the document because some of the specified cultures were expired.")
.Build()),
ContentPublishingOperationStatus.AwaitingRelease => BadRequest(problemDetailsBuilder
.WithTitle("Document awaiting release")
.WithDetail("Could not publish the document because it was awaiting release.")
.Build()),
ContentPublishingOperationStatus.CultureAwaitingRelease => BadRequest(problemDetailsBuilder
.WithTitle("Document culture awaiting release")
.WithDetail(
"Could not publish the document because some of the specified cultures were awaiting release.")
.Build()),
ContentPublishingOperationStatus.InTrash => BadRequest(problemDetailsBuilder
.WithTitle("Document in the recycle bin")
.WithDetail("Could not publish the document because it was in the recycle bin.")
.Build()),
ContentPublishingOperationStatus.PathNotPublished => BadRequest(problemDetailsBuilder
.WithTitle("Parent not published")
.WithDetail("Could not publish the document because its parent was not published.")
.Build()),
ContentPublishingOperationStatus.InvalidCulture => BadRequest(problemDetailsBuilder
.WithTitle("Invalid cultures specified")
.WithDetail("A specified culture is not valid for the operation.")
.Build()),
ContentPublishingOperationStatus.CultureMissing => BadRequest(problemDetailsBuilder
.WithTitle("Culture missing")
.WithDetail("A culture needs to be specified to execute the operation.")
.Build()),
ContentPublishingOperationStatus.CannotPublishInvariantWhenVariant => BadRequest(problemDetailsBuilder
.WithTitle("Cannot publish invariant when variant")
.WithDetail("Cannot publish invariant culture when the document varies by culture.")
.Build()),
ContentPublishingOperationStatus.CannotPublishVariantWhenNotVariant => BadRequest(problemDetailsBuilder
.WithTitle("Cannot publish variant when not variant.")
.WithDetail("Cannot publish a given culture when the document is invariant.")
.Build()),
ContentPublishingOperationStatus.ConcurrencyViolation => BadRequest(problemDetailsBuilder
.WithTitle("Concurrency violation detected")
.WithDetail("An attempt was made to publish a version older than the latest version.")
.Build()),
ContentPublishingOperationStatus.UnsavedChanges => BadRequest(problemDetailsBuilder
.WithTitle("Unsaved changes")
.WithDetail(
"Could not publish the document because it had unsaved changes. Make sure to save all changes before attempting a publish.")
.Build()),
ContentPublishingOperationStatus.UnpublishTimeNeedsToBeAfterPublishTime => BadRequest(problemDetailsBuilder
.WithTitle("Unpublish time needs to be after the publish time")
.WithDetail(
"Cannot handle an unpublish time that is not after the specified publish time.")
.Build()),
ContentPublishingOperationStatus.PublishTimeNeedsToBeInFuture => BadRequest(problemDetailsBuilder
.WithTitle("Publish time needs to be higher than the current time")
.WithDetail(
"Cannot handle a publish time that is not after the current server time.")
.Build()),
ContentPublishingOperationStatus.UpublishTimeNeedsToBeInFuture => BadRequest(problemDetailsBuilder
.WithTitle("Unpublish time needs to be higher than the current time")
.WithDetail(
"Cannot handle an unpublish time that is not after the current server time.")
.Build()),
ContentPublishingOperationStatus.CannotUnpublishWhenReferenced => BadRequest(problemDetailsBuilder
.WithTitle("Cannot unpublish document when it's referenced somewhere else.")
.WithDetail(
"Cannot unpublish a referenced document, while the setting ContentSettings.DisableUnpublishWhenReferenced is enabled.")
.Build()),
ContentPublishingOperationStatus.FailedBranch => BadRequest(problemDetailsBuilder
.WithTitle("Failed branch operation")
.WithDetail("One or more items in the branch could not complete the operation.")
.WithExtension("failedBranchItems", failedBranchItems?.Select(item => new DocumentPublishBranchItemResult
{
Id = item.Key,
OperationStatus = item.OperationStatus
}) ?? Enumerable.Empty<DocumentPublishBranchItemResult>())
.Build()),
ContentPublishingOperationStatus.Failed => BadRequest(problemDetailsBuilder
.WithTitle("Publish or unpublish failed")
.WithDetail(
"An unspecified error occurred while (un)publishing. Please check the logs for additional information.")
.Build()),
ContentPublishingOperationStatus.TaskResultNotFound => NotFound(problemDetailsBuilder
.WithTitle("The result of the submitted task could not be found")
.Build()),
_ => StatusCode(StatusCodes.Status500InternalServerError, "Unknown content operation status."),
});
=> ContentPublishingOperationStatusResult(status, invalidPropertyAliases, failedBranchItems);
protected IActionResult PublicAccessOperationStatusResult(PublicAccessOperationStatus status)
=> OperationStatusResult(status, problemDetailsBuilder => status switch
@@ -38,15 +38,6 @@ public class DomainsController : DocumentControllerBase
_umbracoMapper = umbracoMapper;
}
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public DomainsController(IDomainService domainService, IUmbracoMapper umbracoMapper)
: this(
StaticServiceProvider.Instance.GetRequiredService<IAuthorizationService>(),
domainService,
umbracoMapper)
{
}
/// <summary>
/// Retrieves the list of domains and their associated culture settings assigned to the specified document.
/// </summary>
@@ -1,7 +1,6 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.ViewModels.Document.Item;
using Umbraco.Cms.Core.Models;
@@ -26,7 +25,6 @@ public class ItemDocumentItemController : DocumentItemControllerBase
/// </summary>
/// <param name="entityService">The service used to manage and retrieve entities within the CMS.</param>
/// <param name="documentPresentationFactory">The factory responsible for creating document presentation models.</param>
[ActivatorUtilitiesConstructor]
public ItemDocumentItemController(
IEntityService entityService,
IDocumentPresentationFactory documentPresentationFactory)
@@ -59,7 +57,8 @@ public class ItemDocumentItemController : DocumentItemControllerBase
.GetAll(UmbracoObjectTypes.Document, ids.ToArray())
.OfType<IDocumentEntitySlim>();
IEnumerable<DocumentItemResponseModel> responseModels = documents.Select(_documentPresentationFactory.CreateItemResponseModel);
IEnumerable<Task<DocumentItemResponseModel>> tasks = documents.Select(_documentPresentationFactory.CreateItemResponseModelAsync);
DocumentItemResponseModel[] responseModels = await Task.WhenAll(tasks);
return Ok(responseModels);
}
}
@@ -4,7 +4,6 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.ViewModels.Document.Item;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Entities;
using Umbraco.Cms.Core.Services;
@@ -28,7 +27,6 @@ public class SearchDocumentItemController : DocumentItemControllerBase
/// <param name="indexedEntitySearchService">Service for searching indexed entities.</param>
/// <param name="documentPresentationFactory">Factory for creating document presentation models.</param>
/// <param name="dataTypeService">Service for managing data types.</param>
[ActivatorUtilitiesConstructor]
public SearchDocumentItemController(
IIndexedEntitySearchService indexedEntitySearchService,
IDocumentPresentationFactory documentPresentationFactory,
@@ -39,56 +37,6 @@ public class SearchDocumentItemController : DocumentItemControllerBase
_dataTypeService = dataTypeService;
}
/// <summary>
/// Initializes a new instance of the <see cref="SearchDocumentItemController"/> class.
/// </summary>
/// <param name="indexedEntitySearchService">The service used to perform searches on indexed entities. This dependency is injected.</param>
/// <param name="documentPresentationFactory">The factory responsible for creating document presentation models. This dependency is injected.</param>
[Obsolete("Use the non-obsolete constructor instead. Scheduled for removal in Umbraco 18.")]
public SearchDocumentItemController(
IIndexedEntitySearchService indexedEntitySearchService,
IDocumentPresentationFactory documentPresentationFactory)
: this(
indexedEntitySearchService,
documentPresentationFactory,
StaticServiceProvider.Instance.GetRequiredService<IDataTypeService>())
{
}
/// <summary>
/// Searches for document items, including those in the recycle bin, using the specified query and filters.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="query">The search query string.</param>
/// <param name="trashed">If set, filters results to include only trashed items, only non-trashed items, or both (when null).</param>
/// <param name="culture">An optional culture code to filter search results.</param>
/// <param name="skip">The number of items to skip (for pagination).</param>
/// <param name="take">The maximum number of items to return (for pagination).</param>
/// <param name="parentId">An optional parent ID to filter results by parent.</param>
/// <param name="allowedDocumentTypes">An optional list of allowed document type IDs to filter results.</param>
/// <returns>A task representing the asynchronous operation, with an action result containing the search results.</returns>
[Obsolete("Please use the overload taking all parameters. Scheduled for removal in Umbraco 18.")]
[ApiExplorerSettings(IgnoreApi = true)]
public async Task<IActionResult> SearchWithTrashed(
CancellationToken cancellationToken,
string query,
bool? trashed = null,
string? culture = null,
int skip = 0,
int take = 100,
Guid? parentId = null,
[FromQuery] IEnumerable<Guid>? allowedDocumentTypes = null)
=> await SearchWithTrashed(
cancellationToken,
query,
trashed,
culture,
skip,
take,
parentId,
allowedDocumentTypes,
null);
/// <summary>
/// Searches for document items, including those in the recycle bin, based on the specified query and filters.
/// </summary>
@@ -130,9 +78,12 @@ public class SearchDocumentItemController : DocumentItemControllerBase
take,
ignoreUserStartNodes);
IEnumerable<Task<DocumentItemResponseModel>> tasks = searchResult.Items.OfType<IDocumentEntitySlim>().Select(_documentPresentationFactory.CreateItemResponseModelAsync);
DocumentItemResponseModel[] items = await Task.WhenAll(tasks);
var result = new PagedModel<DocumentItemResponseModel>
{
Items = searchResult.Items.OfType<IDocumentEntitySlim>().Select(_documentPresentationFactory.CreateItemResponseModel),
Items = items,
Total = searchResult.Total,
};
@@ -22,6 +22,8 @@ namespace Umbraco.Cms.Api.Management.Controllers.Document.RecycleBin;
[Authorize(Policy = AuthorizationPolicies.TreeAccessDocuments)]
public class DocumentRecycleBinControllerBase : RecycleBinControllerBase<DocumentRecycleBinItemResponseModel>
{
protected override string EntityName => "document";
private readonly IDocumentPresentationFactory _documentPresentationFactory;
/// <summary>
@@ -37,13 +39,13 @@ public class DocumentRecycleBinControllerBase : RecycleBinControllerBase<Documen
protected override Guid RecycleBinRootKey => Constants.System.RecycleBinContentKey;
protected override DocumentRecycleBinItemResponseModel MapRecycleBinViewModel(Guid? parentId, IEntitySlim entity)
protected override async Task<DocumentRecycleBinItemResponseModel> MapRecycleBinViewModelAsync(Guid? parentId, IEntitySlim entity)
{
DocumentRecycleBinItemResponseModel responseModel = base.MapRecycleBinViewModel(parentId, entity);
DocumentRecycleBinItemResponseModel responseModel = await base.MapRecycleBinViewModelAsync(parentId, entity);
if (entity is IDocumentEntitySlim documentEntitySlim)
{
responseModel.Variants = _documentPresentationFactory.CreateVariantsItemResponseModels(documentEntitySlim);
responseModel.Variants = await _documentPresentationFactory.CreateVariantsItemResponseModelsAsync(documentEntitySlim);
responseModel.DocumentType = _documentPresentationFactory.CreateDocumentTypeReferenceResponseModel(documentEntitySlim);
}
@@ -19,47 +19,6 @@ namespace Umbraco.Cms.Api.Management.Controllers.Document.Tree;
[ApiVersion("1.0")]
public class AncestorsDocumentTreeController : DocumentTreeControllerBase
{
/// <summary>
/// Initializes a new instance of the <see cref="Umbraco.Cms.Api.Management.Controllers.Document.Tree.AncestorsDocumentTreeController"/> class.
/// </summary>
/// <param name="entityService">Service used for entity operations within the Umbraco CMS.</param>
/// <param name="userStartNodeEntitiesService">Service for resolving user start nodes for entities.</param>
/// <param name="dataTypeService">Service for managing data types in the CMS.</param>
/// <param name="publicAccessService">Service for handling public access permissions on content.</param>
/// <param name="appCaches">Provides access to application-level caches.</param>
/// <param name="backofficeSecurityAccessor">Accessor for backoffice security context and operations.</param>
/// <param name="documentPresentationFactory">Factory for creating document presentation models.</param>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public AncestorsDocumentTreeController(
IEntityService entityService,
IUserStartNodeEntitiesService userStartNodeEntitiesService,
IDataTypeService dataTypeService,
IPublicAccessService publicAccessService,
AppCaches appCaches,
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
IDocumentPresentationFactory documentPresentationFactory)
: base(
entityService,
userStartNodeEntitiesService,
dataTypeService,
publicAccessService,
appCaches,
backofficeSecurityAccessor,
documentPresentationFactory)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="AncestorsDocumentTreeController"/> class.
/// </summary>
/// <param name="entityService">Service for managing and retrieving entities within Umbraco.</param>
/// <param name="flagProviders">A collection of providers for handling entity flags.</param>
/// <param name="userStartNodeEntitiesService">Service for resolving user start node entities.</param>
/// <param name="dataTypeService">Service for managing data types in Umbraco.</param>
/// <param name="publicAccessService">Service for handling public access permissions.</param>
/// <param name="appCaches">Provides access to application-level caches.</param>
/// <param name="backofficeSecurityAccessor">Accessor for backoffice security context and operations.</param>
/// <param name="documentPresentationFactory">Factory for creating document presentation models.</param>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 19.")]
public AncestorsDocumentTreeController(
IEntityService entityService,
@@ -20,47 +20,6 @@ namespace Umbraco.Cms.Api.Management.Controllers.Document.Tree;
[ApiVersion("1.0")]
public class ChildrenDocumentTreeController : DocumentTreeControllerBase
{
/// <summary>
/// Initializes a new instance of the <see cref="ChildrenDocumentTreeController"/> class.
/// </summary>
/// <param name="entityService">Service for managing and retrieving entities in the system.</param>
/// <param name="userStartNodeEntitiesService">Service for resolving user start nodes for entities.</param>
/// <param name="dataTypeService">Service for accessing and managing data types.</param>
/// <param name="publicAccessService">Service for handling public access permissions and restrictions.</param>
/// <param name="appCaches">Provides application-level caching mechanisms.</param>
/// <param name="backofficeSecurityAccessor">Accessor for back office security context and operations.</param>
/// <param name="documentPresentationFactory">Factory for creating document presentation models.</param>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public ChildrenDocumentTreeController(
IEntityService entityService,
IUserStartNodeEntitiesService userStartNodeEntitiesService,
IDataTypeService dataTypeService,
IPublicAccessService publicAccessService,
AppCaches appCaches,
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
IDocumentPresentationFactory documentPresentationFactory)
: base(
entityService,
userStartNodeEntitiesService,
dataTypeService,
publicAccessService,
appCaches,
backofficeSecurityAccessor,
documentPresentationFactory)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ChildrenDocumentTreeController"/> class, responsible for managing child document tree operations in the Umbraco backoffice API.
/// </summary>
/// <param name="entityService">Service for accessing and managing entities within Umbraco.</param>
/// <param name="flagProviders">A collection of providers that supply flags for document tree nodes.</param>
/// <param name="userStartNodeEntitiesService">Service for resolving user-specific start nodes in the content tree.</param>
/// <param name="dataTypeService">Service for managing data types in Umbraco.</param>
/// <param name="publicAccessService">Service for handling public access permissions on documents.</param>
/// <param name="appCaches">Provides application-level caching functionality.</param>
/// <param name="backofficeSecurityAccessor">Accessor for backoffice security context and authentication.</param>
/// <param name="documentPresentationFactory">Factory for creating document presentation models for the API.</param>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 19.")]
public ChildrenDocumentTreeController(
IEntityService entityService,
@@ -34,27 +34,6 @@ public abstract class DocumentTreeControllerBase : UserStartNodeTreeControllerBa
private readonly IDocumentPresentationFactory _documentPresentationFactory;
private readonly IDocumentPermissionFilterService _documentPermissionFilterService;
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
protected DocumentTreeControllerBase(
IEntityService entityService,
IUserStartNodeEntitiesService userStartNodeEntitiesService,
IDataTypeService dataTypeService,
IPublicAccessService publicAccessService,
AppCaches appCaches,
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
IDocumentPresentationFactory documentPresentationFactory)
: this(
entityService,
StaticServiceProvider.Instance.GetRequiredService<FlagProviderCollection>(),
userStartNodeEntitiesService,
dataTypeService,
publicAccessService,
appCaches,
backofficeSecurityAccessor,
documentPresentationFactory)
{
}
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 19.")]
protected DocumentTreeControllerBase(
IEntityService entityService,
@@ -102,9 +81,9 @@ public abstract class DocumentTreeControllerBase : UserStartNodeTreeControllerBa
protected override Ordering ItemOrdering => Ordering.By(Infrastructure.Persistence.Dtos.NodeDto.SortOrderColumnName);
protected override DocumentTreeItemResponseModel MapTreeItemViewModel(Guid? parentId, IEntitySlim entity)
protected override async Task<DocumentTreeItemResponseModel> MapTreeItemViewModelAsync(Guid? parentId, IEntitySlim entity)
{
DocumentTreeItemResponseModel responseModel = base.MapTreeItemViewModel(parentId, entity);
DocumentTreeItemResponseModel responseModel = await base.MapTreeItemViewModelAsync(parentId, entity);
if (entity is IDocumentEntitySlim documentEntitySlim)
{
@@ -115,7 +94,7 @@ public abstract class DocumentTreeControllerBase : UserStartNodeTreeControllerBa
responseModel.Id = entity.Key;
responseModel.CreateDate = entity.CreateDate;
responseModel.Variants = _documentPresentationFactory.CreateVariantsItemResponseModels(documentEntitySlim);
responseModel.Variants = await _documentPresentationFactory.CreateVariantsItemResponseModelsAsync(documentEntitySlim);
responseModel.DocumentType = _documentPresentationFactory.CreateDocumentTypeReferenceResponseModel(documentEntitySlim);
}
@@ -20,47 +20,6 @@ namespace Umbraco.Cms.Api.Management.Controllers.Document.Tree;
[ApiVersion("1.0")]
public class RootDocumentTreeController : DocumentTreeControllerBase
{
/// <summary>
/// Initializes a new instance of the <see cref="RootDocumentTreeController"/> class, which manages the root nodes of the document tree in the Umbraco backoffice.
/// </summary>
/// <param name="entityService">The service used for entity operations.</param>
/// <param name="userStartNodeEntitiesService">The service for resolving user start node entities.</param>
/// <param name="dataTypeService">The service for managing data types.</param>
/// <param name="publicAccessService">The service for handling public access permissions.</param>
/// <param name="appCaches">The application-level caches.</param>
/// <param name="backofficeSecurityAccessor">Accessor for backoffice security context.</param>
/// <param name="documentPresentationFactory">Factory for creating document presentation models.</param>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public RootDocumentTreeController(
IEntityService entityService,
IUserStartNodeEntitiesService userStartNodeEntitiesService,
IDataTypeService dataTypeService,
IPublicAccessService publicAccessService,
AppCaches appCaches,
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
IDocumentPresentationFactory documentPresentationFactory)
: base(
entityService,
userStartNodeEntitiesService,
dataTypeService,
publicAccessService,
appCaches,
backofficeSecurityAccessor,
documentPresentationFactory)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Umbraco.Cms.Api.Management.Controllers.Document.Tree.RootDocumentTreeController"/> class.
/// </summary>
/// <param name="entityService">Service for managing and retrieving entities within Umbraco.</param>
/// <param name="flagProviders">A collection of providers that supply flags for document tree nodes.</param>
/// <param name="userStartNodeEntitiesService">Service for resolving user-specific start nodes in the content tree.</param>
/// <param name="dataTypeService">Service for accessing and managing data types.</param>
/// <param name="publicAccessService">Service for handling public access permissions on documents.</param>
/// <param name="appCaches">Provides application-level caching mechanisms.</param>
/// <param name="backofficeSecurityAccessor">Accessor for backoffice security context and operations.</param>
/// <param name="documentPresentationFactory">Factory for creating document presentation models.</param>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 19.")]
public RootDocumentTreeController(
IEntityService entityService,
@@ -20,47 +20,6 @@ namespace Umbraco.Cms.Api.Management.Controllers.Document.Tree;
[ApiVersion("1.0")]
public class SiblingsDocumentTreeController : DocumentTreeControllerBase
{
/// <summary>
/// Initializes a new instance of the <see cref="SiblingsDocumentTreeController"/> class.
/// </summary>
/// <param name="entityService">Service for managing and retrieving entities in the system.</param>
/// <param name="userStartNodeEntitiesService">Service for resolving user start nodes for entities.</param>
/// <param name="dataTypeService">Service for accessing and managing data types.</param>
/// <param name="publicAccessService">Service for handling public access permissions.</param>
/// <param name="appCaches">Provides application-level caching functionality.</param>
/// <param name="backofficeSecurityAccessor">Accessor for backoffice security context and operations.</param>
/// <param name="documentPresentationFactory">Factory for creating document presentation models.</param>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public SiblingsDocumentTreeController(
IEntityService entityService,
IUserStartNodeEntitiesService userStartNodeEntitiesService,
IDataTypeService dataTypeService,
IPublicAccessService publicAccessService,
AppCaches appCaches,
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
IDocumentPresentationFactory documentPresentationFactory)
: base(
entityService,
userStartNodeEntitiesService,
dataTypeService,
publicAccessService,
appCaches,
backofficeSecurityAccessor,
documentPresentationFactory)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SiblingsDocumentTreeController"/> class.
/// </summary>
/// <param name="entityService">Service used for entity operations within the Umbraco CMS.</param>
/// <param name="flagProviders">A collection of providers that supply flag information for entities.</param>
/// <param name="userStartNodeEntitiesService">Service for resolving user start node entities.</param>
/// <param name="dataTypeService">Service for managing data types in the CMS.</param>
/// <param name="publicAccessService">Service for handling public access permissions.</param>
/// <param name="appCaches">Provides access to application-level caches.</param>
/// <param name="backofficeSecurityAccessor">Accessor for backoffice security context and operations.</param>
/// <param name="documentPresentationFactory">Factory for creating document presentation models.</param>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 19.")]
public SiblingsDocumentTreeController(
IEntityService entityService,
@@ -44,16 +44,6 @@ public class UpdateDomainsController : DocumentControllerBase
_domainPresentationFactory = domainPresentationFactory;
}
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public UpdateDomainsController(IDomainService domainService, IUmbracoMapper umbracoMapper, IDomainPresentationFactory domainPresentationFactory)
: this(
StaticServiceProvider.Instance.GetRequiredService<IAuthorizationService>(),
domainService,
umbracoMapper,
domainPresentationFactory)
{
}
/// <summary>
/// Updates the domains assigned to the specified document.
/// </summary>
@@ -41,16 +41,6 @@ public class UpdateNotificationsController : DocumentControllerBase
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public UpdateNotificationsController(IContentEditingService contentEditingService, INotificationService notificationService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
: this(
StaticServiceProvider.Instance.GetRequiredService<IAuthorizationService>(),
contentEditingService,
notificationService,
backOfficeSecurityAccessor)
{
}
/// <summary>
/// Updates the notification subscriptions for the current user on the specified document.
/// </summary>
@@ -14,6 +14,8 @@ namespace Umbraco.Cms.Api.Management.Controllers.DocumentBlueprint;
[ApiExplorerSettings(GroupName = "Document Blueprint")]
public abstract class DocumentBlueprintControllerBase : ContentControllerBase
{
protected override string EntityName => "document blueprint";
protected IActionResult DocumentBlueprintNotFound()
=> OperationStatusResult(ContentEditingOperationStatus.NotFound, problemDetailsBuilder
=> NotFound(problemDetailsBuilder
@@ -1,7 +1,6 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.Services.Flags;
using Umbraco.Cms.Api.Management.ViewModels.Tree;
@@ -15,24 +14,12 @@ namespace Umbraco.Cms.Api.Management.Controllers.DocumentBlueprint.Tree;
[ApiVersion("1.0")]
public class AncestorsDocumentBlueprintTreeController : DocumentBlueprintTreeControllerBase
{
/// <summary>
/// Initializes a new instance of the <see cref="AncestorsDocumentBlueprintTreeController"/> class.
/// </summary>
/// <param name="entityService">Service used for entity operations within the controller.</param>
/// <param name="documentPresentationFactory">Factory responsible for creating document presentation models.</param>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public AncestorsDocumentBlueprintTreeController(IEntityService entityService, IDocumentPresentationFactory documentPresentationFactory)
: base(entityService, documentPresentationFactory)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="AncestorsDocumentBlueprintTreeController"/> class, which manages the retrieval of ancestor document blueprint tree nodes.
/// </summary>
/// <param name="entityService">Service used for entity operations within the document blueprint tree.</param>
/// <param name="flagProviders">A collection of providers that supply flags for tree nodes.</param>
/// <param name="documentPresentationFactory">Factory responsible for creating document presentation models.</param>
[ActivatorUtilitiesConstructor]
public AncestorsDocumentBlueprintTreeController(IEntityService entityService, FlagProviderCollection flagProviders, IDocumentPresentationFactory documentPresentationFactory)
: base(entityService, flagProviders, documentPresentationFactory)
{
@@ -1,7 +1,6 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.Services.Flags;
@@ -16,24 +15,12 @@ namespace Umbraco.Cms.Api.Management.Controllers.DocumentBlueprint.Tree;
[ApiVersion("1.0")]
public class ChildrenDocumentBlueprintTreeController : DocumentBlueprintTreeControllerBase
{
/// <summary>
/// Initializes a new instance of the <see cref="ChildrenDocumentBlueprintTreeController"/> class, which manages the retrieval of child document blueprint nodes in the tree structure.
/// </summary>
/// <param name="entityService">The service used to interact with and retrieve entity data.</param>
/// <param name="documentPresentationFactory">The factory responsible for creating document presentation models.</param>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public ChildrenDocumentBlueprintTreeController(IEntityService entityService, IDocumentPresentationFactory documentPresentationFactory)
: base(entityService, documentPresentationFactory)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ChildrenDocumentBlueprintTreeController"/> class.
/// </summary>
/// <param name="entityService">Service used to manage and retrieve entities within the Umbraco CMS.</param>
/// <param name="flagProviders">A collection of providers that supply flags for document blueprints.</param>
/// <param name="documentPresentationFactory">Factory responsible for creating document presentation models.</param>
[ActivatorUtilitiesConstructor]
public ChildrenDocumentBlueprintTreeController(IEntityService entityService, FlagProviderCollection flagProviders, IDocumentPresentationFactory documentPresentationFactory)
: base(entityService, flagProviders, documentPresentationFactory)
{
@@ -25,20 +25,6 @@ public class DocumentBlueprintTreeControllerBase : FolderTreeControllerBase<Docu
{
private readonly IDocumentPresentationFactory _documentPresentationFactory;
/// <summary>
/// Initializes a new instance of the <see cref="DocumentBlueprintTreeControllerBase"/> class.
/// </summary>
/// <param name="entityService">Service used for entity operations within the document blueprint tree.</param>
/// <param name="documentPresentationFactory">Factory responsible for creating document presentation models.</param>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public DocumentBlueprintTreeControllerBase(IEntityService entityService, IDocumentPresentationFactory documentPresentationFactory)
: this(
entityService,
StaticServiceProvider.Instance.GetRequiredService<FlagProviderCollection>(),
documentPresentationFactory)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DocumentBlueprintTreeControllerBase"/> class, providing required services for managing document blueprint trees.
/// </summary>
@@ -80,15 +66,19 @@ public class DocumentBlueprintTreeControllerBase : FolderTreeControllerBase<Docu
}
}
protected override DocumentBlueprintTreeItemResponseModel[] MapTreeItemViewModels(Guid? parentId, IEntitySlim[] entities)
=> entities.Select(entity =>
protected override async Task<DocumentBlueprintTreeItemResponseModel[]> MapTreeItemViewModelsAsync(Guid? parentId, IEntitySlim[] entities)
{
IEnumerable<Task<DocumentBlueprintTreeItemResponseModel>> tasks = entities.Select(async entity =>
{
DocumentBlueprintTreeItemResponseModel responseModel = MapTreeItemViewModel(parentId, entity);
DocumentBlueprintTreeItemResponseModel responseModel = await MapTreeItemViewModelAsync(parentId, entity);
if (entity is IDocumentEntitySlim documentEntitySlim)
{
responseModel.HasChildren = false;
responseModel.DocumentType = _documentPresentationFactory.CreateDocumentTypeReferenceResponseModel(documentEntitySlim);
}
return responseModel;
}).ToArray();
});
return await Task.WhenAll(tasks);
}
}
@@ -1,7 +1,6 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.Services.Flags;
@@ -17,24 +16,12 @@ namespace Umbraco.Cms.Api.Management.Controllers.DocumentBlueprint.Tree;
[ApiVersion("1.0")]
public class RootDocumentBlueprintTreeController : DocumentBlueprintTreeControllerBase
{
/// <summary>
/// Initializes a new instance of the <see cref="RootDocumentBlueprintTreeController"/> class, which manages the tree structure for root document blueprints.
/// </summary>
/// <param name="entityService">The service used to manage and retrieve entities within the system.</param>
/// <param name="documentPresentationFactory">The factory responsible for creating document presentation models.</param>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public RootDocumentBlueprintTreeController(IEntityService entityService, IDocumentPresentationFactory documentPresentationFactory)
: base(entityService, documentPresentationFactory)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RootDocumentBlueprintTreeController"/> class.
/// </summary>
/// <param name="entityService">Service used for entity operations within the document blueprint tree.</param>
/// <param name="flagProviders">A collection of providers that supply flags for document blueprints.</param>
/// <param name="documentPresentationFactory">Factory responsible for creating document presentation models.</param>
[ActivatorUtilitiesConstructor]
public RootDocumentBlueprintTreeController(IEntityService entityService, FlagProviderCollection flagProviders, IDocumentPresentationFactory documentPresentationFactory)
: base(entityService, flagProviders, documentPresentationFactory)
{
@@ -1,6 +1,5 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.Services.Flags;
@@ -14,19 +13,12 @@ namespace Umbraco.Cms.Api.Management.Controllers.DocumentBlueprint.Tree;
/// </summary>
public class SiblingsDocumentBlueprintTreeController : DocumentBlueprintTreeControllerBase
{
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public SiblingsDocumentBlueprintTreeController(IEntityService entityService, IDocumentPresentationFactory documentPresentationFactory)
: base(entityService, documentPresentationFactory)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SiblingsDocumentBlueprintTreeController"/> class.
/// </summary>
/// <param name="entityService">Service used for entity operations within the Umbraco CMS.</param>
/// <param name="flagProviders">A collection of providers that supply flags for document blueprints.</param>
/// <param name="documentPresentationFactory">Factory responsible for creating document presentation models.</param>
[ActivatorUtilitiesConstructor]
public SiblingsDocumentBlueprintTreeController(IEntityService entityService, FlagProviderCollection flagProviders, IDocumentPresentationFactory documentPresentationFactory)
: base(entityService, flagProviders, documentPresentationFactory)
{
@@ -0,0 +1,43 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.ViewModels.DocumentType;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.DocumentType;
[ApiVersion("1.0")]
public class AllowedInLibraryDocumentTypeController : DocumentTypeControllerBase
{
private readonly IContentTypeService _contentTypeService;
private readonly IUmbracoMapper _umbracoMapper;
public AllowedInLibraryDocumentTypeController(IContentTypeService contentTypeService, IUmbracoMapper umbracoMapper)
{
_contentTypeService = contentTypeService;
_umbracoMapper = umbracoMapper;
}
[HttpGet("allowed-in-library")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedViewModel<AllowedDocumentType>), StatusCodes.Status200OK)]
[EndpointSummary("Gets document types allowed in library.")]
[EndpointDescription("Gets a collection of document types that are allowed in the library.")]
public async Task<IActionResult> AllowedInLibrary(CancellationToken cancellationToken, int skip = 0, int take = 100)
{
PagedModel<IContentType> result = await _contentTypeService.GetAllAllowedInLibraryAsync(skip, take);
List<AllowedDocumentType> viewModels = _umbracoMapper.MapEnumerable<IContentType, AllowedDocumentType>(result.Items);
var pagedViewModel = new PagedViewModel<AllowedDocumentType>
{
Total = result.Total,
Items = viewModels,
};
return Ok(pagedViewModel);
}
}
@@ -15,7 +15,7 @@ namespace Umbraco.Cms.Api.Management.Controllers.DocumentType;
/// </summary>
[VersionedApiBackOfficeRoute(Constants.UdiEntityType.DocumentType)]
[ApiExplorerSettings(GroupName = "Document Type")]
[Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentsOrDocumentTypes)]
[Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentsOrElementsOrDocumentTypes)]
public abstract class DocumentTypeControllerBase : ManagementApiControllerBase
{
protected IActionResult OperationStatusResult(ContentTypeOperationStatus status)
@@ -1,7 +1,6 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Management.Services.Flags;
using Umbraco.Cms.Api.Management.ViewModels.Tree;
using Umbraco.Cms.Core.Services;
@@ -14,24 +13,12 @@ namespace Umbraco.Cms.Api.Management.Controllers.DocumentType.Tree;
[ApiVersion("1.0")]
public class AncestorsDocumentTypeTreeController : DocumentTypeTreeControllerBase
{
/// <summary>
/// Initializes a new instance of the <see cref="AncestorsDocumentTypeTreeController"/> class.
/// </summary>
/// <param name="entityService">Service used for managing and retrieving entities within Umbraco.</param>
/// <param name="contentTypeService">Service used for managing and retrieving content types in Umbraco.</param>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public AncestorsDocumentTypeTreeController(IEntityService entityService, IContentTypeService contentTypeService)
: base(entityService, contentTypeService)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="AncestorsDocumentTypeTreeController"/> class, which manages operations related to ancestor document types in the tree structure.
/// </summary>
/// <param name="entityService">Service used for entity operations within the Umbraco CMS.</param>
/// <param name="flagProviders">A collection of flag providers used to determine additional metadata or state for entities.</param>
/// <param name="contentTypeService">Service used for managing content types in the CMS.</param>
[ActivatorUtilitiesConstructor]
public AncestorsDocumentTypeTreeController(IEntityService entityService, FlagProviderCollection flagProviders, IContentTypeService contentTypeService)
: base(entityService, flagProviders, contentTypeService)
{
@@ -1,7 +1,6 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.Services.Flags;
using Umbraco.Cms.Api.Management.ViewModels.Tree;
@@ -15,24 +14,12 @@ namespace Umbraco.Cms.Api.Management.Controllers.DocumentType.Tree;
[ApiVersion("1.0")]
public class ChildrenDocumentTypeTreeController : DocumentTypeTreeControllerBase
{
/// <summary>
/// Initializes a new instance of the <see cref="ChildrenDocumentTypeTreeController"/> class, which manages the retrieval of child document types in the tree structure.
/// </summary>
/// <param name="entityService">Service used for entity operations within the Umbraco CMS.</param>
/// <param name="contentTypeService">Service used for managing content types in the Umbraco CMS.</param>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public ChildrenDocumentTypeTreeController(IEntityService entityService, IContentTypeService contentTypeService)
: base(entityService, contentTypeService)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ChildrenDocumentTypeTreeController"/> class.
/// </summary>
/// <param name="entityService">The <see cref="IEntityService"/> used to manage entities.</param>
/// <param name="flagProviders">The <see cref="FlagProviderCollection"/> containing flag providers for the tree.</param>
/// <param name="contentTypeService">The <see cref="IContentTypeService"/> used to manage content types.</param>
[ActivatorUtilitiesConstructor]
public ChildrenDocumentTypeTreeController(IEntityService entityService, FlagProviderCollection flagProviders, IContentTypeService contentTypeService)
: base(entityService, flagProviders, contentTypeService)
{
@@ -25,20 +25,6 @@ public class DocumentTypeTreeControllerBase : FolderTreeControllerBase<DocumentT
{
private readonly IContentTypeService _contentTypeService;
/// <summary>
/// Initializes a new instance of the <see cref="DocumentTypeTreeControllerBase"/> class.
/// </summary>
/// <param name="entityService">Service used for managing and retrieving entities within the Umbraco CMS.</param>
/// <param name="contentTypeService">Service used for managing and retrieving content types (document types) within the Umbraco CMS.</param>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public DocumentTypeTreeControllerBase(IEntityService entityService, IContentTypeService contentTypeService)
: this(
entityService,
StaticServiceProvider.Instance.GetRequiredService<FlagProviderCollection>(),
contentTypeService)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DocumentTypeTreeControllerBase"/> class.
/// </summary>
@@ -69,15 +55,15 @@ public class DocumentTypeTreeControllerBase : FolderTreeControllerBase<DocumentT
protected override UmbracoObjectTypes FolderObjectType => UmbracoObjectTypes.DocumentTypeContainer;
protected override DocumentTypeTreeItemResponseModel[] MapTreeItemViewModels(Guid? parentKey, IEntitySlim[] entities)
protected override async Task<DocumentTypeTreeItemResponseModel[]> MapTreeItemViewModelsAsync(Guid? parentKey, IEntitySlim[] entities)
{
var contentTypes = _contentTypeService
.GetMany(entities.Select(entity => entity.Id).ToArray())
.ToDictionary(contentType => contentType.Id);
return entities.Select(entity =>
IEnumerable<Task<DocumentTypeTreeItemResponseModel>> tasks = entities.Select(async entity =>
{
DocumentTypeTreeItemResponseModel responseModel = MapTreeItemViewModel(parentKey, entity);
DocumentTypeTreeItemResponseModel responseModel = await MapTreeItemViewModelAsync(parentKey, entity);
if (contentTypes.TryGetValue(entity.Id, out IContentType? contentType))
{
responseModel.Icon = contentType.Icon ?? responseModel.Icon;
@@ -85,6 +71,8 @@ public class DocumentTypeTreeControllerBase : FolderTreeControllerBase<DocumentT
}
return responseModel;
}).ToArray();
});
return await Task.WhenAll(tasks);
}
}
@@ -1,7 +1,6 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.Services.Flags;
using Umbraco.Cms.Api.Management.ViewModels.Tree;
@@ -16,24 +15,12 @@ namespace Umbraco.Cms.Api.Management.Controllers.DocumentType.Tree;
[ApiVersion("1.0")]
public class RootDocumentTypeTreeController : DocumentTypeTreeControllerBase
{
/// <summary>
/// Initializes a new instance of the <see cref="RootDocumentTypeTreeController"/> class, which manages the root nodes of the document type tree in the Umbraco management API.
/// </summary>
/// <param name="entityService">The service used to manage and retrieve entities.</param>
/// <param name="contentTypeService">The service used to manage and retrieve content types.</param>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public RootDocumentTypeTreeController(IEntityService entityService, IContentTypeService contentTypeService)
: base(entityService, contentTypeService)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RootDocumentTypeTreeController"/> class.
/// </summary>
/// <param name="entityService">An <see cref="IEntityService"/> instance used for managing entities within the system.</param>
/// <param name="flagProviders">A <see cref="FlagProviderCollection"/> containing providers for entity flags.</param>
/// <param name="contentTypeService">An <see cref="IContentTypeService"/> instance used for managing content types.</param>
[ActivatorUtilitiesConstructor]
public RootDocumentTypeTreeController(IEntityService entityService, FlagProviderCollection flagProviders, IContentTypeService contentTypeService)
: base(entityService, flagProviders, contentTypeService)
{
@@ -1,6 +1,5 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.Services.Flags;
using Umbraco.Cms.Api.Management.ViewModels.Tree;
@@ -13,24 +12,12 @@ namespace Umbraco.Cms.Api.Management.Controllers.DocumentType.Tree;
/// </summary>
public class SiblingsDocumentTypeTreeController : DocumentTypeTreeControllerBase
{
/// <summary>
/// Initializes a new instance of the <see cref="SiblingsDocumentTypeTreeController"/> class.
/// </summary>
/// <param name="entityService">Service used for entity operations within the Umbraco CMS.</param>
/// <param name="contentTypeService">Service used for managing content types in the Umbraco CMS.</param>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public SiblingsDocumentTypeTreeController(IEntityService entityService, IContentTypeService contentTypeService)
: base(entityService, contentTypeService)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SiblingsDocumentTypeTreeController"/> class.
/// </summary>
/// <param name="entityService">Service used for entity operations within the controller.</param>
/// <param name="flagProviders">A collection of providers that supply flags for document type tree nodes.</param>
/// <param name="contentTypeService">Service used for managing content types.</param>
[ActivatorUtilitiesConstructor]
public SiblingsDocumentTypeTreeController(IEntityService entityService, FlagProviderCollection flagProviders, IContentTypeService contentTypeService)
: base(entityService, flagProviders, contentTypeService)
{
@@ -62,14 +62,17 @@ public class AllDocumentVersionController : DocumentVersionControllerBase
Attempt<PagedModel<ContentVersionMeta>?, ContentVersionOperationStatus> attempt =
await _contentVersionService.GetPagedContentVersionsAsync(documentId, culture, skip, take);
if (attempt.Success is false)
{
return MapFailure(attempt.Status);
}
var pagedViewModel = new PagedViewModel<DocumentVersionItemResponseModel>
{
Total = attempt.Result!.Total,
Items = await _documentVersionPresentationFactory.CreateMultipleAsync(attempt.Result!.Items),
};
return attempt.Success
? Ok(pagedViewModel)
: MapFailure(attempt.Status);
return Ok(pagedViewModel);
}
}
@@ -16,25 +16,26 @@ namespace Umbraco.Cms.Api.Management.Controllers.DocumentVersion;
[Authorize(Policy = AuthorizationPolicies.TreeAccessDocuments)]
public abstract class DocumentVersionControllerBase : ManagementApiControllerBase
{
protected IActionResult MapFailure(ContentVersionOperationStatus status)
internal static IActionResult MapFailure(ContentVersionOperationStatus status)
=> OperationStatusResult(status, problemDetailsBuilder => status switch
{
ContentVersionOperationStatus.NotFound => NotFound(problemDetailsBuilder
ContentVersionOperationStatus.NotFound => new NotFoundObjectResult(problemDetailsBuilder
.WithTitle("The requested version could not be found")
.Build()),
ContentVersionOperationStatus.ContentNotFound => NotFound(problemDetailsBuilder
ContentVersionOperationStatus.ContentNotFound => new NotFoundObjectResult(problemDetailsBuilder
.WithTitle("The requested document could not be found")
.Build()),
ContentVersionOperationStatus.InvalidSkipTake => SkipTakeToPagingProblem(),
ContentVersionOperationStatus.RollBackFailed => BadRequest(problemDetailsBuilder
ContentVersionOperationStatus.RollBackFailed => new BadRequestObjectResult(problemDetailsBuilder
.WithTitle("Rollback failed")
.WithDetail("An unspecified error occurred while rolling back the requested version. Please check the logs for additional information.")),
ContentVersionOperationStatus.RollBackCanceled => BadRequest(problemDetailsBuilder
.WithDetail(
"An unspecified error occurred while rolling back the requested version. Please check the logs for additional information.")),
ContentVersionOperationStatus.RollBackCanceled => new BadRequestObjectResult(problemDetailsBuilder
.WithTitle("Request cancelled by notification")
.WithDetail("The request to roll back was cancelled by a notification handler.")
.Build()),
_ => StatusCode(StatusCodes.Status500InternalServerError, problemDetailsBuilder
_ => new ObjectResult(problemDetailsBuilder
.WithTitle("Unknown content version operation status.")
.Build()),
.Build()) { StatusCode = StatusCodes.Status500InternalServerError },
});
}
@@ -0,0 +1,81 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.ViewModels.Element;
using Umbraco.Cms.Core.Actions;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Security.Authorization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Element;
/// <summary>
/// Controller for managing elements in Umbraco that are identified by their unique key.
/// </summary>
[ApiVersion("1.0")]
public class ByKeyElementController : ElementControllerBase
{
private readonly IAuthorizationService _authorizationService;
private readonly IElementService _elementService;
private readonly IElementPresentationFactory _elementPresentationFactory;
/// <summary>
/// Initializes a new instance of the <see cref="ByKeyElementController"/> class.
/// </summary>
/// <param name="authorizationService">Service used to authorize access to element resources.</param>
/// <param name="elementService">Service for retrieving element data within the CMS.</param>
/// <param name="elementPresentationFactory">Factory responsible for creating element presentation models.</param>
public ByKeyElementController(
IAuthorizationService authorizationService,
IElementService elementService,
IElementPresentationFactory elementPresentationFactory)
{
_authorizationService = authorizationService;
_elementService = elementService;
_elementPresentationFactory = elementPresentationFactory;
}
/// <summary>
/// Retrieves an element by its unique identifier.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique <see cref="Guid"/> of the element to retrieve.</param>
/// <returns>
/// An <see cref="IActionResult"/> containing an <see cref="ElementResponseModel"/> if the element is found;
/// otherwise, a 404 Not Found or 403 Forbidden error response.
/// </returns>
[HttpGet("{id:guid}")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(ElementResponseModel), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[EndpointSummary("Gets an element.")]
[EndpointDescription("Gets an element identified by the provided Id.")]
public async Task<IActionResult> ByKey(CancellationToken cancellationToken, Guid id)
{
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
User,
ElementPermissionResource.WithKeys(ActionElementBrowse.ActionLetter, id),
AuthorizationPolicies.ElementPermissionByResource);
if (!authorizationResult.Succeeded)
{
return Forbidden();
}
IElement? element = _elementService.GetById(id);
if (element is null)
{
return ContentEditingOperationStatusResult(ContentEditingOperationStatus.NotFound);
}
ContentScheduleCollection contentScheduleCollection = _elementService.GetContentScheduleByContentId(id);
ElementResponseModel model = _elementPresentationFactory.CreateResponseModel(element, contentScheduleCollection);
return Ok(model);
}
}
@@ -0,0 +1,39 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.ViewModels.Element;
namespace Umbraco.Cms.Api.Management.Controllers.Element;
/// <summary>
/// API controller responsible for handling operations related to configuration elements within the Umbraco CMS management area.
/// </summary>
[ApiVersion("1.0")]
public class ConfigurationElementController : ElementControllerBase
{
private readonly IConfigurationPresentationFactory _configurationPresentationFactory;
/// <summary>
/// Initializes a new instance of the <see cref="ConfigurationElementController"/> class.
/// </summary>
/// <param name="configurationPresentationFactory">Factory used to create configuration presentation models for elements.</param>
public ConfigurationElementController(IConfigurationPresentationFactory configurationPresentationFactory)
=> _configurationPresentationFactory = configurationPresentationFactory;
/// <summary>
/// Retrieves the configuration settings for elements.
/// </summary>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
/// <returns>An <see cref="IActionResult"/> containing an <see cref="ElementConfigurationResponseModel"/> with the element configuration settings.</returns>
[HttpGet("configuration")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(ElementConfigurationResponseModel), StatusCodes.Status200OK)]
[EndpointSummary("Gets the element configuration.")]
[EndpointDescription("Gets the configuration settings for elements.")]
public Task<IActionResult> Configuration(CancellationToken cancellationToken)
{
ElementConfigurationResponseModel responseModel = _configurationPresentationFactory.CreateElementConfigurationResponseModel();
return Task.FromResult<IActionResult>(Ok(responseModel));
}
}
@@ -0,0 +1,90 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.Element;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Actions;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Security.Authorization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Element;
/// <summary>
/// API controller responsible for handling copy operations on elements in the Umbraco CMS.
/// </summary>
[ApiVersion("1.0")]
public class CopyElementController : ElementControllerBase
{
private readonly IAuthorizationService _authorizationService;
private readonly IElementEditingService _elementEditingService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
/// <summary>
/// Initializes a new instance of the <see cref="CopyElementController"/> class.
/// </summary>
/// <param name="authorizationService">Service used to authorize element copy operations.</param>
/// <param name="elementEditingService">Service responsible for element editing operations.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context.</param>
public CopyElementController(
IAuthorizationService authorizationService,
IElementEditingService elementEditingService,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
{
_authorizationService = authorizationService;
_elementEditingService = elementEditingService;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}
/// <summary>
/// Creates a copy of an element identified by the specified unique identifier.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier of the element to copy.</param>
/// <param name="copyElementRequestModel">The model containing the target location for the copy.</param>
/// <returns>An <see cref="IActionResult"/> representing the result of the copy operation.</returns>
[HttpPost("{id:guid}/copy")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[EndpointSummary("Copies an element.")]
[EndpointDescription("Creates a duplicate of an existing element identified by the provided Id.")]
public async Task<IActionResult> Copy(CancellationToken cancellationToken, Guid id, CopyElementRequestModel copyElementRequestModel)
{
// Check Copy permission on source element
AuthorizationResult sourceAuthorizationResult = await _authorizationService.AuthorizeResourceAsync(
User,
ElementPermissionResource.WithKeys(ActionElementCopy.ActionLetter, id),
AuthorizationPolicies.ElementPermissionByResource);
if (!sourceAuthorizationResult.Succeeded)
{
return Forbidden();
}
// Check Create permission on target (where we're copying to)
AuthorizationResult targetAuthorizationResult = await _authorizationService.AuthorizeResourceAsync(
User,
ElementPermissionResource.WithKeys(ActionElementNew.ActionLetter, copyElementRequestModel.Target?.Id),
AuthorizationPolicies.ElementPermissionByResource);
if (!targetAuthorizationResult.Succeeded)
{
return Forbidden();
}
Attempt<IElement?, ContentEditingOperationStatus> result = await _elementEditingService.CopyAsync(
id,
copyElementRequestModel.Target?.Id,
CurrentUserKey(_backOfficeSecurityAccessor));
return result.Success
? CreatedAtId<ByKeyElementController>(controller => nameof(controller.ByKey), result.Result!.Key)
: ContentEditingOperationStatusResult(result.Status);
}
}
@@ -0,0 +1,68 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.ViewModels.Element;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Api.Management.Controllers.Element;
/// <summary>
/// API controller responsible for handling operations related to the creation of elements in the Umbraco CMS.
/// </summary>
[ApiVersion("1.0")]
public class CreateElementController : CreateElementControllerBase
{
private readonly IElementEditingPresentationFactory _elementEditingPresentationFactory;
private readonly IElementEditingService _elementEditingService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
/// <summary>
/// Initializes a new instance of the <see cref="CreateElementController"/> class.
/// </summary>
/// <param name="authorizationService">Service used to authorize access to element creation operations.</param>
/// <param name="elementEditingPresentationFactory">Factory for creating element editing presentation models.</param>
/// <param name="elementEditingService">Service responsible for element editing functionality.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context.</param>
public CreateElementController(
IAuthorizationService authorizationService,
IElementEditingPresentationFactory elementEditingPresentationFactory,
IElementEditingService elementEditingService,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
: base(authorizationService)
{
_elementEditingPresentationFactory = elementEditingPresentationFactory;
_elementEditingService = elementEditingService;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}
/// <summary>
/// Creates a new element using the specified request model.
/// </summary>
/// <param name="cancellationToken">Token to monitor for cancellation requests.</param>
/// <param name="requestModel">The details of the element to create.</param>
/// <returns>An <see cref="IActionResult"/> representing the result of the operation.</returns>
[HttpPost]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[EndpointSummary("Creates a new element.")]
[EndpointDescription("Creates a new element with the configuration specified in the request model.")]
public async Task<IActionResult> Create(CancellationToken cancellationToken, CreateElementRequestModel requestModel)
=> await HandleRequest(requestModel, async () =>
{
ElementCreateModel model = _elementEditingPresentationFactory.MapCreateModel(requestModel);
Attempt<ElementCreateResult, ContentEditingOperationStatus> result =
await _elementEditingService.CreateAsync(model, CurrentUserKey(_backOfficeSecurityAccessor));
return result.Success
? CreatedAtId<ByKeyElementController>(controller => nameof(controller.ByKey), result.Result.Content!.Key)
: ContentEditingOperationStatusResult(result.Status);
});
}
@@ -0,0 +1,40 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.Element;
using Umbraco.Cms.Core.Actions;
using Umbraco.Cms.Core.Security.Authorization;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Element;
/// <summary>
/// Serves as the base controller for element creation endpoints in the Umbraco CMS Management API.
/// Provides shared functionality for creating elements.
/// </summary>
public abstract class CreateElementControllerBase : ElementControllerBase
{
private readonly IAuthorizationService _authorizationService;
/// <summary>
/// Initializes a new instance of the <see cref="CreateElementControllerBase"/> class.
/// </summary>
/// <param name="authorizationService">Service used to authorize access to element creation operations.</param>
protected CreateElementControllerBase(IAuthorizationService authorizationService)
=> _authorizationService = authorizationService;
protected async Task<IActionResult> HandleRequest(CreateElementRequestModel requestModel, Func<Task<IActionResult>> authorizedHandler)
{
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
User,
ElementPermissionResource.WithKeys(ActionElementNew.ActionLetter, requestModel.Parent?.Id),
AuthorizationPolicies.ElementPermissionByResource);
if (authorizationResult.Succeeded is false)
{
return Forbidden();
}
return await authorizedHandler();
}
}
@@ -0,0 +1,74 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Actions;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Security.Authorization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Element;
/// <summary>
/// API controller responsible for handling requests to delete elements in the Umbraco CMS.
/// </summary>
[ApiVersion("1.0")]
public class DeleteElementController : ElementControllerBase
{
private readonly IAuthorizationService _authorizationService;
private readonly IElementEditingService _elementEditingService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
/// <summary>
/// Initializes a new instance of the <see cref="DeleteElementController"/> class.
/// </summary>
/// <param name="authorizationService">An <see cref="IAuthorizationService"/> used to authorize element deletion requests.</param>
/// <param name="elementEditingService">An <see cref="IElementEditingService"/> used to perform element editing operations.</param>
/// <param name="backOfficeSecurityAccessor">An <see cref="IBackOfficeSecurityAccessor"/> providing access to back office security information.</param>
public DeleteElementController(
IAuthorizationService authorizationService,
IElementEditingService elementEditingService,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
{
_authorizationService = authorizationService;
_elementEditingService = elementEditingService;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}
/// <summary>
/// Deletes the element with the specified unique identifier.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier (GUID) of the element to delete.</param>
/// <returns>An <see cref="IActionResult"/> indicating the outcome of the delete operation.</returns>
[HttpDelete("{id:guid}")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[EndpointSummary("Deletes an element.")]
[EndpointDescription("Deletes an element identified by the provided Id.")]
public async Task<IActionResult> Delete(CancellationToken cancellationToken, Guid id)
{
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
User,
ElementPermissionResource.WithKeys(ActionElementDelete.ActionLetter, id),
AuthorizationPolicies.ElementPermissionByResource);
if (!authorizationResult.Succeeded)
{
return Forbidden();
}
Attempt<IElement?, ContentEditingOperationStatus> result = await _elementEditingService.DeleteAsync(id, CurrentUserKey(_backOfficeSecurityAccessor));
return result.Success
? Ok()
: ContentEditingOperationStatusResult(result.Status);
}
}
@@ -0,0 +1,37 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Controllers.Content;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Api.Management.ViewModels.Element;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Models.ContentPublishing;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.Element;
/// <summary>
/// Serves as the base controller for implementing element management operations within the Umbraco CMS Management API.
/// Provides shared functionality for derived element controllers.
/// </summary>
[VersionedApiBackOfficeRoute(Constants.UdiEntityType.Element)]
[ApiExplorerSettings(GroupName = nameof(Constants.UdiEntityType.Element))]
[Authorize(Policy = AuthorizationPolicies.TreeAccessElements)]
public class ElementControllerBase : ContentControllerBase
{
protected override string EntityName => "element";
protected IActionResult ElementEditingOperationStatusResult<TContentModelBase>(
ContentEditingOperationStatus status,
TContentModelBase requestModel,
ContentValidationResult validationResult)
where TContentModelBase : ContentModelBase<ElementValueModel, ElementVariantRequestModel>
=> ContentEditingOperationStatusResult<TContentModelBase, ElementValueModel, ElementVariantRequestModel>(status, requestModel, validationResult);
protected IActionResult ElementPublishingOperationStatusResult(
ContentPublishingOperationStatus status,
IEnumerable<string>? invalidPropertyAliases = null,
IEnumerable<ContentPublishingBranchItemResult>? failedBranchItems = null)
=> ContentPublishingOperationStatusResult(status, invalidPropertyAliases, failedBranchItems);
}
@@ -0,0 +1,62 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.Folder;
using Umbraco.Cms.Core.Actions;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Security.Authorization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Element.Folder;
/// <summary>
/// Controller for retrieving element folders by their unique key.
/// </summary>
[ApiVersion("1.0")]
public class ByKeyElementFolderController : ElementFolderControllerBase
{
private readonly IAuthorizationService _authorizationService;
/// <summary>
/// Initializes a new instance of the <see cref="ByKeyElementFolderController"/> class.
/// </summary>
/// <param name="authorizationService">Service used to authorize access to element folder resources.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context.</param>
/// <param name="elementContainerService">Service for managing element containers.</param>
public ByKeyElementFolderController(
IAuthorizationService authorizationService,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IElementContainerService elementContainerService)
: base(backOfficeSecurityAccessor, elementContainerService) =>
_authorizationService = authorizationService;
/// <summary>
/// Retrieves an element folder by its unique identifier.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier of the element folder to retrieve.</param>
/// <returns>An <see cref="IActionResult"/> containing the element folder if found.</returns>
[HttpGet("{id:guid}")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(FolderResponseModel), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[EndpointSummary("Gets an element folder.")]
[EndpointDescription("Gets an element folder identified by the provided Id.")]
public async Task<IActionResult> ByKey(CancellationToken cancellationToken, Guid id)
{
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
User,
ElementPermissionResource.WithKeys(ActionElementBrowse.ActionLetter, id),
AuthorizationPolicies.ElementPermissionByResource);
if (!authorizationResult.Succeeded)
{
return Forbidden();
}
return await GetFolderAsync(id);
}
}
@@ -0,0 +1,65 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.Folder;
using Umbraco.Cms.Core.Actions;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Security.Authorization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Element.Folder;
/// <summary>
/// API controller responsible for creating element folders in the Umbraco CMS.
/// </summary>
[ApiVersion("1.0")]
public class CreateElementFolderController : ElementFolderControllerBase
{
private readonly IAuthorizationService _authorizationService;
/// <summary>
/// Initializes a new instance of the <see cref="CreateElementFolderController"/> class.
/// </summary>
/// <param name="authorizationService">Service used to authorize element folder creation operations.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context.</param>
/// <param name="elementContainerService">Service for managing element containers.</param>
public CreateElementFolderController(
IAuthorizationService authorizationService,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IElementContainerService elementContainerService)
: base(backOfficeSecurityAccessor, elementContainerService) =>
_authorizationService = authorizationService;
/// <summary>
/// Creates a new element folder.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="createFolderRequestModel">The model containing the folder details.</param>
/// <returns>An <see cref="IActionResult"/> representing the result of the creation operation.</returns>
[HttpPost]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[EndpointSummary("Creates an element folder.")]
[EndpointDescription("Creates a new element folder with the provided name and parent location.")]
public async Task<IActionResult> Create(CancellationToken cancellationToken, CreateFolderRequestModel createFolderRequestModel)
{
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
User,
ElementPermissionResource.WithKeys(ActionElementNew.ActionLetter, createFolderRequestModel.Parent?.Id),
AuthorizationPolicies.ElementPermissionByResource);
if (!authorizationResult.Succeeded)
{
return Forbidden();
}
return await CreateFolderAsync<ByKeyElementFolderController>(
createFolderRequestModel,
controller => nameof(controller.ByKey));
}
}
@@ -0,0 +1,62 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core.Actions;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Security.Authorization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Element.Folder;
/// <summary>
/// API controller responsible for deleting element folders in the Umbraco CMS.
/// </summary>
[ApiVersion("1.0")]
public class DeleteElementFolderController : ElementFolderControllerBase
{
private readonly IAuthorizationService _authorizationService;
/// <summary>
/// Initializes a new instance of the <see cref="DeleteElementFolderController"/> class.
/// </summary>
/// <param name="authorizationService">Service used to authorize element folder deletion operations.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context.</param>
/// <param name="elementContainerService">Service for managing element containers.</param>
public DeleteElementFolderController(
IAuthorizationService authorizationService,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IElementContainerService elementContainerService)
: base(backOfficeSecurityAccessor, elementContainerService) =>
_authorizationService = authorizationService;
/// <summary>
/// Deletes the element folder with the specified unique identifier.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier of the element folder to delete.</param>
/// <returns>An <see cref="IActionResult"/> indicating the outcome of the delete operation.</returns>
[HttpDelete("{id:guid}")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[EndpointSummary("Deletes an element folder.")]
[EndpointDescription("Deletes an element folder identified by the provided Id.")]
public async Task<IActionResult> Delete(CancellationToken cancellationToken, Guid id)
{
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
User,
ElementPermissionResource.WithKeys(ActionElementDelete.ActionLetter, id),
AuthorizationPolicies.ElementPermissionByResource);
if (!authorizationResult.Succeeded)
{
return Forbidden();
}
return await DeleteFolderAsync(id);
}
}
@@ -0,0 +1,32 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.Element.Folder;
/// <summary>
/// Serves as the base controller for element folder management operations within the Umbraco CMS Management API.
/// Provides shared functionality for derived element folder controllers.
/// </summary>
[VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.Element}/folder")]
[ApiExplorerSettings(GroupName = nameof(Constants.UdiEntityType.Element))]
[Authorize(Policy = AuthorizationPolicies.TreeAccessElements)]
public abstract class ElementFolderControllerBase : FolderManagementControllerBase<IElement>
{
/// <summary>
/// Initializes a new instance of the <see cref="ElementFolderControllerBase"/> class.
/// </summary>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context.</param>
/// <param name="elementContainerService">Service for managing element containers.</param>
protected ElementFolderControllerBase(
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IElementContainerService elementContainerService)
: base(backOfficeSecurityAccessor, elementContainerService)
{
}
}
@@ -0,0 +1,12 @@
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Core;
namespace Umbraco.Cms.Api.Management.Controllers.Element.Folder.Item;
/// <summary>
/// Serves as the base controller for element folder item operations within the Umbraco CMS Management API.
/// </summary>
[VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Item}/{Constants.UdiEntityType.Element}/folder")]
[ApiExplorerSettings(GroupName = nameof(Constants.UdiEntityType.Element))]
public class FolderItemControllerBase : ManagementApiControllerBase;
@@ -0,0 +1,60 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.Folder.Item;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Entities;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.Element.Folder.Item;
/// <summary>
/// API controller responsible for retrieving element folder items by their identifiers.
/// </summary>
[ApiVersion("1.0")]
public class ItemFolderItemController : FolderItemControllerBase
{
private readonly IEntityService _entityService;
private readonly IUmbracoMapper _umbracoMapper;
/// <summary>
/// Initializes a new instance of the <see cref="ItemFolderItemController"/> class.
/// </summary>
/// <param name="entityService">Service for retrieving entity data.</param>
/// <param name="umbracoMapper">Mapper for converting domain models to view models.</param>
public ItemFolderItemController(
IEntityService entityService,
IUmbracoMapper umbracoMapper)
{
_entityService = entityService;
_umbracoMapper = umbracoMapper;
}
/// <summary>
/// Gets a collection of element folder items identified by the provided identifiers.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="ids">The set of unique identifiers of the element folder items to retrieve.</param>
/// <returns>An <see cref="IActionResult"/> containing the collection of element folder items.</returns>
[HttpGet]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(IEnumerable<FolderItemResponseModel>), StatusCodes.Status200OK)]
[EndpointSummary("Gets a collection of element folder items.")]
[EndpointDescription("Gets a collection of element folder items identified by the provided Ids.")]
public Task<IActionResult> Item(
CancellationToken cancellationToken,
[FromQuery(Name = "id")] HashSet<Guid> ids)
{
if (ids.Count is 0)
{
return Task.FromResult<IActionResult>(Ok(Enumerable.Empty<FolderItemResponseModel>()));
}
IEnumerable<IEntitySlim> elements = _entityService
.GetAll([UmbracoObjectTypes.ElementContainer], ids.ToArray());
List<FolderItemResponseModel> responseModels = _umbracoMapper.MapEnumerable<IEntitySlim, FolderItemResponseModel>(elements);
return Task.FromResult<IActionResult>(Ok(responseModels));
}
}
@@ -0,0 +1,89 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.Folder;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Actions;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Security.Authorization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Element.Folder;
/// <summary>
/// API controller responsible for handling move operations on element folders in the Umbraco CMS.
/// </summary>
[ApiVersion("1.0")]
public class MoveElementFolderController : ElementFolderControllerBase
{
private readonly IAuthorizationService _authorizationService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
private readonly IElementContainerService _elementContainerService;
/// <summary>
/// Initializes a new instance of the <see cref="MoveElementFolderController"/> class.
/// </summary>
/// <param name="authorizationService">Service used to authorize element folder move operations.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context.</param>
/// <param name="elementContainerService">Service for managing element containers.</param>
public MoveElementFolderController(
IAuthorizationService authorizationService,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IElementContainerService elementContainerService)
: base(backOfficeSecurityAccessor, elementContainerService)
{
_authorizationService = authorizationService;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
_elementContainerService = elementContainerService;
}
/// <summary>
/// Moves an element folder identified by the specified unique identifier to a different location.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier of the element folder to move.</param>
/// <param name="moveFolderRequestModel">The model containing the target location for the move.</param>
/// <returns>An <see cref="IActionResult"/> representing the outcome of the move operation.</returns>
[HttpPut("{id:guid}/move")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[EndpointSummary("Moves an element folder.")]
[EndpointDescription("Moves an element folder identified by the provided Id to a different location.")]
public async Task<IActionResult> Move(CancellationToken cancellationToken, Guid id, MoveFolderRequestModel moveFolderRequestModel)
{
// Check Move permission on source folder
AuthorizationResult sourceAuthorizationResult = await _authorizationService.AuthorizeResourceAsync(
User,
ElementPermissionResource.WithKeys(ActionElementMove.ActionLetter, id),
AuthorizationPolicies.ElementPermissionByResource);
if (!sourceAuthorizationResult.Succeeded)
{
return Forbidden();
}
// Check Create permission on target (where we're moving to)
AuthorizationResult targetAuthorizationResult = await _authorizationService.AuthorizeResourceAsync(
User,
ElementPermissionResource.WithKeys(ActionElementNew.ActionLetter, moveFolderRequestModel.Target?.Id),
AuthorizationPolicies.ElementPermissionByResource);
if (!targetAuthorizationResult.Succeeded)
{
return Forbidden();
}
Attempt<EntityContainerOperationStatus> result = await _elementContainerService
.MoveAsync(id, moveFolderRequestModel.Target?.Id, CurrentUserKey(_backOfficeSecurityAccessor));
return result.Success
? Ok()
: OperationStatusResult(result.Result);
}
}
@@ -0,0 +1,75 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Actions;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Security.Authorization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Element.Folder;
/// <summary>
/// API controller responsible for moving element folders to the recycle bin in the Umbraco CMS.
/// </summary>
[ApiVersion("1.0")]
public class MoveToRecycleBinElementFolderController : ElementFolderControllerBase
{
private readonly IAuthorizationService _authorizationService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
private readonly IElementContainerService _elementContainerService;
/// <summary>
/// Initializes a new instance of the <see cref="MoveToRecycleBinElementFolderController"/> class.
/// </summary>
/// <param name="authorizationService">Service used to authorize recycle bin operations.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context.</param>
/// <param name="elementContainerService">Service for managing element containers.</param>
public MoveToRecycleBinElementFolderController(
IAuthorizationService authorizationService,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IElementContainerService elementContainerService)
: base(backOfficeSecurityAccessor, elementContainerService)
{
_authorizationService = authorizationService;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
_elementContainerService = elementContainerService;
}
/// <summary>
/// Moves an element folder identified by the specified unique identifier to the recycle bin.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier of the element folder to move to the recycle bin.</param>
/// <returns>An <see cref="IActionResult"/> representing the outcome of the operation.</returns>
[HttpPut("{id:guid}/move-to-recycle-bin")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[EndpointSummary("Moves an element folder to the recycle bin.")]
[EndpointDescription("Moves an element folder identified by the provided Id to the recycle bin.")]
public async Task<IActionResult> Move(CancellationToken cancellationToken, Guid id)
{
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
User,
ElementPermissionResource.WithKeys(ActionElementDelete.ActionLetter, id),
AuthorizationPolicies.ElementPermissionByResource);
if (!authorizationResult.Succeeded)
{
return Forbidden();
}
Attempt<EntityContainerOperationStatus> result = await _elementContainerService
.MoveToRecycleBinAsync(id, CurrentUserKey(_backOfficeSecurityAccessor));
return result.Success
? Ok()
: OperationStatusResult(result.Result);
}
}
@@ -0,0 +1,64 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.Folder;
using Umbraco.Cms.Core.Actions;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Security.Authorization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Element.Folder;
/// <summary>
/// API controller responsible for updating element folders in the Umbraco CMS.
/// </summary>
[ApiVersion("1.0")]
public class UpdateElementFolderController : ElementFolderControllerBase
{
private readonly IAuthorizationService _authorizationService;
/// <summary>
/// Initializes a new instance of the <see cref="UpdateElementFolderController"/> class.
/// </summary>
/// <param name="authorizationService">Service used to authorize element folder update operations.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context.</param>
/// <param name="elementContainerService">Service for managing element containers.</param>
public UpdateElementFolderController(
IAuthorizationService authorizationService,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IElementContainerService elementContainerService)
: base(backOfficeSecurityAccessor, elementContainerService) =>
_authorizationService = authorizationService;
/// <summary>
/// Updates the element folder with the specified unique identifier.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier of the element folder to update.</param>
/// <param name="updateFolderResponseModel">The model containing the updated folder details.</param>
/// <returns>An <see cref="IActionResult"/> representing the outcome of the update operation.</returns>
[HttpPut("{id:guid}")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[EndpointSummary("Updates an element folder.")]
[EndpointDescription("Updates an element folder identified by the provided Id with the details provided in the request model.")]
public async Task<IActionResult> Update(CancellationToken cancellationToken, Guid id, UpdateFolderResponseModel updateFolderResponseModel)
{
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
User,
ElementPermissionResource.WithKeys(ActionElementUpdate.ActionLetter, id),
AuthorizationPolicies.ElementPermissionByResource);
if (!authorizationResult.Succeeded)
{
return Forbidden();
}
return await UpdateFolderAsync(id, updateFolderResponseModel);
}
}
@@ -0,0 +1,81 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.ViewModels.AuditLog;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Actions;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Security.Authorization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Element;
/// <summary>
/// API controller responsible for retrieving audit log entries for elements in the Umbraco CMS.
/// </summary>
[ApiVersion("1.0")]
public class GetAuditLogElementController : ElementControllerBase
{
private readonly IAuthorizationService _authorizationService;
private readonly IAuditService _auditService;
private readonly IAuditLogPresentationFactory _auditLogPresentationFactory;
/// <summary>
/// Initializes a new instance of the <see cref="GetAuditLogElementController"/> class.
/// </summary>
/// <param name="authorizationService">Service used to authorize access to audit log data.</param>
/// <param name="auditService">Service for retrieving audit log entries.</param>
/// <param name="auditLogPresentationFactory">Factory for creating audit log presentation models.</param>
public GetAuditLogElementController(
IAuthorizationService authorizationService,
IAuditService auditService,
IAuditLogPresentationFactory auditLogPresentationFactory)
{
_authorizationService = authorizationService;
_auditService = auditService;
_auditLogPresentationFactory = auditLogPresentationFactory;
}
/// <summary>
/// Retrieves a paginated collection of audit log entries for the specified element.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier of the element to retrieve audit logs for.</param>
/// <param name="orderDirection">The sort order direction for the results.</param>
/// <param name="sinceDate">Optional date filter to retrieve entries since a specific date.</param>
/// <param name="skip">The number of entries to skip for pagination.</param>
/// <param name="take">The number of entries to return for pagination.</param>
/// <returns>An <see cref="IActionResult"/> containing a paginated collection of audit log entries.</returns>
[MapToApiVersion("1.0")]
[HttpGet("{id:guid}/audit-log")]
[ProducesResponseType(typeof(PagedViewModel<AuditLogResponseModel>), StatusCodes.Status200OK)]
[EndpointSummary("Gets the audit log for an element.")]
[EndpointDescription("Gets a paginated collection of audit log entries for the element identified by the provided Id.")]
public async Task<IActionResult> GetAuditLog(CancellationToken cancellationToken, Guid id, Direction orderDirection = Direction.Descending, DateTimeOffset? sinceDate = null, int skip = 0, int take = 100)
{
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
User,
ElementPermissionResource.WithKeys(ActionElementBrowse.ActionLetter, id),
AuthorizationPolicies.ElementPermissionByResource);
if (!authorizationResult.Succeeded)
{
return Forbidden();
}
PagedModel<IAuditItem> result = await _auditService.GetItemsByKeyAsync(id, UmbracoObjectTypes.Element, skip, take, orderDirection, sinceDate);
IEnumerable<AuditLogResponseModel> mapped = _auditLogPresentationFactory.CreateAuditLogViewModel(result.Items);
var viewModel = new PagedViewModel<AuditLogResponseModel>
{
Total = result.Total,
Items = mapped,
};
return Ok(viewModel);
}
}
@@ -0,0 +1,12 @@
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Core;
namespace Umbraco.Cms.Api.Management.Controllers.Element.Item;
/// <summary>
/// Serves as the base controller for element item operations within the Umbraco CMS Management API.
/// </summary>
[VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Item}/{Constants.UdiEntityType.Element}")]
[ApiExplorerSettings(GroupName = nameof(Constants.UdiEntityType.Element))]
public class ElementItemControllerBase : ManagementApiControllerBase;
@@ -0,0 +1,62 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.ViewModels.Element.Item;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Entities;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.Element.Item;
/// <summary>
/// API controller responsible for retrieving element items by their identifiers.
/// </summary>
[ApiVersion("1.0")]
public class ItemElementItemController : ElementItemControllerBase
{
private readonly IEntityService _entityService;
private readonly IElementPresentationFactory _elementPresentationFactory;
/// <summary>
/// Initializes a new instance of the <see cref="ItemElementItemController"/> class.
/// </summary>
/// <param name="entityService">Service for retrieving entity data.</param>
/// <param name="elementPresentationFactory">Factory responsible for creating element presentation models.</param>
public ItemElementItemController(
IEntityService entityService,
IElementPresentationFactory elementPresentationFactory)
{
_entityService = entityService;
_elementPresentationFactory = elementPresentationFactory;
}
/// <summary>
/// Gets a collection of element items identified by the provided identifiers.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="ids">The set of unique identifiers of the element items to retrieve.</param>
/// <returns>An <see cref="IActionResult"/> containing the collection of element items.</returns>
[HttpGet]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(IEnumerable<ElementItemResponseModel>), StatusCodes.Status200OK)]
[EndpointSummary("Gets a collection of element items.")]
[EndpointDescription("Gets a collection of element items identified by the provided Ids.")]
public async Task<IActionResult> Item(
CancellationToken cancellationToken,
[FromQuery(Name = "id")] HashSet<Guid> ids)
{
if (ids.Count is 0)
{
return Ok(Enumerable.Empty<ElementItemResponseModel>());
}
IEnumerable<IElementEntitySlim> elements = _entityService
.GetAll(UmbracoObjectTypes.Element, ids.ToArray())
.OfType<IElementEntitySlim>();
IEnumerable<Task<ElementItemResponseModel>> tasks = elements.Select(_elementPresentationFactory.CreateItemResponseModelAsync);
ElementItemResponseModel[] responseModels = await Task.WhenAll(tasks);
return Ok(responseModels);
}
}
@@ -0,0 +1,89 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.Element;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Actions;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Security.Authorization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Element;
/// <summary>
/// API controller responsible for handling move operations on elements in the Umbraco CMS.
/// </summary>
[ApiVersion("1.0")]
public class MoveElementController : ElementControllerBase
{
private readonly IAuthorizationService _authorizationService;
private readonly IElementEditingService _elementEditingService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
/// <summary>
/// Initializes a new instance of the <see cref="MoveElementController"/> class.
/// </summary>
/// <param name="authorizationService">Service used to authorize element move operations.</param>
/// <param name="elementEditingService">Service responsible for element editing operations.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context.</param>
public MoveElementController(
IAuthorizationService authorizationService,
IElementEditingService elementEditingService,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
{
_authorizationService = authorizationService;
_elementEditingService = elementEditingService;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}
/// <summary>
/// Moves an element identified by the specified unique identifier to a different location.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier of the element to move.</param>
/// <param name="moveElementRequestModel">The model containing the target location for the move.</param>
/// <returns>An <see cref="IActionResult"/> representing the outcome of the move operation.</returns>
[HttpPut("{id:guid}/move")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[EndpointSummary("Moves an element.")]
[EndpointDescription("Moves an element identified by the provided Id to a different location.")]
public async Task<IActionResult> Move(CancellationToken cancellationToken, Guid id, MoveElementRequestModel moveElementRequestModel)
{
// Check Move permission on source element
AuthorizationResult sourceAuthorizationResult = await _authorizationService.AuthorizeResourceAsync(
User,
ElementPermissionResource.WithKeys(ActionElementMove.ActionLetter, id),
AuthorizationPolicies.ElementPermissionByResource);
if (!sourceAuthorizationResult.Succeeded)
{
return Forbidden();
}
// Check Create permission on target (where we're moving to)
AuthorizationResult targetAuthorizationResult = await _authorizationService.AuthorizeResourceAsync(
User,
ElementPermissionResource.WithKeys(ActionElementNew.ActionLetter, moveElementRequestModel.Target?.Id),
AuthorizationPolicies.ElementPermissionByResource);
if (!targetAuthorizationResult.Succeeded)
{
return Forbidden();
}
Attempt<ContentEditingOperationStatus> result = await _elementEditingService.MoveAsync(
id,
moveElementRequestModel.Target?.Id,
CurrentUserKey(_backOfficeSecurityAccessor));
return result.Success
? Ok()
: ContentEditingOperationStatusResult(result.Result);
}
}
@@ -0,0 +1,72 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Actions;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Security.Authorization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Element;
/// <summary>
/// API controller responsible for handling move-to-recycle-bin operations on elements in the Umbraco CMS.
/// </summary>
[ApiVersion("1.0")]
public class MoveToRecycleBinElementController : ElementControllerBase
{
private readonly IAuthorizationService _authorizationService;
private readonly IElementEditingService _elementEditingService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
/// <summary>
/// Initializes a new instance of the <see cref="MoveToRecycleBinElementController"/> class.
/// </summary>
/// <param name="authorizationService">Service used to authorize recycle bin operations.</param>
/// <param name="elementEditingService">Service responsible for element editing operations.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context.</param>
public MoveToRecycleBinElementController(
IAuthorizationService authorizationService,
IElementEditingService elementEditingService,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
{
_authorizationService = authorizationService;
_elementEditingService = elementEditingService;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}
/// <summary>
/// Moves an element identified by the specified unique identifier to the recycle bin.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier of the element to move to the recycle bin.</param>
/// <returns>An <see cref="IActionResult"/> representing the outcome of the operation.</returns>
[HttpPut("{id:guid}/move-to-recycle-bin")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[EndpointSummary("Moves an element to the recycle bin.")]
[EndpointDescription("Moves an element identified by the provided Id to the recycle bin.")]
public async Task<IActionResult> MoveToRecycleBin(CancellationToken cancellationToken, Guid id)
{
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
User,
ElementPermissionResource.WithKeys(ActionElementDelete.ActionLetter, id),
AuthorizationPolicies.ElementPermissionByResource);
if (!authorizationResult.Succeeded)
{
return Forbidden();
}
Attempt<ContentEditingOperationStatus> result = await _elementEditingService.MoveToRecycleBinAsync(id, CurrentUserKey(_backOfficeSecurityAccessor));
return result.Success
? Ok()
: ContentEditingOperationStatusResult(result.Result);
}
}
@@ -0,0 +1,96 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.ViewModels.Document;
using Umbraco.Cms.Api.Management.ViewModels.Element;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Actions;
using Umbraco.Cms.Core.Models.ContentPublishing;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Security.Authorization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Element;
/// <summary>
/// API controller responsible for handling publish operations on elements in the Umbraco CMS.
/// </summary>
[ApiVersion("1.0")]
public class PublishElementController : ElementControllerBase
{
private readonly IAuthorizationService _authorizationService;
private readonly IElementPublishingService _elementPublishingService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
private readonly IElementPresentationFactory _elementPresentationFactory;
/// <summary>
/// Initializes a new instance of the <see cref="PublishElementController"/> class.
/// </summary>
/// <param name="authorizationService">Service used to authorize element publish operations.</param>
/// <param name="elementPublishingService">Service responsible for publishing elements.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context.</param>
/// <param name="elementPresentationFactory">Factory used for creating culture publish schedule models.</param>
public PublishElementController(
IAuthorizationService authorizationService,
IElementPublishingService elementPublishingService,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IElementPresentationFactory elementPresentationFactory)
{
_authorizationService = authorizationService;
_elementPublishingService = elementPublishingService;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
_elementPresentationFactory = elementPresentationFactory;
}
/// <summary>
/// Publishes an element identified by the specified unique identifier.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier of the element to publish.</param>
/// <param name="requestModel">The model containing the publish schedule configuration.</param>
/// <returns>An <see cref="IActionResult"/> representing the outcome of the publish operation.</returns>
[HttpPut("{id:guid}/publish")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[EndpointSummary("Publishes an element.")]
[EndpointDescription("Publishes an element identified by the provided Id.")]
public async Task<IActionResult> Publish(CancellationToken cancellationToken, Guid id, PublishElementRequestModel requestModel)
{
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
User,
ElementPermissionResource.WithKeys(
ActionElementPublish.ActionLetter,
id,
requestModel.PublishSchedules
.Where(x => x.Culture is not null)
.Select(x => x.Culture!)),
AuthorizationPolicies.ElementPermissionByResource);
if (!authorizationResult.Succeeded)
{
return Forbidden();
}
Attempt<List<CulturePublishScheduleModel>, ContentPublishingOperationStatus> modelResult = _elementPresentationFactory.CreateCulturePublishScheduleModels(requestModel);
if (modelResult.Success is false)
{
return ElementPublishingOperationStatusResult(modelResult.Status);
}
Attempt<ContentPublishingResult, ContentPublishingOperationStatus> attempt = await _elementPublishingService.PublishAsync(
id,
modelResult.Result,
CurrentUserKey(_backOfficeSecurityAccessor));
return attempt.Success
? Ok()
: ElementPublishingOperationStatusResult(attempt.Status, attempt.Result.InvalidPropertyAliases);
}
}
@@ -0,0 +1,48 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.ViewModels.Element.RecycleBin;
namespace Umbraco.Cms.Api.Management.Controllers.Element.RecycleBin;
/// <summary>
/// API controller responsible for retrieving child elements within the element recycle bin.
/// </summary>
[ApiVersion("1.0")]
public class ChildrenElementRecycleBinController : ElementRecycleBinControllerBase
{
/// <summary>
/// Initializes a new instance of the <see cref="ChildrenElementRecycleBinController"/> class.
/// </summary>
/// <param name="entityService">Service for retrieving entity data.</param>
/// <param name="elementPresentationFactory">Factory responsible for creating element presentation models.</param>
public ChildrenElementRecycleBinController(
IEntityService entityService,
IElementPresentationFactory elementPresentationFactory)
: base(entityService, elementPresentationFactory)
{
}
/// <summary>
/// Gets a paginated collection of child elements in the recycle bin for the specified parent.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="parentId">The unique identifier of the parent element in the recycle bin.</param>
/// <param name="skip">The number of items to skip for pagination.</param>
/// <param name="take">The number of items to return for pagination.</param>
/// <returns>A paginated collection of element recycle bin items.</returns>
[HttpGet("children")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedViewModel<ElementRecycleBinItemResponseModel>), StatusCodes.Status200OK)]
[EndpointSummary("Gets a collection of elements in the recycle bin.")]
[EndpointDescription("Gets a paginated collection of elements that are children of the provided parent in the recycle bin.")]
public async Task<ActionResult<PagedViewModel<ElementRecycleBinItemResponseModel>>> Children(
CancellationToken cancellationToken,
Guid parentId,
int skip = 0,
int take = 100)
=> await GetChildren(parentId, skip, take);
}
@@ -0,0 +1,80 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Actions;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Security.Authorization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Element.RecycleBin;
/// <summary>
/// API controller responsible for permanently deleting element folders from the recycle bin.
/// </summary>
[ApiVersion("1.0")]
public class DeleteElementFolderRecycleBinController : ElementRecycleBinControllerBase
{
private readonly IAuthorizationService _authorizationService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
private readonly IElementContainerService _elementContainerService;
/// <summary>
/// Initializes a new instance of the <see cref="DeleteElementFolderRecycleBinController"/> class.
/// </summary>
/// <param name="authorizationService">Service used to authorize recycle bin deletion operations.</param>
/// <param name="entityService">Service for retrieving entity data.</param>
/// <param name="elementPresentationFactory">Factory responsible for creating element presentation models.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context.</param>
/// <param name="elementContainerService">Service for managing element containers.</param>
public DeleteElementFolderRecycleBinController(
IAuthorizationService authorizationService,
IEntityService entityService,
IElementPresentationFactory elementPresentationFactory,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IElementContainerService elementContainerService)
: base(entityService, elementPresentationFactory)
{
_authorizationService = authorizationService;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
_elementContainerService = elementContainerService;
}
/// <summary>
/// Permanently deletes an element folder from the recycle bin.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier of the element folder to permanently delete.</param>
/// <returns>An <see cref="IActionResult"/> indicating the outcome of the delete operation.</returns>
[HttpDelete("folder/{id:guid}")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[EndpointSummary("Deletes an element folder from the recycle bin.")]
[EndpointDescription("Permanently deletes an element folder identified by the provided Id from the recycle bin.")]
public async Task<IActionResult> Delete(CancellationToken cancellationToken, Guid id)
{
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
User,
ElementPermissionResource.RecycleBin(ActionElementDelete.ActionLetter),
AuthorizationPolicies.ElementPermissionByResource);
if (!authorizationResult.Succeeded)
{
return Forbidden();
}
Attempt<EntityContainer?, EntityContainerOperationStatus> result = await _elementContainerService.DeleteFromRecycleBinAsync(id, CurrentUserKey(_backOfficeSecurityAccessor));
return result.Success
? Ok()
: OperationStatusResult(result.Status);
}
}
@@ -0,0 +1,80 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Actions;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Security.Authorization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Element.RecycleBin;
/// <summary>
/// API controller responsible for permanently deleting elements from the recycle bin.
/// </summary>
[ApiVersion("1.0")]
public class DeleteElementRecycleBinController : ElementRecycleBinControllerBase
{
private readonly IAuthorizationService _authorizationService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
private readonly IElementEditingService _elementEditingService;
/// <summary>
/// Initializes a new instance of the <see cref="DeleteElementRecycleBinController"/> class.
/// </summary>
/// <param name="authorizationService">Service used to authorize recycle bin deletion operations.</param>
/// <param name="entityService">Service for retrieving entity data.</param>
/// <param name="elementPresentationFactory">Factory responsible for creating element presentation models.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context.</param>
/// <param name="elementEditingService">Service responsible for element editing operations.</param>
public DeleteElementRecycleBinController(
IAuthorizationService authorizationService,
IEntityService entityService,
IElementPresentationFactory elementPresentationFactory,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IElementEditingService elementEditingService)
: base(entityService, elementPresentationFactory)
{
_authorizationService = authorizationService;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
_elementEditingService = elementEditingService;
}
/// <summary>
/// Permanently deletes an element from the recycle bin.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier of the element to permanently delete.</param>
/// <returns>An <see cref="IActionResult"/> indicating the outcome of the delete operation.</returns>
[HttpDelete("{id:guid}")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[EndpointSummary("Deletes an element from the recycle bin.")]
[EndpointDescription("Permanently deletes an element identified by the provided Id from the recycle bin.")]
public async Task<IActionResult> Delete(CancellationToken cancellationToken, Guid id)
{
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
User,
ElementPermissionResource.RecycleBin(ActionElementDelete.ActionLetter),
AuthorizationPolicies.ElementPermissionByResource);
if (!authorizationResult.Succeeded)
{
return Forbidden();
}
Attempt<IElement?, ContentEditingOperationStatus> result = await _elementEditingService.DeleteFromRecycleBinAsync(id, CurrentUserKey(_backOfficeSecurityAccessor));
return result.Success
? Ok()
: ContentEditingOperationStatusResult(result.Status);
}
}
@@ -0,0 +1,104 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Controllers.Element.Folder;
using Umbraco.Cms.Api.Management.Controllers.RecycleBin;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.Filters;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Api.Management.ViewModels.Element.RecycleBin;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Entities;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.Element.RecycleBin;
/// <summary>
/// Serves as the base controller for element recycle bin operations within the Umbraco CMS Management API.
/// Provides shared functionality for derived element recycle bin controllers.
/// </summary>
[VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.RecycleBin}/{Constants.UdiEntityType.Element}")]
[RequireElementTreeRootAccess]
[ApiExplorerSettings(GroupName = nameof(Constants.UdiEntityType.Element))]
[Authorize(Policy = AuthorizationPolicies.TreeAccessElements)]
public class ElementRecycleBinControllerBase : RecycleBinControllerBase<ElementRecycleBinItemResponseModel>
{
/// <inheritdoc />
protected override string EntityName => "element";
private readonly IEntityService _entityService;
private readonly IElementPresentationFactory _elementPresentationFactory;
/// <summary>
/// Initializes a new instance of the <see cref="ElementRecycleBinControllerBase"/> class.
/// </summary>
/// <param name="entityService">Service for retrieving entity data.</param>
/// <param name="elementPresentationFactory">Factory responsible for creating element presentation models.</param>
public ElementRecycleBinControllerBase( IEntityService entityService, IElementPresentationFactory elementPresentationFactory)
: base(entityService)
{
_entityService = entityService;
_elementPresentationFactory = elementPresentationFactory;
}
protected override UmbracoObjectTypes ItemObjectType => UmbracoObjectTypes.Element;
protected override Guid RecycleBinRootKey => Constants.System.RecycleBinElementKey;
protected override async Task<ElementRecycleBinItemResponseModel> MapRecycleBinViewModelAsync(Guid? parentId, IEntitySlim entity)
{
ElementRecycleBinItemResponseModel responseModel = await base.MapRecycleBinViewModelAsync(parentId, entity);
responseModel.Name = entity.Name ?? string.Empty;
if (entity is IElementEntitySlim elementEntitySlim)
{
responseModel.Variants = await _elementPresentationFactory.CreateVariantsItemResponseModelsAsync(elementEntitySlim);
responseModel.DocumentType = _elementPresentationFactory.CreateDocumentTypeReferenceResponseModel(elementEntitySlim);
responseModel.IsFolder = false;
}
else
{
responseModel.Variants = [];
responseModel.IsFolder = true;
}
return responseModel;
}
protected override IEntitySlim[] GetPagedRootEntities(int skip, int take, out long totalItems)
=> GetPagedChildEntities(RecycleBinRootKey, skip, take, out totalItems);
protected override IEntitySlim[] GetPagedChildEntities(Guid parentKey, int skip, int take, out long totalItems)
{
IEntitySlim[] rootEntities = _entityService
.GetPagedChildren(
parentKey,
parentObjectTypes: [UmbracoObjectTypes.ElementContainer],
childObjectTypes: [UmbracoObjectTypes.ElementContainer, ItemObjectType],
skip: skip,
take: take,
trashed: true,
out totalItems)
.ToArray();
return rootEntities;
}
protected override IEntitySlim[] GetSiblingEntities(Guid target, int before, int after, out long totalBefore, out long totalAfter) =>
_entityService
.GetTrashedSiblings(
target,
objectTypes: [UmbracoObjectTypes.ElementContainer, ItemObjectType],
before,
after,
out totalBefore,
out totalAfter,
ordering: Ordering.By(nameof(Infrastructure.Persistence.Dtos.NodeDto.Text)))
.ToArray();
protected IActionResult OperationStatusResult(EntityContainerOperationStatus status)
=> ElementFolderControllerBase.OperationStatusResult(status);
}
@@ -0,0 +1,76 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Actions;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Security.Authorization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Element.RecycleBin;
/// <summary>
/// API controller responsible for emptying the element recycle bin in the Umbraco CMS.
/// </summary>
[ApiVersion("1.0")]
public class EmptyElementRecycleBinController : ElementRecycleBinControllerBase
{
private readonly IAuthorizationService _authorizationService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
private readonly IElementContainerService _elementContainerService;
/// <summary>
/// Initializes a new instance of the <see cref="EmptyElementRecycleBinController"/> class.
/// </summary>
/// <param name="authorizationService">Service used to authorize recycle bin operations.</param>
/// <param name="entityService">Service for retrieving entity data.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context.</param>
/// <param name="elementContainerService">Service for managing element containers.</param>
/// <param name="elementPresentationFactory">Factory responsible for creating element presentation models.</param>
public EmptyElementRecycleBinController(
IAuthorizationService authorizationService,
IEntityService entityService,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IElementContainerService elementContainerService,
IElementPresentationFactory elementPresentationFactory)
: base(entityService, elementPresentationFactory)
{
_authorizationService = authorizationService;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
_elementContainerService = elementContainerService;
}
/// <summary>
/// Permanently deletes all elements in the recycle bin.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <returns>An <see cref="IActionResult"/> indicating the outcome of the operation.</returns>
[HttpDelete]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[EndpointSummary("Empties the element recycle bin.")]
[EndpointDescription("Permanently deletes all elements in the recycle bin. This operation cannot be undone.")]
public async Task<IActionResult> EmptyRecycleBin(CancellationToken cancellationToken)
{
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
User,
ElementPermissionResource.RecycleBin(ActionElementDelete.ActionLetter),
AuthorizationPolicies.ElementPermissionByResource);
if (!authorizationResult.Succeeded)
{
return Forbidden();
}
Attempt<EntityContainerOperationStatus> result = await _elementContainerService.EmptyRecycleBinAsync(CurrentUserKey(_backOfficeSecurityAccessor));
return result.Success
? Ok()
: OperationStatusResult(result.Result);
}
}
@@ -0,0 +1,80 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.ViewModels;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Actions;
using Umbraco.Cms.Core.Models.Entities;
using Umbraco.Cms.Core.Security.Authorization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.Querying.RecycleBin;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Element.RecycleBin;
/// <summary>
/// API controller responsible for retrieving the original parent of an element folder in the recycle bin.
/// </summary>
[ApiVersion("1.0")]
public class OriginalParentElementFolderRecycleBinController : ElementRecycleBinControllerBase
{
private readonly IAuthorizationService _authorizationService;
private readonly IElementRecycleBinQueryService _elementRecycleBinQueryService;
/// <summary>
/// Initializes a new instance of the <see cref="OriginalParentElementFolderRecycleBinController"/> class.
/// </summary>
/// <param name="entityService">Service for retrieving entity data.</param>
/// <param name="elementPresentationFactory">Factory responsible for creating element presentation models.</param>
/// <param name="authorizationService">Service used to authorize recycle bin operations.</param>
/// <param name="elementRecycleBinQueryService">Service for querying the element recycle bin.</param>
public OriginalParentElementFolderRecycleBinController(
IEntityService entityService,
IElementPresentationFactory elementPresentationFactory,
IAuthorizationService authorizationService,
IElementRecycleBinQueryService elementRecycleBinQueryService)
: base(entityService, elementPresentationFactory)
{
_authorizationService = authorizationService;
_elementRecycleBinQueryService = elementRecycleBinQueryService;
}
/// <summary>
/// Gets the original parent location of an element folder before it was moved to the recycle bin.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier of the element folder in the recycle bin.</param>
/// <returns>An <see cref="IActionResult"/> containing the original parent reference, or null if the parent was root.</returns>
[HttpGet("folder/{id:guid}/original-parent")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(ReferenceByIdModel), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[EndpointSummary("Gets the original parent of an element folder in the recycle bin.")]
[EndpointDescription("Gets the original parent location of an element folder before it was moved to the recycle bin.")]
public async Task<IActionResult> OriginalParentFolder(CancellationToken cancellationToken, Guid id)
{
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
User,
ElementPermissionResource.RecycleBin(ActionElementBrowse.ActionLetter),
AuthorizationPolicies.ElementPermissionByResource);
if (!authorizationResult.Succeeded)
{
return Forbidden();
}
Attempt<IEntitySlim?, RecycleBinQueryResultType> getParentAttempt = await _elementRecycleBinQueryService.GetOriginalParentForContainerAsync(id);
return getParentAttempt.Success switch
{
true when getParentAttempt.Status == RecycleBinQueryResultType.Success
=> Ok(new ReferenceByIdModel(getParentAttempt.Result!.Key)),
true when getParentAttempt.Status == RecycleBinQueryResultType.ParentIsRoot
=> Ok(null),
_ => MapRecycleBinQueryAttemptFailure(getParentAttempt.Status, "element folder"),
};
}
}
@@ -0,0 +1,80 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.ViewModels;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Actions;
using Umbraco.Cms.Core.Models.Entities;
using Umbraco.Cms.Core.Security.Authorization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.Querying.RecycleBin;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Element.RecycleBin;
/// <summary>
/// API controller responsible for retrieving the original parent of an element in the recycle bin.
/// </summary>
[ApiVersion("1.0")]
public class OriginalParentElementRecycleBinController : ElementRecycleBinControllerBase
{
private readonly IAuthorizationService _authorizationService;
private readonly IElementRecycleBinQueryService _elementRecycleBinQueryService;
/// <summary>
/// Initializes a new instance of the <see cref="OriginalParentElementRecycleBinController"/> class.
/// </summary>
/// <param name="entityService">Service for retrieving entity data.</param>
/// <param name="elementPresentationFactory">Factory responsible for creating element presentation models.</param>
/// <param name="authorizationService">Service used to authorize recycle bin operations.</param>
/// <param name="elementRecycleBinQueryService">Service for querying the element recycle bin.</param>
public OriginalParentElementRecycleBinController(
IEntityService entityService,
IElementPresentationFactory elementPresentationFactory,
IAuthorizationService authorizationService,
IElementRecycleBinQueryService elementRecycleBinQueryService)
: base(entityService, elementPresentationFactory)
{
_authorizationService = authorizationService;
_elementRecycleBinQueryService = elementRecycleBinQueryService;
}
/// <summary>
/// Gets the original parent location of an element before it was moved to the recycle bin.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier of the element in the recycle bin.</param>
/// <returns>An <see cref="IActionResult"/> containing the original parent reference, or null if the parent was root.</returns>
[HttpGet("{id:guid}/original-parent")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(ReferenceByIdModel), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[EndpointSummary("Gets the original parent of an element in the recycle bin.")]
[EndpointDescription("Gets the original parent location of an element before it was moved to the recycle bin.")]
public async Task<IActionResult> OriginalParent(CancellationToken cancellationToken, Guid id)
{
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
User,
ElementPermissionResource.RecycleBin(ActionElementBrowse.ActionLetter),
AuthorizationPolicies.ElementPermissionByResource);
if (!authorizationResult.Succeeded)
{
return Forbidden();
}
Attempt<IEntitySlim?, RecycleBinQueryResultType> getParentAttempt = await _elementRecycleBinQueryService.GetOriginalParentAsync(id);
return getParentAttempt.Success switch
{
true when getParentAttempt.Status == RecycleBinQueryResultType.Success
=> Ok(new ReferenceByIdModel(getParentAttempt.Result!.Key)),
true when getParentAttempt.Status == RecycleBinQueryResultType.ParentIsRoot
=> Ok(null),
_ => MapRecycleBinQueryAttemptFailure(getParentAttempt.Status, "element"),
};
}
}
@@ -0,0 +1,67 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.Element.RecycleBin;
/// <summary>
/// API controller responsible for retrieving tracked references for elements in the recycle bin.
/// </summary>
[ApiVersion("1.0")]
public class ReferencedByElementRecycleBinController : ElementRecycleBinControllerBase
{
private readonly ITrackedReferencesService _trackedReferencesService;
private readonly IRelationTypePresentationFactory _relationTypePresentationFactory;
/// <summary>
/// Initializes a new instance of the <see cref="ReferencedByElementRecycleBinController"/> class.
/// </summary>
/// <param name="entityService">Service for retrieving entity data.</param>
/// <param name="elementPresentationFactory">Factory responsible for creating element presentation models.</param>
/// <param name="trackedReferencesService">Service for tracking content references.</param>
/// <param name="relationTypePresentationFactory">Factory for creating relation type presentation models.</param>
public ReferencedByElementRecycleBinController(
IEntityService entityService,
IElementPresentationFactory elementPresentationFactory,
ITrackedReferencesService trackedReferencesService,
IRelationTypePresentationFactory relationTypePresentationFactory)
: base(entityService, elementPresentationFactory)
{
_trackedReferencesService = trackedReferencesService;
_relationTypePresentationFactory = relationTypePresentationFactory;
}
/// <summary>
/// Gets a paginated collection of tracked references for all items in the element recycle bin.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="skip">The number of items to skip for pagination.</param>
/// <param name="take">The number of items to return for pagination.</param>
/// <returns>A paginated collection of reference response models.</returns>
[HttpGet("referenced-by")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedViewModel<IReferenceResponseModel>), StatusCodes.Status200OK)]
[EndpointSummary("Gets items referencing an element in the recycle bin.")]
[EndpointDescription("Gets a paginated collection of items that reference the element in the recycle bin.")]
public async Task<ActionResult<PagedViewModel<IReferenceResponseModel>>> ReferencedBy(
CancellationToken cancellationToken,
int skip = 0,
int take = 20)
{
PagedModel<RelationItemModel> relationItems = await _trackedReferencesService
.GetPagedRelationsForRecycleBinAsync(UmbracoObjectTypes.Element, skip, take, true);
var pagedViewModel = new PagedViewModel<IReferenceResponseModel>
{
Total = relationItems.Total,
Items = await _relationTypePresentationFactory.CreateReferenceResponseModelsAsync(relationItems.Items),
};
return pagedViewModel;
}
}
@@ -0,0 +1,84 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.ViewModels.Folder;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Actions;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Security.Authorization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Element.RecycleBin;
/// <summary>
/// API controller responsible for restoring element folders from the recycle bin.
/// </summary>
[ApiVersion("1.0")]
public class RestoreElementFolderRecycleBinController : ElementRecycleBinControllerBase
{
private readonly IAuthorizationService _authorizationService;
private readonly IElementContainerService _elementContainerService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
/// <summary>
/// Initializes a new instance of the <see cref="RestoreElementFolderRecycleBinController"/> class.
/// </summary>
/// <param name="authorizationService">Service used to authorize restore operations.</param>
/// <param name="elementContainerService">Service for managing element containers.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context.</param>
/// <param name="entityService">Service for retrieving entity data.</param>
/// <param name="elementPresentationFactory">Factory responsible for creating element presentation models.</param>
public RestoreElementFolderRecycleBinController(
IAuthorizationService authorizationService,
IElementContainerService elementContainerService,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IEntityService entityService,
IElementPresentationFactory elementPresentationFactory)
: base(entityService, elementPresentationFactory)
{
_authorizationService = authorizationService;
_elementContainerService = elementContainerService;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}
/// <summary>
/// Restores an element folder from the recycle bin to its original location or a specified parent.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier of the element folder to restore.</param>
/// <param name="moveFolderRequestModel">The model containing the target location for the restored folder.</param>
/// <returns>An <see cref="IActionResult"/> representing the outcome of the restore operation.</returns>
[HttpPut("folder/{id:guid}/restore")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[EndpointSummary("Restores an element folder from the recycle bin.")]
[EndpointDescription("Restores an element folder from the recycle bin to its original location or a specified parent.")]
public async Task<IActionResult> RestoreFolder(CancellationToken cancellationToken, Guid id, MoveFolderRequestModel moveFolderRequestModel)
{
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
User,
ElementPermissionResource.RecycleBin(ActionElementMove.ActionLetter),
AuthorizationPolicies.ElementPermissionByResource);
if (!authorizationResult.Succeeded)
{
return Forbidden();
}
Attempt<EntityContainerOperationStatus> result = await _elementContainerService.RestoreAsync(
id,
moveFolderRequestModel.Target?.Id,
CurrentUserKey(_backOfficeSecurityAccessor));
return result.Success
? Ok()
: OperationStatusResult(result.Result);
}
}
@@ -0,0 +1,84 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.ViewModels.Element;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Actions;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Security.Authorization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Element.RecycleBin;
/// <summary>
/// API controller responsible for restoring elements from the recycle bin.
/// </summary>
[ApiVersion("1.0")]
public class RestoreElementRecycleBinController : ElementRecycleBinControllerBase
{
private readonly IAuthorizationService _authorizationService;
private readonly IElementEditingService _elementEditingService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
/// <summary>
/// Initializes a new instance of the <see cref="RestoreElementRecycleBinController"/> class.
/// </summary>
/// <param name="authorizationService">Service used to authorize restore operations.</param>
/// <param name="elementEditingService">Service responsible for element editing operations.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context.</param>
/// <param name="entityService">Service for retrieving entity data.</param>
/// <param name="elementPresentationFactory">Factory responsible for creating element presentation models.</param>
public RestoreElementRecycleBinController(
IAuthorizationService authorizationService,
IElementEditingService elementEditingService,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IEntityService entityService,
IElementPresentationFactory elementPresentationFactory)
: base(entityService, elementPresentationFactory)
{
_authorizationService = authorizationService;
_elementEditingService = elementEditingService;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}
/// <summary>
/// Restores an element from the recycle bin to its original location or a specified parent.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier of the element to restore.</param>
/// <param name="moveElementRequestModel">The model containing the target location for the restored element.</param>
/// <returns>An <see cref="IActionResult"/> representing the outcome of the restore operation.</returns>
[HttpPut("{id:guid}/restore")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[EndpointSummary("Restores an element from the recycle bin.")]
[EndpointDescription("Restores an element from the recycle bin to its original location or a specified parent.")]
public async Task<IActionResult> Restore(CancellationToken cancellationToken, Guid id, MoveElementRequestModel moveElementRequestModel)
{
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
User,
ElementPermissionResource.RecycleBin(ActionElementMove.ActionLetter),
AuthorizationPolicies.ElementPermissionByResource);
if (!authorizationResult.Succeeded)
{
return Forbidden();
}
Attempt<ContentEditingOperationStatus> result = await _elementEditingService.RestoreAsync(
id,
moveElementRequestModel.Target?.Id,
CurrentUserKey(_backOfficeSecurityAccessor));
return result.Success
? Ok()
: ContentEditingOperationStatusResult(result.Result);
}
}
@@ -0,0 +1,46 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.ViewModels.Element.RecycleBin;
namespace Umbraco.Cms.Api.Management.Controllers.Element.RecycleBin;
/// <summary>
/// API controller responsible for retrieving root-level elements in the element recycle bin.
/// </summary>
[ApiVersion("1.0")]
public class RootElementRecycleBinController : ElementRecycleBinControllerBase
{
/// <summary>
/// Initializes a new instance of the <see cref="RootElementRecycleBinController"/> class.
/// </summary>
/// <param name="entityService">Service for retrieving entity data.</param>
/// <param name="elementPresentationFactory">Factory responsible for creating element presentation models.</param>
public RootElementRecycleBinController(
IEntityService entityService,
IElementPresentationFactory elementPresentationFactory)
: base(entityService, elementPresentationFactory)
{
}
/// <summary>
/// Gets a paginated collection of elements at the root level of the recycle bin.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="skip">The number of items to skip for pagination.</param>
/// <param name="take">The number of items to return for pagination.</param>
/// <returns>A paginated collection of element recycle bin items.</returns>
[HttpGet("root")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedViewModel<ElementRecycleBinItemResponseModel>), StatusCodes.Status200OK)]
[EndpointSummary("Gets elements at the root of the recycle bin.")]
[EndpointDescription("Gets a paginated collection of elements at the root level of the recycle bin.")]
public async Task<ActionResult<PagedViewModel<ElementRecycleBinItemResponseModel>>> Root(
CancellationToken cancellationToken,
int skip = 0,
int take = 100)
=> await GetRoot(skip, take);
}
@@ -0,0 +1,45 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.ViewModels.Element.RecycleBin;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.Element.RecycleBin;
/// <summary>
/// API controller responsible for retrieving sibling elements in the element recycle bin.
/// </summary>
[ApiVersion("1.0")]
public class SiblingsElementRecycleBinController : ElementRecycleBinControllerBase
{
/// <summary>
/// Initializes a new instance of the <see cref="SiblingsElementRecycleBinController"/> class.
/// </summary>
/// <param name="entityService">Service for retrieving entity data.</param>
/// <param name="elementPresentationFactory">Factory responsible for creating element presentation models.</param>
public SiblingsElementRecycleBinController(
IEntityService entityService,
IElementPresentationFactory elementPresentationFactory)
: base(entityService, elementPresentationFactory)
{
}
/// <summary>
/// Gets a collection of sibling elements in the recycle bin at the same level as the specified target.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="target">The unique identifier of the target element.</param>
/// <param name="before">The number of sibling items to retrieve before the target.</param>
/// <param name="after">The number of sibling items to retrieve after the target.</param>
/// <param name="dataTypeId">Optional data type filter.</param>
/// <returns>A subset collection of element recycle bin items.</returns>
[HttpGet("siblings")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(SubsetViewModel<ElementRecycleBinItemResponseModel>), StatusCodes.Status200OK)]
[EndpointSummary("Gets sibling elements in the recycle bin.")]
[EndpointDescription("Gets a collection of sibling elements in the recycle bin at the same level as the provided Id.")]
public async Task<ActionResult<SubsetViewModel<ElementRecycleBinItemResponseModel>>> Siblings(CancellationToken cancellationToken, Guid target, int before, int after, Guid? dataTypeId = null)
=> await GetSiblings(target, before, after);
}
@@ -0,0 +1,63 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.ViewModels;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.Element.References;
/// <summary>
/// API controller responsible for checking whether elements are referenced by other content.
/// </summary>
[ApiVersion("1.0")]
public class AreReferencedElementController : ElementControllerBase
{
private readonly ITrackedReferencesService _trackedReferencesService;
private readonly IUmbracoMapper _umbracoMapper;
/// <summary>
/// Initializes a new instance of the <see cref="AreReferencedElementController"/> class.
/// </summary>
/// <param name="trackedReferencesService">Service for tracking content references.</param>
/// <param name="umbracoMapper">Mapper for converting domain models to view models.</param>
public AreReferencedElementController(ITrackedReferencesService trackedReferencesService, IUmbracoMapper umbracoMapper)
{
_trackedReferencesService = trackedReferencesService;
_umbracoMapper = umbracoMapper;
}
/// <summary>
/// Gets a paged list of the items used in any kind of relation from selected keys.
/// </summary>
/// <remarks>
/// Used when bulk deleting elements and bulk unpublishing elements (delete and unpublish on List view).
/// This is basically finding children of relations.
/// </remarks>
[HttpGet("are-referenced")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedViewModel<ReferenceByIdModel>), StatusCodes.Status200OK)]
[EndpointSummary("Gets a collection of items that reference elements.")]
[EndpointDescription("Gets a paginated collection of items that reference the elements identified by the provided Ids.")]
public async Task<ActionResult<PagedViewModel<ReferenceByIdModel>>> GetPagedReferencedItems(
CancellationToken cancellationToken,
[FromQuery(Name = "id")] HashSet<Guid> ids,
int skip = 0,
int take = 20)
{
PagedModel<Guid> distinctByKeyItemsWithReferencedRelations = await _trackedReferencesService
.GetPagedKeysWithDependentReferencesAsync(ids, Constants.ObjectTypes.Element, skip, take);
var pagedViewModel = new PagedViewModel<ReferenceByIdModel>
{
Total = distinctByKeyItemsWithReferencedRelations.Total,
Items = _umbracoMapper.MapEnumerable<Guid, ReferenceByIdModel>(
distinctByKeyItemsWithReferencedRelations.Items),
};
return pagedViewModel;
}
}
@@ -0,0 +1,76 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Api.Management.Controllers.Element.References;
/// <summary>
/// API controller responsible for retrieving tracked references for a specific element.
/// </summary>
[ApiVersion("1.0")]
public class ReferencedByElementController : ElementControllerBase
{
private readonly ITrackedReferencesService _trackedReferencesService;
private readonly IRelationTypePresentationFactory _relationTypePresentationFactory;
/// <summary>
/// Initializes a new instance of the <see cref="ReferencedByElementController"/> class.
/// </summary>
/// <param name="trackedReferencesService">Service for tracking content references.</param>
/// <param name="relationTypePresentationFactory">Factory for creating relation type presentation models.</param>
public ReferencedByElementController(
ITrackedReferencesService trackedReferencesService,
IRelationTypePresentationFactory relationTypePresentationFactory)
{
_trackedReferencesService = trackedReferencesService;
_relationTypePresentationFactory = relationTypePresentationFactory;
}
/// <summary>
/// Gets a paged list of tracked references for the current item, so you can see where an element is being used.
/// </summary>
/// <remarks>
/// Used by info tabs on elements and for the delete and unpublish of single items.
/// This is basically finding parents of relations.
/// </remarks>
[HttpGet("{id:guid}/referenced-by")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedViewModel<IReferenceResponseModel>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[EndpointSummary("Gets items referencing an element.")]
[EndpointDescription("Gets a paginated collection of items that reference the element identified by the provided Id.")]
public async Task<IActionResult> ReferencedBy(
CancellationToken cancellationToken,
Guid id,
int skip = 0,
int take = 20)
{
Attempt<PagedModel<RelationItemModel>, GetReferencesOperationStatus> relationItemsAttempt =
await _trackedReferencesService.GetPagedRelationsForItemAsync(
id,
UmbracoObjectTypes.Element,
skip,
take,
true);
if (relationItemsAttempt.Success is false)
{
return GetReferencesOperationStatusResult(relationItemsAttempt.Status);
}
var pagedViewModel = new PagedViewModel<IReferenceResponseModel>
{
Total = relationItemsAttempt.Result.Total,
Items = await _relationTypePresentationFactory.CreateReferenceResponseModelsAsync(relationItemsAttempt.Result.Items),
};
return Ok(pagedViewModel);
}
}
@@ -0,0 +1,75 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.ViewModels;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Api.Management.Controllers.Element.References;
/// <summary>
/// API controller responsible for retrieving referenced descendants of an element folder.
/// </summary>
[ApiVersion("1.0")]
public class ReferencedDescendantsElementFolderController : ElementControllerBase
{
private readonly ITrackedReferencesService _trackedReferencesService;
private readonly IUmbracoMapper _umbracoMapper;
/// <summary>
/// Initializes a new instance of the <see cref="ReferencedDescendantsElementFolderController"/> class.
/// </summary>
/// <param name="trackedReferencesService">Service for tracking content references.</param>
/// <param name="umbracoMapper">Mapper for converting domain models to view models.</param>
public ReferencedDescendantsElementFolderController(
ITrackedReferencesService trackedReferencesService,
IUmbracoMapper umbracoMapper)
{
_trackedReferencesService = trackedReferencesService;
_umbracoMapper = umbracoMapper;
}
/// <summary>
/// Gets a paged list of the descendant nodes of the element folder used in any kind of relation.
/// </summary>
/// <remarks>
/// Used when deleting an element folder to check if it has any descending items that are in any kind of relation.
/// </remarks>
[HttpGet("folder/{id:guid}/referenced-descendants")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedViewModel<ReferenceByIdModel>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[EndpointSummary("Gets element folder descendants that are referenced.")]
[EndpointDescription("Gets a paginated collection of descendant elements in a folder that are referenced by other content.")]
public async Task<IActionResult> ReferencedDescendants(
CancellationToken cancellationToken,
Guid id,
int skip = 0,
int take = 20)
{
Attempt<PagedModel<RelationItemModel>, GetReferencesOperationStatus> relationItemsAttempt =
await _trackedReferencesService.GetPagedDescendantsInReferencesAsync(
id,
UmbracoObjectTypes.ElementContainer,
skip,
take,
true);
if (relationItemsAttempt.Success is false)
{
return GetReferencesOperationStatusResult(relationItemsAttempt.Status);
}
var pagedViewModel = new PagedViewModel<ReferenceByIdModel>
{
Total = relationItemsAttempt.Result.Total,
Items = _umbracoMapper.MapEnumerable<RelationItemModel, ReferenceByIdModel>(relationItemsAttempt.Result.Items),
};
return Ok(pagedViewModel);
}
}
@@ -0,0 +1,48 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.Services.Entities;
using Umbraco.Cms.Api.Management.Services.Flags;
using Umbraco.Cms.Api.Management.Services.PermissionFilter;
using Umbraco.Cms.Api.Management.ViewModels.Tree;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.Element.Tree;
/// <summary>
/// API controller responsible for retrieving ancestor element tree items.
/// </summary>
[ApiVersion("1.0")]
public class AncestorsElementTreeController : ElementTreeControllerBase
{
/// <inheritdoc />
public AncestorsElementTreeController(
IEntityService entityService,
FlagProviderCollection flagProviders,
IUserStartNodeEntitiesService userStartNodeEntitiesService,
IDataTypeService dataTypeService,
AppCaches appCaches,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IElementPresentationFactory elementPresentationFactory,
IElementPermissionFilterService elementPermissionFilterService)
: base(entityService, flagProviders, userStartNodeEntitiesService, dataTypeService, appCaches, backOfficeSecurityAccessor, elementPresentationFactory, elementPermissionFilterService)
{
}
/// <summary>
/// Gets a collection of ancestor element tree items for the specified descendant.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="descendantId">The unique identifier of the descendant element.</param>
/// <returns>A collection of ancestor element tree items.</returns>
[HttpGet("ancestors")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(IEnumerable<ElementTreeItemResponseModel>), StatusCodes.Status200OK)]
[EndpointSummary("Gets a collection of ancestor element items.")]
[EndpointDescription("Gets a collection of element items that are ancestors to the provided Id.")]
public async Task<ActionResult<IEnumerable<ElementTreeItemResponseModel>>> Ancestors(CancellationToken cancellationToken, Guid descendantId)
=> await GetAncestors(descendantId);
}
@@ -0,0 +1,57 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.Services.Entities;
using Umbraco.Cms.Api.Management.Services.Flags;
using Umbraco.Cms.Api.Management.Services.PermissionFilter;
using Umbraco.Cms.Api.Management.ViewModels.Tree;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.Element.Tree;
/// <summary>
/// API controller responsible for retrieving child element tree items.
/// </summary>
[ApiVersion("1.0")]
public class ChildrenElementTreeController : ElementTreeControllerBase
{
/// <inheritdoc />
public ChildrenElementTreeController(
IEntityService entityService,
FlagProviderCollection flagProviders,
IUserStartNodeEntitiesService userStartNodeEntitiesService,
IDataTypeService dataTypeService,
AppCaches appCaches,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IElementPresentationFactory elementPresentationFactory,
IElementPermissionFilterService elementPermissionFilterService)
: base(entityService, flagProviders, userStartNodeEntitiesService, dataTypeService, appCaches, backOfficeSecurityAccessor, elementPresentationFactory, elementPermissionFilterService)
{
}
/// <summary>
/// Gets a paginated collection of child element tree items for the specified parent.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="parentId">The unique identifier of the parent element.</param>
/// <param name="skip">The number of items to skip for pagination.</param>
/// <param name="take">The number of items to return for pagination.</param>
/// <param name="foldersOnly">Whether to return only folder items.</param>
/// <param name="dataTypeId">An optional data type identifier to filter the element items.</param>
/// <returns>A paginated collection of child element tree items.</returns>
[HttpGet("children")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedViewModel<ElementTreeItemResponseModel>), StatusCodes.Status200OK)]
[EndpointSummary("Gets a collection of element tree child items.")]
[EndpointDescription("Gets a paginated collection of element tree items that are children of the provided parent Id.")]
public async Task<ActionResult<PagedViewModel<ElementTreeItemResponseModel>>> Children(CancellationToken cancellationToken, Guid parentId, int skip = 0, int take = 100, bool foldersOnly = false, Guid? dataTypeId = null)
{
RenderFoldersOnly(foldersOnly);
IgnoreUserStartNodesForDataType(dataTypeId);
return await GetChildren(parentId, skip, take);
}
}
@@ -0,0 +1,114 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Controllers.Tree;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Api.Management.Services.Entities;
using Umbraco.Cms.Api.Management.Services.Flags;
using Umbraco.Cms.Api.Management.Services.PermissionFilter;
using Umbraco.Cms.Api.Management.ViewModels.Tree;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Entities;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.Element.Tree;
/// <summary>
/// Serves as the base controller for element tree operations within the Umbraco CMS Management API.
/// Provides shared functionality for derived element tree controllers including user start node support and permission filtering.
/// </summary>
[VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Tree}/{Constants.UdiEntityType.Element}")]
[ApiExplorerSettings(GroupName = nameof(Constants.UdiEntityType.Element))]
[Authorize(Policy = AuthorizationPolicies.SectionAccessForElementTree)]
public class ElementTreeControllerBase : UserStartNodeFolderTreeControllerBase<ElementTreeItemResponseModel>
{
private readonly AppCaches _appCaches;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
private readonly IElementPresentationFactory _elementPresentationFactory;
private readonly IElementPermissionFilterService _elementPermissionFilterService;
/// <summary>
/// Initializes a new instance of the <see cref="ElementTreeControllerBase"/> class.
/// </summary>
/// <param name="entityService">Service for retrieving entity data.</param>
/// <param name="flagProviders">Collection of flag providers for tree item flags.</param>
/// <param name="userStartNodeEntitiesService">Service for user start node entity resolution.</param>
/// <param name="dataTypeService">Service for data type operations.</param>
/// <param name="appCaches">Application caches for performance optimization.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context.</param>
/// <param name="elementPresentationFactory">Factory responsible for creating element presentation models.</param>
/// <param name="elementPermissionFilterService">Service for filtering tree entities based on element permissions.</param>
public ElementTreeControllerBase(
IEntityService entityService,
FlagProviderCollection flagProviders,
IUserStartNodeEntitiesService userStartNodeEntitiesService,
IDataTypeService dataTypeService,
AppCaches appCaches,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IElementPresentationFactory elementPresentationFactory,
IElementPermissionFilterService elementPermissionFilterService)
: base(entityService, flagProviders, userStartNodeEntitiesService, dataTypeService)
{
_appCaches = appCaches;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
_elementPresentationFactory = elementPresentationFactory;
_elementPermissionFilterService = elementPermissionFilterService;
}
protected override UmbracoObjectTypes ItemObjectType => UmbracoObjectTypes.Element;
protected override UmbracoObjectTypes FolderObjectType => UmbracoObjectTypes.ElementContainer;
protected override int[] GetUserStartNodeIds()
=> _backOfficeSecurityAccessor
.BackOfficeSecurity?
.CurrentUser?
.CalculateElementStartNodeIds(EntityService, _appCaches)
?? [];
protected override string[] GetUserStartNodePaths()
=> _backOfficeSecurityAccessor
.BackOfficeSecurity?
.CurrentUser?
.GetElementStartNodePaths(EntityService, _appCaches)
?? [];
protected override async Task<ElementTreeItemResponseModel> MapTreeItemViewModelAsync(Guid? parentKey, IEntitySlim entity)
{
ElementTreeItemResponseModel responseModel = await base.MapTreeItemViewModelAsync(parentKey, entity);
if (entity is IElementEntitySlim elementEntitySlim)
{
responseModel.CreateDate = elementEntitySlim.CreateDate;
responseModel.DocumentType = _elementPresentationFactory.CreateDocumentTypeReferenceResponseModel(elementEntitySlim);
responseModel.Variants = await _elementPresentationFactory.CreateVariantsItemResponseModelsAsync(elementEntitySlim);
}
return responseModel;
}
protected override async Task<ElementTreeItemResponseModel> MapTreeItemViewModelAsNoAccessAsync(Guid? parentKey, IEntitySlim entity)
{
ElementTreeItemResponseModel viewModel = await MapTreeItemViewModelAsync(parentKey, entity);
viewModel.NoAccess = true;
return viewModel;
}
/// <inheritdoc/>
protected override Task<(IEntitySlim[] Entities, long TotalItems)> FilterTreeEntities(
IEntitySlim[] entities,
long totalItems)
=> _elementPermissionFilterService.FilterAsync(entities, totalItems);
/// <inheritdoc/>
protected override Task<(IEntitySlim[] Entities, long TotalBefore, long TotalAfter)> FilterTreeEntities(
Guid targetKey,
IEntitySlim[] entities,
long totalBefore,
long totalAfter)
=> _elementPermissionFilterService.FilterAsync(targetKey, entities, totalBefore, totalAfter);
}
@@ -0,0 +1,61 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.Services.Entities;
using Umbraco.Cms.Api.Management.Services.Flags;
using Umbraco.Cms.Api.Management.Services.PermissionFilter;
using Umbraco.Cms.Api.Management.ViewModels.Tree;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.Element.Tree;
/// <summary>
/// API controller responsible for retrieving root-level element tree items.
/// </summary>
[ApiVersion("1.0")]
public class RootElementTreeController : ElementTreeControllerBase
{
/// <inheritdoc />
public RootElementTreeController(
IEntityService entityService,
FlagProviderCollection flagProviders,
IUserStartNodeEntitiesService userStartNodeEntitiesService,
IDataTypeService dataTypeService,
AppCaches appCaches,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IElementPresentationFactory elementPresentationFactory,
IElementPermissionFilterService elementPermissionFilterService)
: base(entityService, flagProviders, userStartNodeEntitiesService, dataTypeService, appCaches, backOfficeSecurityAccessor, elementPresentationFactory, elementPermissionFilterService)
{
}
/// <summary>
/// Gets a paginated collection of element tree items from the root level.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="skip">The number of items to skip for pagination.</param>
/// <param name="take">The number of items to return for pagination.</param>
/// <param name="foldersOnly">Whether to return only folder items.</param>
/// <param name="dataTypeId">An optional identifier to filter element items by data type.</param>
/// <returns>A paginated collection of root element tree items.</returns>
[HttpGet("root")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedViewModel<ElementTreeItemResponseModel>), StatusCodes.Status200OK)]
[EndpointSummary("Gets a collection of element items from the root of the tree.")]
[EndpointDescription("Gets a paginated collection of element items from the root of the tree with optional filtering.")]
public async Task<ActionResult<PagedViewModel<ElementTreeItemResponseModel>>> Root(
CancellationToken cancellationToken,
int skip = 0,
int take = 100,
bool foldersOnly = false,
Guid? dataTypeId = null)
{
RenderFoldersOnly(foldersOnly);
IgnoreUserStartNodesForDataType(dataTypeId);
return await GetRoot(skip, take);
}
}
@@ -0,0 +1,63 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.Services.Entities;
using Umbraco.Cms.Api.Management.Services.Flags;
using Umbraco.Cms.Api.Management.Services.PermissionFilter;
using Umbraco.Cms.Api.Management.ViewModels.Tree;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.Element.Tree;
/// <summary>
/// API controller responsible for retrieving sibling element tree items.
/// </summary>
[ApiVersion("1.0")]
public class SiblingsElementTreeController : ElementTreeControllerBase
{
/// <inheritdoc />
public SiblingsElementTreeController(
IEntityService entityService,
FlagProviderCollection flagProviders,
IUserStartNodeEntitiesService userStartNodeEntitiesService,
IDataTypeService dataTypeService,
AppCaches appCaches,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IElementPresentationFactory elementPresentationFactory,
IElementPermissionFilterService elementPermissionFilterService)
: base(entityService, flagProviders, userStartNodeEntitiesService, dataTypeService, appCaches, backOfficeSecurityAccessor, elementPresentationFactory, elementPermissionFilterService)
{
}
/// <summary>
/// Gets a collection of sibling element tree items for the specified target.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="target">The unique identifier of the target element.</param>
/// <param name="before">The number of sibling items to retrieve before the target.</param>
/// <param name="after">The number of sibling items to retrieve after the target.</param>
/// <param name="foldersOnly">Whether to return only folder items.</param>
/// <param name="dataTypeId">An optional data type identifier to filter the sibling items.</param>
/// <returns>A subset collection of sibling element tree items.</returns>
[HttpGet("siblings")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(SubsetViewModel<ElementTreeItemResponseModel>), StatusCodes.Status200OK)]
[EndpointSummary("Gets a collection of element tree sibling items.")]
[EndpointDescription("Gets a collection of element tree items that are siblings of the provided Id.")]
public async Task<ActionResult<SubsetViewModel<ElementTreeItemResponseModel>>> Siblings(
CancellationToken cancellationToken,
Guid target,
int before,
int after,
bool foldersOnly = false,
Guid? dataTypeId = null)
{
RenderFoldersOnly(foldersOnly);
IgnoreUserStartNodesForDataType(dataTypeId);
return await GetSiblings(target, before, after);
}
}
@@ -0,0 +1,80 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.Element;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Actions;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Security.Authorization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Element;
/// <summary>
/// API controller responsible for handling unpublish operations on elements in the Umbraco CMS.
/// </summary>
[ApiVersion("1.0")]
public class UnpublishElementController : ElementControllerBase
{
private readonly IAuthorizationService _authorizationService;
private readonly IElementPublishingService _elementPublishingService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
/// <summary>
/// Initializes a new instance of the <see cref="UnpublishElementController"/> class.
/// </summary>
/// <param name="authorizationService">Service used to authorize element unpublish operations.</param>
/// <param name="elementPublishingService">Service responsible for unpublishing elements.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context.</param>
public UnpublishElementController(
IAuthorizationService authorizationService,
IElementPublishingService elementPublishingService,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
{
_authorizationService = authorizationService;
_elementPublishingService = elementPublishingService;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}
/// <summary>
/// Unpublishes an element identified by the specified unique identifier.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier of the element to unpublish.</param>
/// <param name="requestModel">The model containing the cultures to unpublish.</param>
/// <returns>An <see cref="IActionResult"/> representing the outcome of the unpublish operation.</returns>
[HttpPut("{id:guid}/unpublish")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[EndpointSummary("Unpublishes an element.")]
[EndpointDescription("Unpublishes an element identified by the provided Id.")]
public async Task<IActionResult> Unpublish(CancellationToken cancellationToken, Guid id, UnpublishElementRequestModel requestModel)
{
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
User,
ElementPermissionResource.WithKeys(
ActionElementUnpublish.ActionLetter,
id,
requestModel.Cultures ?? Enumerable.Empty<string>()),
AuthorizationPolicies.ElementPermissionByResource);
if (!authorizationResult.Succeeded)
{
return Forbidden();
}
Attempt<ContentPublishingOperationStatus> attempt = await _elementPublishingService.UnpublishAsync(
id,
requestModel.Cultures,
CurrentUserKey(_backOfficeSecurityAccessor));
return attempt.Success
? Ok()
: ElementPublishingOperationStatusResult(attempt.Result);
}
}
@@ -0,0 +1,67 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.ViewModels.Element;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Api.Management.Controllers.Element;
/// <summary>
/// Controller responsible for handling update operations on elements in the management API.
/// </summary>
[ApiVersion("1.0")]
public class UpdateElementController : UpdateElementControllerBase
{
private readonly IElementEditingPresentationFactory _elementEditingPresentationFactory;
private readonly IElementEditingService _elementEditingService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
/// <summary>
/// Initializes a new instance of the <see cref="UpdateElementController"/> class.
/// </summary>
/// <param name="authorizationService">Service for verifying user permissions.</param>
/// <param name="elementEditingPresentationFactory">Factory for creating element editing presentation models.</param>
/// <param name="elementEditingService">Service for managing element updates.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for the back office user security context.</param>
public UpdateElementController(
IAuthorizationService authorizationService,
IElementEditingPresentationFactory elementEditingPresentationFactory,
IElementEditingService elementEditingService,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
: base(authorizationService)
{
_elementEditingPresentationFactory = elementEditingPresentationFactory;
_elementEditingService = elementEditingService;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}
/// <summary>Updates the specified element with new details provided in the request model.</summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier of the element to update.</param>
/// <param name="requestModel">The model containing the updated element details.</param>
/// <returns>An <see cref="IActionResult"/> representing the outcome of the update operation.</returns>
[HttpPut("{id:guid}")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[EndpointSummary("Updates an element.")]
[EndpointDescription("Updates an element identified by the provided Id with the details from the request model.")]
public async Task<IActionResult> Update(CancellationToken cancellationToken, Guid id, UpdateElementRequestModel requestModel)
=> await HandleRequest(id, requestModel, async () =>
{
ElementUpdateModel model = _elementEditingPresentationFactory.MapUpdateModel(requestModel);
Attempt<ElementUpdateResult, ContentEditingOperationStatus> result =
await _elementEditingService.UpdateAsync(id, model, CurrentUserKey(_backOfficeSecurityAccessor));
return result.Success
? Ok()
: ContentEditingOperationStatusResult(result.Status);
});
}
@@ -0,0 +1,40 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.Element;
using Umbraco.Cms.Core.Actions;
using Umbraco.Cms.Core.Security.Authorization;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Element;
/// <summary>
/// Serves as the base controller for handling update operations on elements within the Umbraco CMS Management API.
/// Provides common functionality for derived controllers that manage element updates.
/// </summary>
public abstract class UpdateElementControllerBase : ElementControllerBase
{
private readonly IAuthorizationService _authorizationService;
/// <summary>
/// Initializes a new instance of the <see cref="UpdateElementControllerBase"/> class.
/// </summary>
/// <param name="authorizationService">Service used to authorize access to element update operations.</param>
protected UpdateElementControllerBase(IAuthorizationService authorizationService)
=> _authorizationService = authorizationService;
protected async Task<IActionResult> HandleRequest(Guid id, UpdateElementRequestModel requestModel, Func<Task<IActionResult>> authorizedHandler)
{
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
User,
ElementPermissionResource.WithKeys(ActionElementUpdate.ActionLetter, id),
AuthorizationPolicies.ElementPermissionByResource);
if (authorizationResult.Succeeded is false)
{
return Forbidden();
}
return await authorizedHandler();
}
}
@@ -0,0 +1,68 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.ViewModels.Element;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Api.Management.Controllers.Element;
/// <summary>
/// API controller responsible for validating element creation requests without persisting changes.
/// </summary>
[ApiVersion("1.0")]
public class ValidateCreateElementController : CreateElementControllerBase
{
private readonly IElementEditingPresentationFactory _elementEditingPresentationFactory;
private readonly IElementEditingService _elementEditingService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
/// <summary>
/// Initializes a new instance of the <see cref="ValidateCreateElementController"/> class.
/// </summary>
/// <param name="authorizationService">Service used to authorize access to element creation operations.</param>
/// <param name="elementEditingPresentationFactory">Factory for creating element editing presentation models.</param>
/// <param name="elementEditingService">Service responsible for element editing functionality.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context.</param>
public ValidateCreateElementController(
IAuthorizationService authorizationService,
IElementEditingPresentationFactory elementEditingPresentationFactory,
IElementEditingService elementEditingService,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
: base(authorizationService)
{
_elementEditingPresentationFactory = elementEditingPresentationFactory;
_elementEditingService = elementEditingService;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}
/// <summary>
/// Validates the request model for creating a new element without actually creating it.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="requestModel">The element creation request model to validate.</param>
/// <returns>An <see cref="IActionResult"/> representing the validation result.</returns>
[HttpPost("validate")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[EndpointSummary("Validates creating an element.")]
[EndpointDescription("Validates the request model for creating a new element without actually creating it.")]
public async Task<IActionResult> Validate(CancellationToken cancellationToken, CreateElementRequestModel requestModel)
=> await HandleRequest(requestModel, async () =>
{
ElementCreateModel model = _elementEditingPresentationFactory.MapCreateModel(requestModel);
Attempt<ContentValidationResult, ContentEditingOperationStatus> result =
await _elementEditingService.ValidateCreateAsync(model, CurrentUserKey(_backOfficeSecurityAccessor));
return result.Success
? Ok()
: ElementEditingOperationStatusResult(result.Status, requestModel, result.Result);
});
}
@@ -0,0 +1,69 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.ViewModels.Element;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Api.Management.Controllers.Element;
/// <summary>
/// API controller responsible for validating element update requests without persisting changes.
/// </summary>
[ApiVersion("1.0")]
public class ValidateUpdateElementController : UpdateElementControllerBase
{
private readonly IElementEditingPresentationFactory _elementEditingPresentationFactory;
private readonly IElementEditingService _elementEditingService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
/// <summary>
/// Initializes a new instance of the <see cref="ValidateUpdateElementController"/> class.
/// </summary>
/// <param name="authorizationService">Service used to authorize access to element update operations.</param>
/// <param name="elementEditingPresentationFactory">Factory for creating element editing presentation models.</param>
/// <param name="elementEditingService">Service responsible for element editing functionality.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context.</param>
public ValidateUpdateElementController(
IAuthorizationService authorizationService,
IElementEditingPresentationFactory elementEditingPresentationFactory,
IElementEditingService elementEditingService,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
: base(authorizationService)
{
_elementEditingPresentationFactory = elementEditingPresentationFactory;
_elementEditingService = elementEditingService;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}
/// <summary>
/// Validates the request model for updating an element without actually updating it.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier of the element to validate the update for.</param>
/// <param name="requestModel">The element update request model to validate.</param>
/// <returns>An <see cref="IActionResult"/> representing the validation result.</returns>
[HttpPut("{id:guid}/validate")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[EndpointSummary("Validates updating an element.")]
[EndpointDescription("Validates the request model for updating an element without actually updating it.")]
public async Task<IActionResult> Validate(CancellationToken cancellationToken, Guid id, ValidateUpdateElementRequestModel requestModel)
=> await HandleRequest(id, requestModel, async () =>
{
ValidateElementUpdateModel model = _elementEditingPresentationFactory.MapValidateUpdateModel(requestModel);
Attempt<ContentValidationResult, ContentEditingOperationStatus> result =
await _elementEditingService.ValidateUpdateAsync(id, model, CurrentUserKey(_backOfficeSecurityAccessor));
return result.Success
? Ok()
: ElementEditingOperationStatusResult(result.Status, requestModel, result.Result);
});
}
@@ -0,0 +1,76 @@
using System.ComponentModel.DataAnnotations;
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.ViewModels.Element;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Api.Management.Controllers.ElementVersion;
/// <summary>
/// API controller responsible for retrieving all versions of a specific element.
/// </summary>
[ApiVersion("1.0")]
public class AllElementVersionController : ElementVersionControllerBase
{
private readonly IElementVersionService _elementVersionService;
private readonly IElementVersionPresentationFactory _elementVersionPresentationFactory;
/// <summary>
/// Initializes a new instance of the <see cref="AllElementVersionController"/> class.
/// </summary>
/// <param name="elementVersionService">Service for managing element versions.</param>
/// <param name="elementVersionPresentationFactory">Factory for creating element version presentation models.</param>
public AllElementVersionController(
IElementVersionService elementVersionService,
IElementVersionPresentationFactory elementVersionPresentationFactory)
{
_elementVersionService = elementVersionService;
_elementVersionPresentationFactory = elementVersionPresentationFactory;
}
/// <summary>
/// Gets a paginated collection of versions for a specific element.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="elementId">The unique identifier of the element to retrieve versions for.</param>
/// <param name="culture">Optional culture filter for variant elements.</param>
/// <param name="skip">The number of items to skip for pagination.</param>
/// <param name="take">The number of items to return for pagination.</param>
/// <returns>An <see cref="IActionResult"/> containing a paginated collection of element versions.</returns>
[MapToApiVersion("1.0")]
[HttpGet]
[ProducesResponseType(typeof(PagedViewModel<ElementVersionItemResponseModel>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[EndpointSummary("Gets a paginated collection of versions for a specific element.")]
[EndpointDescription("Gets a paginated collection of versions for a specific element and optional culture. Each result describes the version and includes details of the element type, editor, version date, and published status.")]
public async Task<IActionResult> All(
CancellationToken cancellationToken,
[Required] Guid elementId,
string? culture,
int skip = 0,
int take = 100)
{
Attempt<PagedModel<ContentVersionMeta>?, ContentVersionOperationStatus> attempt =
await _elementVersionService.GetPagedContentVersionsAsync(elementId, culture, skip, take);
if (attempt.Success is false)
{
return MapFailure(attempt.Status);
}
var pagedViewModel = new PagedViewModel<ElementVersionItemResponseModel>
{
Total = attempt.Result!.Total,
Items = await _elementVersionPresentationFactory.CreateMultipleAsync(attempt.Result!.Items),
};
return Ok(pagedViewModel);
}
}
@@ -0,0 +1,57 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.Element;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Api.Management.Controllers.ElementVersion;
/// <summary>
/// Controller for retrieving a specific element version by its unique key.
/// </summary>
[ApiVersion("1.0")]
public class ByKeyElementVersionController : ElementVersionControllerBase
{
private readonly IElementVersionService _elementVersionService;
private readonly IUmbracoMapper _umbracoMapper;
/// <summary>
/// Initializes a new instance of the <see cref="ByKeyElementVersionController"/> class.
/// </summary>
/// <param name="elementVersionService">Service for managing element versions.</param>
/// <param name="umbracoMapper">Mapper for converting domain models to view models.</param>
public ByKeyElementVersionController(
IElementVersionService elementVersionService,
IUmbracoMapper umbracoMapper)
{
_elementVersionService = elementVersionService;
_umbracoMapper = umbracoMapper;
}
/// <summary>
/// Retrieves a specific element version by its unique identifier.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier of the element version to retrieve.</param>
/// <returns>An <see cref="IActionResult"/> containing the element version if found.</returns>
[MapToApiVersion("1.0")]
[HttpGet("{id:guid}")]
[ProducesResponseType(typeof(ElementVersionResponseModel), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[EndpointSummary("Gets a specific element version.")]
[EndpointDescription("Gets a specific element version by its Id. If found, the result describes the version and includes details of the element type, editor, version date, and published status.")]
public async Task<IActionResult> ByKey(CancellationToken cancellationToken, Guid id)
{
Attempt<IElement?, ContentVersionOperationStatus> attempt =
await _elementVersionService.GetAsync(id);
return attempt.Success
? Ok(_umbracoMapper.Map<ElementVersionResponseModel>(attempt.Result))
: MapFailure(attempt.Status);
}
}
@@ -0,0 +1,22 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Controllers.DocumentVersion;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.ElementVersion;
/// <summary>
/// Serves as the base controller for element version management operations within the Umbraco CMS Management API.
/// Provides shared functionality for derived element version controllers.
/// </summary>
[VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.Element}-version")]
[ApiExplorerSettings(GroupName = $"{nameof(Constants.UdiEntityType.Element)} Version")]
[Authorize(Policy = AuthorizationPolicies.TreeAccessElements)]
public abstract class ElementVersionControllerBase : ManagementApiControllerBase
{
protected IActionResult MapFailure(ContentVersionOperationStatus status)
=> DocumentVersionControllerBase.MapFailure(status);
}
@@ -0,0 +1,83 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Actions;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Security.Authorization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.ElementVersion;
/// <summary>
/// API controller responsible for rolling back an element to a specific version.
/// </summary>
[ApiVersion("1.0")]
public class RollbackElementVersionController : ElementVersionControllerBase
{
private readonly IElementVersionService _elementVersionService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
private readonly IAuthorizationService _authorizationService;
/// <summary>
/// Initializes a new instance of the <see cref="RollbackElementVersionController"/> class.
/// </summary>
/// <param name="elementVersionService">Service for managing element versions.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context.</param>
/// <param name="authorizationService">Service used to authorize rollback operations.</param>
public RollbackElementVersionController(
IElementVersionService elementVersionService,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IAuthorizationService authorizationService)
{
_elementVersionService = elementVersionService;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
_authorizationService = authorizationService;
}
/// <summary>
/// Rolls back an element to the version identified by the specified unique identifier.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier of the element version to roll back to.</param>
/// <param name="culture">Optional culture to target for variant elements.</param>
/// <returns>An <see cref="IActionResult"/> representing the outcome of the rollback operation.</returns>
[MapToApiVersion("1.0")]
[HttpPost("{id:guid}/rollback")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[EndpointSummary("Rolls back an element to a specific version.")]
[EndpointDescription("Rolls back an element to the version indicated by the provided Id. This will archive the current version of the element and publish the provided one.")]
public async Task<IActionResult> Rollback(CancellationToken cancellationToken, Guid id, string? culture)
{
Attempt<IElement?, ContentVersionOperationStatus> getContentAttempt = await _elementVersionService.GetAsync(id);
if (getContentAttempt.Success is false || getContentAttempt.Result is null)
{
return MapFailure(getContentAttempt.Status);
}
IElement element = getContentAttempt.Result;
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
User,
ElementPermissionResource.WithKeys(ActionElementRollback.ActionLetter, element.Key),
AuthorizationPolicies.ElementPermissionByResource);
if (!authorizationResult.Succeeded)
{
return Forbidden();
}
Attempt<ContentVersionOperationStatus> rollBackAttempt =
await _elementVersionService.RollBackAsync(id, culture, CurrentUserKey(_backOfficeSecurityAccessor));
return rollBackAttempt.Success
? Ok()
: MapFailure(rollBackAttempt.Result);
}
}
@@ -0,0 +1,56 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Api.Management.Controllers.ElementVersion;
/// <summary>
/// API controller responsible for updating the prevent-cleanup status of an element version.
/// </summary>
[ApiVersion("1.0")]
public class UpdatePreventCleanupElementVersionController : ElementVersionControllerBase
{
private readonly IElementVersionService _elementVersionService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
/// <summary>
/// Initializes a new instance of the <see cref="UpdatePreventCleanupElementVersionController"/> class.
/// </summary>
/// <param name="elementVersionService">Service for managing element versions.</param>
/// <param name="backOfficeSecurityAccessor">Accessor for back office security context.</param>
public UpdatePreventCleanupElementVersionController(
IElementVersionService elementVersionService,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
{
_elementVersionService = elementVersionService;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}
/// <summary>
/// Sets the prevent-cleanup status for an element version.
/// </summary>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <param name="id">The unique identifier of the element version.</param>
/// <param name="preventCleanup">Whether the version should be excluded from content history cleanup.</param>
/// <returns>An <see cref="IActionResult"/> representing the outcome of the operation.</returns>
[MapToApiVersion("1.0")]
[HttpPut("{id:guid}/prevent-cleanup")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[EndpointSummary("Sets the prevent clean up status for an element version.")]
[EndpointDescription("Sets the prevent clean up boolean status for an element version to the provided value. This controls whether the version will be a candidate for removal in content history clean up.")]
public async Task<IActionResult> Set(CancellationToken cancellationToken, Guid id, bool preventCleanup)
{
Attempt<ContentVersionOperationStatus> attempt =
await _elementVersionService.SetPreventCleanupAsync(id, preventCleanup, CurrentUserKey(_backOfficeSecurityAccessor));
return attempt.Success
? Ok()
: MapFailure(attempt.Result);
}
}
@@ -42,7 +42,8 @@ public abstract class FolderManagementControllerBase<TTreeEntity> : ManagementAp
return Ok(new FolderResponseModel
{
Name = container.Name!,
Id = container.Key
Id = container.Key,
IsTrashed = container.Trashed
});
}
@@ -81,33 +82,37 @@ public abstract class FolderManagementControllerBase<TTreeEntity> : ManagementAp
: OperationStatusResult(result.Status);
}
protected IActionResult OperationStatusResult(EntityContainerOperationStatus status)
internal static IActionResult OperationStatusResult(EntityContainerOperationStatus status)
=> OperationStatusResult(status, problemDetailsBuilder => status switch
{
EntityContainerOperationStatus.NotFound => NotFound(problemDetailsBuilder
EntityContainerOperationStatus.NotFound => new NotFoundObjectResult(problemDetailsBuilder
.WithTitle("The folder could not be found")
.Build()),
EntityContainerOperationStatus.ParentNotFound => NotFound(problemDetailsBuilder
EntityContainerOperationStatus.ParentNotFound => new NotFoundObjectResult(problemDetailsBuilder
.WithTitle("The parent folder could not be found")
.Build()),
EntityContainerOperationStatus.DuplicateName => BadRequest(problemDetailsBuilder
EntityContainerOperationStatus.DuplicateName => new BadRequestObjectResult(problemDetailsBuilder
.WithTitle("The name is already used")
.WithDetail("The folder name must be unique on this parent.")
.Build()),
EntityContainerOperationStatus.DuplicateKey => BadRequest(problemDetailsBuilder
EntityContainerOperationStatus.DuplicateKey => new BadRequestObjectResult(problemDetailsBuilder
.WithTitle("The id is already used")
.WithDetail("The folder id must be unique.")
.Build()),
EntityContainerOperationStatus.NotEmpty => BadRequest(problemDetailsBuilder
EntityContainerOperationStatus.NotEmpty => new BadRequestObjectResult(problemDetailsBuilder
.WithTitle("The folder is not empty")
.WithDetail("The folder must be empty to perform this action.")
.Build()),
EntityContainerOperationStatus.CancelledByNotification => BadRequest(problemDetailsBuilder
EntityContainerOperationStatus.CancelledByNotification => new BadRequestObjectResult(problemDetailsBuilder
.WithTitle("Cancelled by notification")
.WithDetail("A notification handler prevented the folder operation.")
.Build()),
_ => StatusCode(StatusCodes.Status500InternalServerError, problemDetailsBuilder
.WithTitle("Unknown folder operation status.")
EntityContainerOperationStatus.HasReferencedDescendants => new BadRequestObjectResult(problemDetailsBuilder
.WithTitle("The folder has referenced descendants")
.WithDetail("The folder cannot be moved to the recycle bin because it contains items that are referenced by other content.")
.Build()),
_ => new ObjectResult(problemDetailsBuilder
.WithTitle("Unknown folder operation status.")
.Build()) { StatusCode = StatusCodes.Status500InternalServerError },
});
}
@@ -72,8 +72,8 @@ public abstract class ManagementApiControllerBase : Controller, IUmbracoFeature
where TEnum : Enum
=> result(new ProblemDetailsBuilder().WithOperationStatus(status));
protected BadRequestObjectResult SkipTakeToPagingProblem() =>
BadRequest(new ProblemDetails
protected static BadRequestObjectResult SkipTakeToPagingProblem() =>
new(new ProblemDetails
{
Title = "Invalid skip/take",
Detail = "Skip must be a multiple of take - i.e. skip = 10, take = 5",

Some files were not shown because too many files have changed in this diff Show More